nestjs动态配置接入的原因
应用程序通常在不同的环境中运行。根据环境的不同,应使用不同的配置设置。例如,通常本地环境依赖于本机的数据库凭证,仅对本地数据库实例有效。生产环境将使用一组单独的数据库凭据。由于配置变量会发生变化,因此最佳实践是在环境中存储配置变量。
如何安装配置
1
| $ npm i --save @nestjs/config
|
@nestjs/config 的内部其实使用的是 dotenv
如何使用配置
1 2 3 4 5 6 7 8 9
| ConfigModule.forRoot({ isGlobal: true, envFilePath: process.env.NODE_ENV === 'production' ? [] : [`.env.${process.env.NODE_ENV || 'development'}`], load: [configFile], })
|
默认代码是从项目的根目录加载 .env 文件,合并 env 文件中的键值对,并分配给 process.env 环境变量,并且可以通过 configService 来读取环境变量
环境变量文件应该是这样的:
1 2
| DATABASE_USER=test DATABASE_PASSWORD=test
|
我们优先是将所有的配置文件放在根目录,并用不同的环境来区分
例如 .env.development 、 .env.test 、 .env.production 分别代表本地、测试和线上环境
当然我们也可以有一些无关紧要的通用配置,比如 .env.local
1 2 3 4 5 6 7 8 9 10
| ConfigModule.forRoot({ envFilePath: '.env.development', });
ConfigModule.forRoot({ envFilePath: ['.env.local', '.env.development'], });
|
通常我们是全局使用配置,所以可以加入 isGlobal 用来标记全局使用
如何加载配置项
一般我们的项目会涉及到多个不同类型的配置,比如 redis,mongo,mysql,es等等
所以我们的最佳实践应该是将不同类型的配置文件分开存放
1 2 3 4 5 6 7 8 9
| config | ——————mongo | ——————mysql | ——————es | ——————redis
|
这样我们可以快速的查看配置项
例如下面的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| export default () => ({ port: parseInt(process.env.PORT, 10) || 3000, database: { host: process.env.DATABASE_HOST, port: parseInt(process.env.DATABASE_PORT, 10) || 5432 } });
import configuration from './config/configuration'; @Module({ imports: [ ConfigModule.forRoot({ load: [configuration], }), ], }) export class AppModule {}
|
我们注意到 load 方法是一个数组,这也就意味着我们可以引入多个配置文件
在nestjs接入数据库、mongo一章我们讲到了如何在nestjs中加载mysql配置,但是我们是直接在代码中写死的,现在我们来改写一下,通过加载本地的 .env.development 文件配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { ConfigModule, ConfigService } from '@nestjs/config';
TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => { const mysql = configService.getOrThrow('mysql', { infer: true }); return { ...mysql, type: 'mysql', autoLoadEntities: true, synchronize: false, }; }, })
|
其次我们需要知道mysql的配置从哪里来
1 2 3 4 5 6 7 8 9
| config | ——————mongo | ——————mysql (index.ts) | ——————es | ——————redis
|
我们现在来编写mysql下的 index.ts 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import { registerAs } from '@nestjs/config';
export type MysqlConfig = { host?: string; port: number; username?: string; password?: string; database?: string; };
export default registerAs<MysqlConfig>('mysql', () => { return { host: process.env.MYSQL_HOST, port: Number(process.env.MYSQL_PORT || 3306), username: process.env.MYSQL_USER, password: process.env.MYSQL_PASSWORD, database: process.env.MYSQL_DB, }; });
|
那 process.env 下面的变量在哪里定义呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| // .env.development
NODE_ENV=development APP_PORT=3000
MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USER=root MYSQL_PASSWORD=root MYSQL_DB=db_nest
MONGO_URI=mongodb://root:root@localhost:27017/db_nest
|
现在我们把所有配置都已经准备齐全了,那我们通过 configService.getOrThrow('mysql', { infer: true }); 拿到的 mysql 就是 MysqlConfig 命名空间下的导出的所有配置字段
同理,我们还会接入 mongoose 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| MongooseModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], useFactory: (configService: ConfigService) => { const mongo = configService.getOrThrow('mongo', { infer: true }); return { uri: mongo.uri, }; }, }),
import { registerAs } from '@nestjs/config';
export type MongoConfig = { uri?: string; };
export default registerAs<MongoConfig>('mongo', () => { return { uri: process.env.MONGO_URI, }; });
|
这基本上可以满足我们的配置化需求,但是我们通常还会面临一个问题,我们的本地开发是可以把密码什么的告诉开发者,那如果是线上怎么办呢?我们是不是应该把这些密码等一些关键的信息通过其他的方式打进去呢?
我们可以通过 ci/cd 的方式注入
- github actions -> Repository Settings-> Secrets and variables -> Actions
- gitlab -> Settings -> CI/CD -> Variables
构建docker镜像
- 我们在CI/CD中只打包代码,不打包配置
- 线上部署时,配置交给 K8s,通过 ConfigMap + Secret 管理
- Deployment 里注入环境变量CI/CD(GitHub Actions / GitLab CI / Jenkins)在执行到 部署阶段时,把配置注入到 K8s
1 2 3 4 5 6 7 8
| - name: Deploy to K8s run: | kubectl create secret generic app-secrets \ --from-literal=DB_HOST=${{ secrets.DB_HOST }} \ --from-literal=DB_USER=${{ secrets.DB_USER }} \ --from-literal=DB_PASS=${{ secrets.DB_PASS }} \ --dry-run=client -o yaml | kubectl apply -f - kubectl apply -f k8s/deployment.yaml
|
通过consul等类配置中心注入
- 在 NestJS 启动时,通过 Consul HTTP API 拉取配置
- 在 CI/CD 流水线里,把敏感配置写入 Consul
- 部署时应用直接去 Consul 取,不需要 CI/CD 再往 Pod 里打环境变量