오늘 요약
약 3주 만에 앱 업데이트를 진행했는데, 큰 수정 사항이 있었음에도 불구하고, 고려하지 못한 채로 배포를 해서 일부 서비스가 1시간 동안 제대로 동작하지 않았고, 앱 로그인 조차도 10분 정도 되지 않은 상황이 발생했다. 이번 문제는 크게 3가지로 나눌 수 있다. 첫 번째로는 데이터 마이그레이션 문제, 두 번째로는 설정 파일 업데이트 문제, 마지막으로는 Docker Repository의 권한 변경으로 인한 이미지 PULL 문제였다.
1. 데이터 마이그레이션 문제
새로운 기능 추가로 10개 남짓한 테이블이 추가되고, 기존 테이블의 속성 정의 자체도 변경되어야 하는 상황이었다. 상용 DB와 개발 DB의 테이블 값과 속성을 일일이 비교하여 SQL 스크립트를 작성해서 배포 직전에 테이블 수정을 했다.
앞으로도 빈번히 발생할 수 있는 문제라고 생각해서 데이터베이스 형상 관리 라이브러리인 Flyway를 추후 도입하여 문제를 해결하기로 했다.
-
Flyway란?
Flyway는 스프링(Spring) 프레임워크에서 데이터베이스 마이그레이션을 관리하기 위한 라이브러리이다. Flyway를 사용하면 데이터베이스 구조가 변경될 때마다 수동으로 SQL 스크립트를 작성하지 않아도, 애플리케이션이 자동으로 마이그레이션을 수행한다.
또한 Flyway는 데이터베이스 스키마의 버전 관리를 가능하게 하며, 데이터베이스 변경 사항이 버전 관리 시스템(Git 등)을 통해 관리될 수 있다. 이를 통해 다른 개발자나 운영자도 쉽게 데이터베이스 변경 이력을 파악할 수 있다.
Flyway는 스프링 프레임워크와 호환성이 좋으며, 스프링에서 지원하는 다양한 데이터베이스와도 잘 동작한다. 이러한 이점들로 인해 Flyway는 스프링 프로젝트에서 데이터베이스 마이그레이션을 수행하기 위한 많은 개발자들에게 인기가 있는 라이브러리이다.
2. 설정 파일 업데이트 문제
현재 프로젝트는 Git으로 관리되고 있으며, private repository로 사용하고 있지만, application.properties
및 application.yaml
파일에는 DB 계정과 같은 중요한 정보가 담겨 있기 때문에, Github Actions CICD 과정에서 Secret Key로 추가되어 사용되고 있다.
- name: app-admin-api application.yaml 생성
run: |
cd ./app-admin-api/src/main
if [ ! -d resources ]; then
mkdir resources
fi
cd resources
touch ./application.yaml
echo "${{ secrets.APP_ADMIN_API_DEV_YAML }}" > ./application.yaml
touch ./application-dev.yaml
echo "${{ secrets.APP_ADMIN_API_DEV_DEV_YAML }}" > ./application-dev.yaml
shell: bash
따라서 위와 같이 설정 파일이 추가 될 때 마다 .yaml 스크립트에 추가 해야 하는데, 이전에 추가한 스크립트 변경 사항을 인지하지 못하고 배포를 했다. 자꾸 Docker Container가 종료되어서 로그를 확인해보니 설정 파일 추가가 안 됐던 것이었다.
💡해결방안?
private repository로 관리되고 있으니, 설정 파일 자체를 Secret으로 관리하지 않는 것도 하나의 방안이 될 수 있을 것 같다. 개발 팀원들이 로컬에서 테스트 할 때에도 Git pull 이후에 설정 파일이 존재하지 않아서 RUN 자체가 안되는 경우가 몇 번 있었기 때문에 Secret Key로 관리되는 것은 로그인 정보나 RSA키 정도만 있어도 되지 않을까. 다른 기업에서는 어떤 식으로 설정 파일을 관리하는 지 궁금했다.
-
GPT에게 질문
💬Qusetion
현재 내 프로젝트는 github Actions를 사용하여 CICD를 진행하고 있습니다. 구성 파일에는 DB 계정, Secret Key 등의 주요 정보가 저장되어 있기 때문에 Git에서 제공하는 Secret Key로 구성 파일을 관리하고 있습니다.그러나 오늘 배포 과정에서 개발 서버에서 등록한 구성 파일이 상용 서버로 추가되지 않아 앱이 일시 중단되었습니다. 다른 회사에서는 구성 파일을 어떻게 관리합니까?어떻게 하면 이런 문제를 막을 수 있을까요?
👁️🗨️Answer
구성 파일, 특히 데이터베이스 자격 증명 또는 비밀 키와 같은 중요한 정보를 포함하는 구성 파일을 관리하는 것은 안전한 애플리케이션 배포의 중요한 측면입니다.다음은 이러한 구성 파일을 관리하기 위한 몇 가지 일반적인 방법입니다.
- 환경 변수:중요한 정보를 구성 파일에 저장하는 대신 응용 프로그램이 실행되는 시스템에서 이 정보를 환경 변수로 설정하는 것이 일반적인 방법입니다.이러한 파일은 런타임에 응용프로그램에 주입될 수 있으므로 코드베이스에 포함되지 않습니다.GitHub Actions의 암호 관리를 사용하여 CI/CD 중에 이러한 환경 변수를 안전하게 제공할 수 있습니다.
- 비밀 관리 도구:HashiCorp의 Vault, AWS Secrets Manager 또는 Azure Key Vault와 같은 특수한 비밀 관리 도구가 있습니다.이러한 도구는 토큰, 암호, 인증서 및 암호 키에 대한 액세스를 저장하고 엄격하게 제어하여 기밀 및 기타 중요한 데이터를 보호할 수 있습니다.
- 암호화된 파일:구성 파일을 암호화하고 암호화된 버전을 코드베이스에 포함할 수 있습니다.그런 다음 암호 해독 키는 환경 변수 또는 다른 보안 방법을 통해 런타임에 제공됩니다.
- 구성 서버:또 다른 접근 방식은 전용 구성 서버를 사용하여 응용 프로그램에 구성 파일을 제공하는 것입니다.이러한 서버는 애플리케이션 서버와 별도로 보안 및 관리할 수 있으며, 환경(개발, 스테이징, 프로덕션 등)에 따라 다양한 구성을 제공할 수 있습니다.Java 에코시스템의 Spring Cloud Config Server가 그 예입니다.
- Git-Cryptor BlackBox: Git 저장소에 비밀을 저장해야 하는 경우 Git-Crypt 또는 BlackBox와 같은 도구를 사용하는 것을 고려하십시오.이러한 도구는 Git 저장소의 파일을 투명하게 암호화하고 해독합니다.
서버에서 구성 파일이 누락된 문제를 방지하려면 다음을 수행합니다.
- 배포 전에 필요한 모든 파일이 있는지 확인하기 위해 CI/CD 파이프라인에 검사를 구현합니다.예를 들어 GitHub Actions를 사용하는 경우 워크플로에 이러한 파일의 존재를 확인하는 단계를 추가할 수 있습니다.
- 운영 환경에 배포하기 전에 애플리케이션이 구성 파일을 성공적으로 읽고 필요한 리소스(예: 데이터베이스)에 연결할 수 있는지 확인하는 자동 테스트를 추가합니다.
- 배포가 실패할 경우 애플리케이션의 이전 버전으로 신속하게 되돌릴 수 있는 롤백 전략을 구현합니다.
구성 파일을 관리하는 방법을 선택하더라도 항상 최소 권한 원칙을 준수해야 합니다. 권한이 절대적으로 필요한 사용자에게만 권한을 부여해야 합니다.또한 정기적으로 액세스 제어를 감사하고 기밀을 순환하여 중요한 정보를 더욱 보호합니다.
→ Spring Cloud Config Server라는 흥미로운 라이브러리를 알게 되었다.
Spring Cloud Config Server는 Spring Cloud에서 제공하는 서비스 중 하나로, 분산 시스템에서 사용되는 구성 정보를 중앙 집중식으로 관리할 수 있도록 도와준다. 이를 통해 서비스간의 설정 정보를 일관성 있게 관리할 수 있으며, 변경사항이 발생할 때마다 모든 서비스에서 설정 정보를 일일이 업데이트하지 않아도 된다는 이점을 가진다. Config Server는 Git, Subversion, HashiCorp Vault 등 다양한 백엔드 저장소를 지원하며, 설정 정보를 JSON, YAML 등 다양한 형식으로 저장할 수 있다.
→ Spring Cloud Config Server를 도입하는 것도 방안이 될 수 있을 것 같다. 이번 오류의 문제점은 상용 서버 테스트를 위한 서버를 제대로 활용하지 않고 빠르게 배포한 게 문제가 된 듯하다.
3. Docker Repository 권한 변경 문제
사실 2번 문제와 더불어 배포 지연에 가장 문제를 일으켰던 오류였다. 최근에 우리가 PUSH 하던 이미지가 사실 공개(public) 레파지토리에 저장되고 있다는 사실을 알았고, 허겁지겁 Docker hub 결제를 하여 private으로 레파지토리를 변환했다.
문제는, private으로 권한을 변경한 후 수정한 코드가 앱에 적용이 안 되고 있던 것이다. 원인을 모르고 당황하던 중 최근 권한 변경을 했다는 사실을 깨닫고 원인을 찾아내기 시작했다. 문제는 Github Actions 스크립트 파일에 존재했다.
- name: Docker 빌드
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
cd app-public-api
docker build -t kurrant_v1 .
docker tag kurrant_v1 ${{ secrets.DOCKER_USERNAME }}/kurrant_v1:${GITHUB_SHA::7}
docker push ${{ secrets.DOCKER_USERNAME }}/kurrant_v1:${GITHUB_SHA::7}
- name: 배포
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ubuntu
key: ${{ secrets.PROD_PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
docker pull ${{ secrets.DOCKER_USERNAME }}/*******:${GITHUB_SHA::7}
docker tag ${{ secrets.DOCKER_USERNAME }}/*******:${GITHUB_SHA::7} *******
docker stop *******
sleep 5
docker run -i -t --log-driver=awslogs --log-opt awslogs-region=ap-northeast-2 --log-opt awslogs-group=******* --log-opt awslogs-stream=app -d --rm --net redis_redis_cluster --name ******* -p 8882:8882 *******
배포 스크립트 부분에서 docker 로그인 부분이 없었던 것이다. Docker 빌드 부분이 존재했기 때문에 사실 로그인 문제라고 생각하지 못하고 좀 해맸었는데 조금만 생각해보면 private repository 이미지르 불러오지 못한 것은 당연하다.
Docker 빌드 부분은 Github에 Push한 프로젝트 파일 기반으로 이미지를 생성하는 것인데, 배포 코드는 EC2 서버에 접근하는 부분이므로 프로젝트 내부에서 일어나는 Docker 빌드 과정과는 사실 전혀 다른 작업이기 때문이다.
초기 작성할 당시에는 Job 분리에 대한 개념이 크게 없었는데 이번 기회로 Job을 나누어서 진행해야겠다고 생각했다.
- name: 배포
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ubuntu
key: ${{ secrets.PROD_PRIVATE_KEY }}
envs: GITHUB_SHA
script: |
echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u ${{ secrets.DOCKER_USERNAME }} --password-stdin
docker pull ${{ secrets.DOCKER_USERNAME }}/*******:${GITHUB_SHA::7}
docker tag ${{ secrets.DOCKER_USERNAME }}/*******:${GITHUB_SHA::7} *******
docker stop *******
sleep 5
docker run -i -t --log-driver=awslogs --log-opt awslogs-region=ap-northeast-2 --log-opt awslogs-group=******* --log-opt awslogs-stream=app -d --rm --net redis_redis_cluster --name ******* -p 8882:8882 *******
배포 과정에서 로그인 하는 코드만 넣어주니 정상적으로 작동하는 것을 확인했다. 🥲
잘 읽었습니다. ^^