Dev 비밀값과 설정 관리 - 100-hours-a-week/KTB4-2nd-Cloud GitHub Wiki
운영 설정과 비밀값은 Parameter Store 한 곳에 두고, 배포할 때마다 각 EC2가 자기 값을 읽어 환경 파일을 새로 만듭니다. Git, Terraform State, GitHub Actions에는 실제 값이 남지 않습니다.
중간 배포 때는 App과 Worker의 /etc/yeodam/*.env 파일을 SSM으로 들어가 직접 고쳤습니다. 이미지 태그를 바꿀 때도, API 키를 추가할 때도 서버에서 sed나 nano로 수정했습니다.
이렇게 운영해보니 불편한 점이 분명했습니다.
- 어떤 값이 들어가 있는지 서버에 들어가야만 확인 가능
- 값을 바꾼 기록이 어디에도 남지 않음
- 배포 워크플로가 이 파일을 쓰려면 비밀값을 CI로 넘겨야 함
그래서 값의 원본을 Parameter Store로 옮기고, 서버의 파일은 배포할 때마다 만들어지는 결과물로 바꿨습니다.
| 경로 | 내용 | 개수 | 읽을 수 있는 주체 |
|---|---|---|---|
/yeodam/v1/app/* |
DB, OAuth, JWT, S3, AI 호출, 외부 API 설정 | 18 | App EC2 |
/yeodam/v1/worker/* |
API 키, S3 버킷, 실행 모드 등 AI 설정 | 4 | Worker EC2 |
/yeodam/v1/deploy/* |
GHCR pull 계정과 PAT | 2 | App, Worker EC2 |
/yeodam/v1/observability/discord-webhook-url |
알림 Webhook | 1 | 알림 Lambda |
DB 비밀번호, OAuth, JWT, API 키, GHCR PAT는 SecureString으로, URL이나 버킷 이름, Timeout 같은 일반 설정은 String으로 넣었습니다.
경로를 서버 단위로 나눠서 App은 Worker 설정을, Worker는 App 설정을 읽을 수 없습니다. GitHub 배포 Role은 어느 경로도 읽지 못합니다.
값 자체는 Terraform 리소스로 만들지 않았습니다. Terraform은 경로에 대한 읽기 권한만 관리하고, 값은 콘솔이나 CLI로 넣습니다. 그래서 State 파일에도 비밀값이 들어가지 않습니다.
키 이름과 용도는 Cloud 저장소의 deploy/parameter-store.md에서 관리합니다.
flowchart LR
PS[("Parameter Store<br/>/yeodam/v1/app")] --> R["render-runtime-env.sh<br/>Instance Role로 조회"]
R --> F["/run/yeodam/app.env<br/>root:root 600"]
M["deploy/image-versions.env<br/>이미지 태그, digest"] --> C
F --> C["docker compose<br/>--env-file"]
C --> CT["컨테이너<br/>environment:에 적힌 값만"]
render-runtime-env.sh가 자기 경로의 값을 모두 읽어 /run/yeodam/*.env를 만듭니다. 권한은 root:root 600이고, 배포할 때마다 새로 씁니다.
이미지 태그와 digest는 여기 섞지 않고 Cloud 릴리스의 deploy/image-versions.env에서 따로 가져옵니다. 설정 값은 Parameter Store에서, 어떤 이미지를 띄울지는 Git에서 관리하는 셈입니다.
GHCR PAT는 환경 파일에 쓰지 않습니다. 배포 중에만 임시 Docker 설정에 docker login --password-stdin으로 넣고, pull이 끝나면 로그아웃하고 디렉터리를 지웁니다.
- 기존 서버 파일의 값을 Parameter Store로 이전 (App 18개, Worker 4개, Deploy 2개)
- 이전하는 동안만 EC2 Role에 임시 쓰기 권한 부여
- 이전이 끝나자마자 쓰기 권한 제거
- 두 EC2에서 환경 파일을 새로 만들고 Compose 설정 검사 통과 확인
처음 정책에는 하위 경로인 /yeodam/v1/app/*만 허용했습니다. 그런데 EC2에서 경로 단위 조회를 하자 AccessDeniedException이 났습니다.
GetParametersByPath는 하위 경로뿐 아니라 조회 기준 경로 자체에도 권한이 필요했습니다. 정책에 기준 경로 ARN과 하위 경로 ARN을 같이 넣어서 해결했습니다.
resources = [
"arn:aws:ssm:ap-northeast-2:<account>:parameter/yeodam/v1/app",
"arn:aws:ssm:ap-northeast-2:<account>:parameter/yeodam/v1/app/*",
]값을 바꾸면 Parameter Store를 수정하고 다시 배포합니다. 실행 중인 컨테이너에는 자동으로 반영되지 않습니다.
새 환경변수를 추가할 때는 네 곳을 같이 확인합니다.
- Parameter Store에 값 등록
-
render-runtime-env.sh가 해당 값을 파일에 쓰는지 - Compose의
environment:에 이름이 있는지 - 애플리케이션 설정에서 그 이름을 쓰는지
Compose 선언을 빠뜨려서 장소 검색이 실패했던 일 이후로 이 순서대로 확인합니다. 환경 파일에 있는 값이 컨테이너에 없음