AWS S3 를 사용한 env 관리 방법 및 자동화
Situation
- 코드 베이스가 늘어나며 스프링 애플리케이션 실행에 필요한 설정값이 늘어나고 있음
- 대부분의 상황은 테스트 코드로 검증하지만, 로컬에서 bootRun 으로 테스트해보는 방법도 써야할 때가 있음
Complication
- 설정값들을 env 로 분리하여 별도로 관리하고 싶음
.env
파일은 일반적으로 git ignore 대상이므로 버전 추적이 어렵고, 파편화되기 쉬움- 여러 머신에서 파일을 동기화할 수 있는 방법이 필요함
Question
- 개발자들 간에 괴리가 덜하면서도, 편리하게 적용 가능한 방법이 있는가?
- 가급적이면 익숙한 방법이어야 유지하기도 쉽다
.env
파일의 버전을 관리할 수 있는가?- 러닝커브가 낮은가?
- 배보다 배꼽이 커지는 상황은 피하고 싶다
- 운영환경에 바로 적용 가능한가?
Answer
AWS S3
.env
업데이트가 필요한 경우 AWS CLI 를 사용하면 편리하게 업데이트 가능- 스냅샷을 통해
.env
버전을 관리할 수 있음 - AWS S3 는 대부분의 개발자에게 이미 익숙한 서비스고 러닝커브가 높지 않음
- 서비스 운영 환경인 AWS ECS 는 S3 의 arn 을 사용하여 바로 시스템 변수 적용이 가능함
.
..
...
....
이걸로 끝?
이라면 글이 조금 심심하죠? 당연하게도 아직 몇가지 문제가 남아있어요.
그래서 어느 버킷에 있나
일반적으로 S3 를 쓰다보면 파일구조 최적화나 비즈니스에 따른 구분 등으로 인해 굉장히 많은 버킷이 생기기 마련이에요.
aws s3 cp s3://something.service.com/enviroment/.env .env
.env
파일이 없는 경우 AWS CLI 를 사용하여 위 명령어처럼 .env
파일을 내려받아야 합니다. 미리 버킷 이름을 누군가로부터 공유받지 않으면, 버킷을 모두 뒤져봐야하니 환경변수 파일을 찾기가 쉽지 않을 것 같네요. 공유하는 과정을 없애려고 한건데, 다시 뭔가를 공유 받아야하는 상황은 조금 불편하게 느껴지는 것 같아요.
버킷은 너무 많다. env 야 어딨니...?
S3 에서 버킷을 탐색하며 다운로드해야할 .env
파일을 찾는 과정을 자동화하면 편하겠죠? 이 부분은 fzf 나 gum 등으로 쉽게 스크립트를 작성하는 것도 가능합니다.
스프링부트는 시스템 환경변수가 필요하다. .env
가 아니라...
이미 눈치채신 분들도 계실지 모르겠지만, 스프링부트는 yaml 파일의 placeholder 에 시스템 환경변수를 읽어서 값을 채우게 되요. 하지만 .env
파일만으로는 시스템 환경변수 적용이 되지 않고, 따라서 스프링부트의 초기화 과정에 적용되지 않아요.
실제 동작을 잠깐 살펴볼게요.
# .env
HELLO=WORLD
# application.yml
something:
hello: ${HELLO} # OS 에서 HELLO 라는 환경변수를 찾아서 값을 얻는다.
@Slf4j
@Component
public class HelloWorld {
@Value("${something.hello}")
private String hello;
@PostConstruct
public void init() {
log.info("Hello: {}", hello);
}
}
SystemEnvironmentPropertySource.java
@Value
의 placeholder 를 해결하지 못하기 때문에 빈이 등록되지 못하고 에러가 발생하는걸 확인할 수 있어요.
.env
파일이 있다고 해서 시스템 환경변수로 등록되진 않는다
.env
파일을 적용하려면 export
명령을 실행하거나, 인텔리제이의 런 설정에 .env
파일을 등록하면 되요. 하지만 export
명령을 사용하는건, 로컬 머신에 너무 많은 변수가 글로벌로 등록되면서 덮어쓰기 등 의도치 않은 동작을 초래할 수 있으므로 인텔리제이의 GUI 를 통해 개별로 관리하는걸 추천해요.
인텔리제이는 .env
파일 설정을 GUI 를 통해 지원한다
placeholder 가 해결되고 정상 적용된걸 확인할 수 있다
Answer-최최종-이게진짜.final
휴, 길고 긴 문제 인식과 범위 확정의 과정이 끝났네요. 워크플로우를 마지막으로 한 번 정리해보고 스크립트를 소개할게요.
- 자동화 스크립트를 통해 s3 에서 적절한
.env
를 찾고 다운로드할 수 있도록 한다. .env
를 읽어서 시스템 환경변수로 설정하자.
쉘 스크립트는 gum 을 사용하여 최대한 단순하면서도 스타일링이 가능하도록 작성했습니다.
#!/bin/bash
S3_BUCKET=$(aws s3 ls | awk '{print $3}' | gum filter --reverse --placeholder "Select...") # 1.
# 배포 환경 선택
TARGET=$(gum choose --header "Select a environment" "Elastic Container Service" "EC2")
if [ "$TARGET" = "Elastic Container Service" ]; then
TARGET="ecs"
else
TARGET="ec2"
fi
S3_BUCKET_PATH=s3://$S3_BUCKET/$TARGET/
# search env file
ENV_FILE=$(aws s3 ls "$S3_BUCKET_PATH" | grep env | awk '{print $4}' | gum filter --reverse --placeholder "Select...") # 2.
# confirm
if (gum confirm "Are you sure you want to use $ENV_FILE?"); then
echo "You selected $ENV_FILE"
else
die "Aborted."
fi
ENV_FILE_NAME=$(gum input --prompt.foreground "#04B575" --prompt "Enter the name of the env file: " --value ".env" --placeholder ".env")
gum spin -s meter --title "Copying env file..." -- aws s3 cp "$S3_BUCKET_PATH$ENV_FILE" "$ENV_FILE_NAME" # 3.
echo "Done."
gum filter
를 사용하여 원하는 s3 bucket 을 선택합니다.env
라는 단어가 포함된 항목만 검색하고ENV_FILE
이라는 이름의 변수로 할당합니다..env
파일의 objectkey 를 확정하고 다운로드를 진행합니다.
이 스크립트의 실행 과정을 간단하게 데모영상으로 만들어봤어요.
Demo
이후에는 현재 디렉토리로 복사된 .env
파일을 먼저 소개했던 방식으로 인텔리제이에 적용해주면 됩니다.
direnv 와 인텔리제이의 direnv plugin 을 활용하면 더 편하게 적용이 가능합니다.
Conclusion
- 스크립트가 단순하기 때문에 유지보수 편리
- 팀원 반응 매우 좋음
- 개발자도 예쁜걸 좋아한다
- 민감한 Credentials 이라면 AWS Secret Manager 를 이용하자