[Nest.js] AWS Parameter Store를 통해 환경변수 관리하기
여러명이서 프로젝트를 진행하다보면 환경변수 관리가 불편해진다.
이 때 도입할수 있는 방식이 AWS에서 제공하는 Parameter Store 이다.
Parameter Store를 사용하면 환경변수를 관리를 중앙화 할 수 있다.
● AWS Parameter Store
우선 AWS 공식문서에 따르면 아래와 같은 서비스로 소개하고있다.
AWS Systems Manager의 기능인 Parameter Store는 구성 데이터 관리 및 암호 관리를 위한 안전한 계층적 스토리지를 제공합니다.
이러한 서비스를 통해서 중요한 데이터를 중앙화하여 관리가 가능하다.
● 기존 환경변수 관리방식
노드 프로젝트의 경우 환경변수를 .env 파일을 통해서 관리하게 된다.
하지만 환경변수가 추가/삭제/변경되면 해당 내용을 다른 개발자에게 공유하고 최신화를 해야한다.
자동화를 좋아하는 사람으로써 이러한 작업은 매우 귀찮다..
● 변경된 환경변수 관리방식
사진에서 보는 방식 그대로 Parameter Store를 통해서 환경변수를 관리할 수 있다.
환경변수 등록과정과 해당 데이터를 가져오는 모듈을 따로 만들어야되지만 기존 과정보다는 편할것으로 예상된다.
● Parameter Store에 환경변수 저장하기
우선 Parameter Store로 이동한다.
아래 경로의 Region은 서울로 되어있지만 다른곳에서 관리하고 싶을경우 다른 Region으로 이동하면 된다.
파라미터 생성 버튼을 클릭해서 새로운 값을 등록할 수 있다.
우선 파라미터 스토어에는 일반 문자열로 저장하거나 JSON 형식으로 저장하거나 원하는 형식으로 저장이 가능하다.
나의 경우 /my_app/development/database로 지정하고 JSON 형식의 데이터를 저장해줬다.
/my_app/development/database/host 형식으로 단순 문자열도 저장이 가능한데, 이는 취향대로 하면 될것같다.
● AWS-SDK를 통한 Parameter Store 접근
우선 nodejs에서 AWS에서 제공하는 서비스를 코드 상에서 접근하기 위해서는 AWS-SDK를 사용해야 한다.
npm install --save @aws-sdk/client-ssm
나의 경우 Parameter Store에 접근하기 위한 모듈(MyConfigModule)을 따로 작성했다.
먼저 my-config.module.ts 내용이다. 이는 별건없다.
import { Module } from '@nestjs/common';
import MyConfigService from './my-config.service';
@Module({
providers: [MyConfigService],
exports: [MyConfigService],
})
export default class MyConfigModule {}
다음은 my-config.service.ts 내용이다.
import { Injectable, InternalServerErrorException } from '@nestjs/common';
import {
GetParameterCommand,
SSMClient,
SSMClientConfig,
} from '@aws-sdk/client-ssm';
import { DatabaseConfig } from './interfaces/my-config.interface';
@Injectable()
export default class MyConfigService {
private readonly ssmClient: SSMClient;
private readonly ssmClientConfig: SSMClientConfig;
constructor() {
this.ssmClientConfig = {
region: process.env.IAM_REGION,
credentials: {
accessKeyId: process.env.IAM_ACCESS_KEY_ID,
secretAccessKey: process.env.IAM_SECRET_ACCESS_KEY,
},
};
this.ssmClient = new SSMClient(this.ssmClientConfig);
}
// Example of How to get JSON Format Data to Object
async getDatabaseConfig(): Promise<DatabaseConfig> {
const name = 'database';
try {
const value = await this.getValue(name);
const databaseConfig: DatabaseConfig = JSON.parse(value);
return databaseConfig;
} catch (err: any) {
throw new InternalServerErrorException();
}
}
// Example of How to get String Format Data to String
async getAdminId(): Promise<string> {
const name = 'admin/id';
try {
const value = await this.getValue(name);
return value;
} catch (err: any) {
throw new InternalServerErrorException();
}
}
private async getValue(name: string): Promise<string> {
const command = new GetParameterCommand({
Name: `/my_app/${process.env.NODE_ENV}/${name}`,
WithDecryption: true,
});
const response = await this.ssmClient.send(command);
const value = response.Parameter.Value;
return value;
}
}
- MyConfigService → constuctor() {}
- SSMClient에 제공할 config 내용을 정의해준다.
- region, credentials(accessKeyId, secretAccesskey)를 작성해준다. 이는 미리 .env에 넣어두자
- JSON 데이터를 객체로 변환해서 반환하기
- 하단에 정의한 private getValue 메소드에서 parameter store의 값을 가져온다.
- 그 후 JSON.parse()를 통해서 객체 형식의 데이터를 반환해준다
- 일반 텍스트 데이터를 반환하기
- 위와 동일하게 private getValue 메소드에서 parameter store의 값을 가져온다.
- 그 후 해당 값을 반환한다.
위 방식으로 환경변수를 처리하면 아무래도 기존 configService 방식보다는 코드의 양과 따로 작업해야할게 늘어나긴한다.
하지만 .env로 관리하면서 매번 최신화 해주는것보단 나은것같다.
● 고민거리?
MyConfigService에서는 특정 데이터에 대해서 가져오도록 메소드명을 설정했다.
하지만 이렇게 되면 데이터를 가져올때마다 메소드가 하나씩 추가된다.
이를 해결하는 방법은 get@@@ 메소드에 인자로 name값을 받는것이다.
하지만 parameter store의 key 값이 바뀐다면 모든 파일에서 다 바꿔줘야된다. 상당히 귀찮다.
어떤게 더 좋은 방법인지는 조금더 고민이 필요할듯하다.