728x90
- 2023년 8월 인턴 업무를 진행하며 프로젝트 키 관리 방식을 바꾸면서
개발팀 내부에 공유했었던 문서를 각색하여 작성하였습니다.
- Nest js 프로젝트에서 키 파일 관리를 중앙에서 하기 위해 도입한 방식으로
본 포스팅은 NestJS + AWS 환경에서 배포를 진행하는 프로젝트에 최적화되어 있습니다.
🧍🏻Intro
- 도입 배경
- Redis를 새롭게 추가하던 중 파이프라인에 변수 설정 누락, Bitbuckets Repository Variables 변수설정 누락 시 빌드는 성공하지만 정작
.env
파일 내에 해당 시크릿 정보가 기입되지 않는 문제 발생 - 공동 작업 중
DATABASE_SYNCHRONIZE = true
로 설정하는 실수로 인해 DB의 모든 데이터 유실 - 키 파일 변경 시 일일이 모든 개발자에게 키 파일을 보내야하는 번거로움
- 등등등… 기존의 👤개발자 한명 → 👥 개발자 여러명으로 늘면서 공동작업시의 별도 환경 변수 관리가 없었음
- Redis를 새롭게 추가하던 중 파이프라인에 변수 설정 누락, Bitbuckets Repository Variables 변수설정 누락 시 빌드는 성공하지만 정작
- 사용 이점 및 특징
- 연관 AWS 리소스
- AWS Secrets Manager
- AWS CLI 접근 & 애플리케이션 키파일 접근용 IAM 액세스키
- 연관 AWS 리소스
- Secrets Key의 버전관리 가능 → Cloud Trail, Cloud Watch 연동 시에 (추후 업데이트해야할 사항)
- 모든 개발자가 동일한 환경변수 설정 공유 가능
- 런타임 시에 환경 변수 설정
- 별도로
.dev.env
파일 내부에 모든 설정 정보를 넣지 않아도됨- But, AWS Secrets Manager 설정 정보는 추가해야함
- 파이프라인 구성시에
Make .env file
스테이지 간소화 가능 pipelines: default: - step: name: Make .env file
Before
DATABASE_HOST=
...(약 30개의 key 정보들)...
REDIS_DB=
After
SECRETS_REGION=
SECRETS_ACCESS_KEY_ID=
SECRETS_ACCESS_KEY=
SECRETS_ARN=
📜 Guide
- 로컬에서 Secrets Manger 값을 쉽게 확인할 수 있는 AWS-CLI 사용 방법에 대해 안내합니다.
- AWS GUI(Web 환경)에서도 동일한 내용 확인 가능합니다.
Step 1. 로컬에서 aws-cli 접근이 가능하도록 설정
AWS CLI 설치, 업데이트 및 제거 - AWS Command Line Interface
- AWS CLI 설정이 가능하도록 현재 개발중인 OS에
aws-cli
를 설치해주세요.
Step 2-1. [aws-cli 접속을 위한 profile 설정하기]
profile 직접 명시
# .aws 디렉토리가 없으면 생성
>> cd ~/.aws
# credentials에 프로필 액세스 키 설정
>> cat > credentials
$ [team-secrets-manage]
$ aws_access_key_id={AWS ACCESS KEY ID}
$ aws_secret_access_key={AWS SECRET ACCESS KEY}
^C
# 메타 정보 설정
>> cat > config
$ [team-secrets-manage]
$ region=ap-northeast-2
$ output=json
^C
Step 2-2. [aws-cli 접속을 위한 profile 설정하기]
aws configure
default profile 설정
>> aws configure
$ AWS Access Key ID [****************HZ3P]: {AWS ACCESS KEY ID}
$ AWS Secret Access Key [****************R9qH]: {AWS SECRET ACCESS KEY}
$ Default region name [ap-northeast-2]: ap-northeast-2
$ Default output format [json]: json
Step 3. AWS-CLI 에서 Key-Value 확인하기
>> aws secretsmanager get-secret-value --secret-id {secrets-id}
- secrets-id 선택 옵션
- mars-dev
- mars-prod
- e.g.
>> aws secretsmanager get-secret-value --secret-id mars-dev > .mars-dev.env
>> cat .mars-dev.env
$ {
$ "ARN": "arn:aws:secretsmanager:ap-northeast-2:...",
$ "Name": "mars-dev",
$ "VersionId": "cd9bb8ca-3747-4486-9958-afa8e28c8d94",
$ "SecretString": ...,
$ "VersionStages": [
$ "AWSCURRENT"
$ ],
$ "CreatedDate": "2023-08-30T17:05:40.322000+09:00"
$ }
🏃🏻 Try
Try 1
Nest 애플리케이션의 ConfigModule
로 AWS Secrets Manger를 로드해오고 이를 맨 처음 애플리케이션 모듈 로드시 설정 정보를 넣어주도록 할 예정이었다.
- But, 서비스 로직에서 설정정보 사용시, Config 객체 자체를 넘겨주고 있음
/** src/videos/video.service.ts */
@Injectable()
export class VideoService implements OnModuleInit {
constructor(
...
@Inject(aiConfig.KEY)
private readonly aiConfigSet: ConfigType<typeof aiConfig>,
@Inject(s3Config.KEY)
private readonly s3ConfigSet: ConfigType<typeof s3Config>,
@Inject(esConfig.KEY)
private readonly esConfigSet: ConfigType<typeof esConfig>,
@Inject(videoConfig.KEY)
private readonly videoConfigSet: ConfigType<typeof videoConfig>,
...
) {
...
→ 일종의 DTO 개념처럼 설정 정보를 직접적으로 서비스 로직에서 기입해주기보다 기존 로직 처럼 맨처음 app.module.ts
에서 넣어주는게 더 직관적이고 객체 단위로 주고 받기에 편하다고 판단함
Try 2
그렇다면 Secrets Manager 또한 ConfigService로 등록을 해서 각각의 Config 설정에 등록을 해주자!
[MARS] Info 2023.08.30 15:59:41:5941 [NestFactory] Starting Nest application... - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] TypeOrmModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] DiscoveryModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] ConfigHostModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] SseModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] AppModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] ScheduleModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] ConfigModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] ConfigModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] JwtModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] CacheModule dependencies initialized - {}
[MARS] Info 2023.08.30 15:59:41:5941 [InstanceLoader] AuthModule dependencies initialized - {}
...
- 위와 같이 애플리케이션 구동 시 찍히는 로그를 확인해보면
app.module.ts
에 명시한 모듈들이 순서에 상관없이 비동기적으로 로드되는 걸 볼 수 있다. - ∴ Secrets Manager를 로드해오는데에 시간이 걸림 (비동기)
→ 동시에 TypeOrmModule 로드 시작
→ Secrets Manager를 아직 로드해오지 못했으므로 DB 설정 정보가 주입이 안된 상태
→ 에러 발생
🎉 Solve
위와 같은 이유들로, Nest 애플리케이션 인스턴스가 생성되기 전에 process.env
에 직접 설정 정보를 주입해주는 방식을 채택했다.
⭐ 로컬에서 간단하게 설정 정보를 바꾸면서 테스트하고 싶은데, 중간에 Secrets Manager가 끼어있어서 불편하셨다고요?
아래와 같이 loadSecrets();
를 주석처리해주시면 Secrets Manager 없이도 애플리케이션 개발이 가능합니다.
/** src/main.ts */
async function bootstrap() {
**await loadSecrets();
// 해당 코드 주석 처리 후, 동일하게 .env 파일 구성 후 애플리케이션 구동하면 됩니다!
// 또는 새로 추가하는 변수의 경우 .env 에 해당하는 변수 값만 넣어주면 됩니다.**
const app = await NestFactory.create(AppModule, {
logger: piaLogger,
});
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
app.enableCors();
setUpSwagger(app);
await app.listen(3000);
}
- NestFactory.create로 NestApplication 인스턴스 생성 이전에 SecretsManager의 값을 불러오는 로직을 추가한다.
📄 src/utils/loadSecrets.ts
import { GetSecretValueCommand, GetSecretValueResponse, SecretsManagerClient } from '@aws-sdk/client-secrets-manager';
import { Logger, HttpException, HttpStatus } from '@nestjs/common';
interface SecretsStringList {
key: string;
value: string;
}
const logger = new Logger('Load Secrets Variable');
export const loadSecrets = async () => {
// secretsManager 객체를 가져올 수 있도록 객체 생성
const secretsManager = new SecretsManagerClient({
region: `${process.env.SECRETS_REGION}`,
credentials: {
accessKeyId: `${process.env.SECRETS_ACCESS_KEY_ID}`,
secretAccessKey: `${process.env.SECRETS_ACCESS_KEY}`,
},
});
const command = new GetSecretValueCommand({
SecretId: `${process.env.SECRETS_ARN}`,
});
try {
const secretResult: GetSecretValueResponse = await secretsManager.send(command);
const secretList: SecretsStringList = JSON.parse(secretResult.SecretString);
// Secrets Manager SecretString
// key-value 값을 읽어와서 'process.env'에 넣어주는 작업 수행
for (const [key, value] of Object.entries(secretList)) {
process.env[key] = value;
}
} catch (e) {
logger.error(e.message);
throw new HttpException(
'AWS Secrets Manager 환경변수를 읽어오는데 문제가 발생했습니다.',
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
};
- ‘aws sdk’ 사용 시, 구버전의 호환성 문제로 인해 ‘@aws-sdk/client-secrets-manager’ 를 이용하여 Secrets Manager 로드 환경을 구성하였습니다.
🗓️ TBD
- Cloud Trail, Cloud Watch 연동을 통한 환경 변수 설정 버전 관리
- 이전 버전의 환경 변수 혹은 변경자 확인이 필요할 경우 사용하면 좋을 것 같다!
🔗 Reference
728x90
'⚙️ DevOps > AWS' 카테고리의 다른 글
[AWS/EFS, API Gateway, Lambda] 비디오 서버 서버리스로 마이그레이션 하기 (45) | 2024.01.28 |
---|---|
[AWS/DynamoDB] Explore DynamoDB with the CLI (35) | 2024.01.21 |
[AWS/DynamoDB] Overview (2) | 2024.01.17 |
[AWS/Elastic IP] EC2에 탄력적 IP 주소 할당하기 (0) | 2022.08.09 |
[AWS/RDS] RDS TimeZone 변경하기 (0) | 2022.05.20 |