1. 배경
이전 글을 통해 Github action과 ArgoCD를 통해 Spring Cloud 기반의 MSA 애플리케이션의 CI/CD를 구축하였습니다. 하지만 해당 구조는 실제 환경에서 사용하게 부족한 부분이 많습니다.
- 개발계/운영계가 나누어져 있지 않다.
- Image의 버전 관리가 되어 있지 않다.
- 배포 방법에 대한 고려가 되어 있지 않다(Rolling,Blue/Green 등)
- 롤백 기능이 존재하지 않는다.
이러한 문제점을 해결하여 실제 프로젝트에 적용할 수 있도록 구조를 변경하며, 테스트한 내용을 기록합니다.
2. 환경 분리
우선 개발계/운영계는 각각 다른 쿠버네티스 클러스터입니다. 두 개의 클러스터를 한 개의 파이프 라인을 통해 관리하다 보니 다양한 변수를 고려해야 돼서 초기 구축에 공수가 많이 들었습니다. 물론 클러스터 별로 개별 파이프라인을 통해서 구축하면 더욱 쉽게 구성할 수 있지만, 향후 운영 과정에서의 복잡성을 늘어납니다. 전체 흐름은 다음과 같습니다.
- Github 코드는 dev/main 브랜치로 환경을 분리합니다.
- dev 브랜치에서 Push, main 브랜치에서 Merge가 발생하면 Github Action에서 CI 플로우가 실행됩니다.
- Gradle Build를 통해 Jar 파일을 생성하고, 해당 파일을 바탕으로 이미지를 Build 합니다.(dev 브랜치는 Commit Hash로, main 브랜치는 새로운 태그 버전으로 이미지 버전을 정합니다)
- 빌드된 이미지를 ECR에 푸쉬합니다.
- Helm 차트가 저장된 Repo에서 values 파일을 변경 후(values-dev.yaml, values-prd.yaml로 환경 구분) 푸시합니다.
- ArgoCD에서 변경된 Values 파일을 바탕으로 어플레이케이션을 업그레이드합니다.
2-1. Helm Chart 레포지토리를 꼭 분리해야 할까?
이전 환경에서도 헬름 차트를 따로 분리하지 않고, 같은 소스 레포지토리에서 폴더를 통해 차트를 관리하였습니다. 환경이 분리되어도, branch 별로 values 파일을 구분해서 배포할 수도 있습니다. 그렇지만 저희는 프론트엔드 레포지토리는 따로 구분되어있습니다.
그렇기 때문에 프론트엔드의 새로운 버전이 릴리즈 되면 백엔드의 레포지토리의 헬름차트 디렉토리로 들어와values.yaml파일을 변경해야됩니다. 이런 문제를 해결하기 위해, 프론트엔드의 Helm을 분리하면 ArgoCD에서 별개의 Application으로 관리되기 때문에 복잡성이 늘어납니다.
3. CI Pipeline 구성
저희 소스코드의 구성은 하나의 레포지토리에 여러 개의 MSA 코드가 저장되기 때문에 각 스텝마다, 변경된 MSA를 찾아서 반복문을 통해 스텝을 실행합니다. 또한 github action 파일은 dev/main 브랜치에 각각 생성하여 관리합니다.
#레포지토리 구조 예시
.
├── README.md
├── action
├── api-gateway
├── approval
├── auth
3-1. 이미지 빌드/푸시
CI의 전체 플로우는 dev/main 대부분 비슷하지만 다른 부분이 있습니다. Spring Property에서 환경에 따라 다른 Profile이 적용돼야 하기 때문에, Dockerfile 또한 환경에 따라 다르게 생성합니다.
#Dockerfile 수정
- name: Update Dockerfile by Profiles
run: |
IFS=' ' read -ra MODULES <<< "${{ steps.changed-files.outputs.all_changed_files }}"
for module in "${MODULES[@]}"; do
module=$(echo $module | cut -d'/' -f1)
if [ -f "$module/Dockerfile" ]; then
#Profile 설정 예시
sed -i 's/ENTRYPOINT \["java","-jar", "'"$module"'-0.0.1-SNAPSHOT.jar"\]/ENTRYPOINT \["java","-jar", "'"$module"'-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=prd"\]/' ./$module/Dockerfile
#sed -i 's/ENTRYPOINT \["java","-jar", "'"$module"'-0.0.1-SNAPSHOT.jar"\]/ENTRYPOINT \["java","-jar", "'"$module"'-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev"\]/' ./$module/Dockerfile
cat ./$module/Dockerfile
fi
done
또한 dev는 Commit Hash로 이미지 태그를 지정하고, main 브랜치는 Tag 버전으로 태깅합니다.
#dev.yaml의 이미지 빌드 및 푸쉬
- name: Build and push Docker images
run: |
IFS=' ' read -ra MODULES <<< "${{ steps.changed-files.outputs.all_changed_files }}"
for module in "${MODULES[@]}"; do
module=$(echo $module | cut -d'/' -f1)
echo "Building Docker image in $module..."
if [ -f "$module/Dockerfile" ]; then
# 이미지 빌드 및 푸쉬
docker build -t 077728726991.dkr.ecr.us-east-2.amazonaws.com/$module:${{ github.sha }} "$module"
docker push 077728726991.dkr.ecr.us-east-2.amazonaws.com/$module:${{ github.sha }}
fi
done
#main.yml의 이미지 빌드 및 푸쉬
# 태그 버전 생성
- name: Create new Tag Version
id: tag_version
uses: mathieudutour/github-tag-action@v5.5
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
# 이미지 빌드 및 푸쉬
- name: Build and push Docker images
run: |
IFS=' ' read -ra MODULES <<< "${{ steps.changed-files.outputs.all_changed_files }}"
for module in "${MODULES[@]}"; do
module=$(echo $module | cut -d'/' -f1)
echo "Building Docker image in $module..."
if [ -f "$module/Dockerfile" ]; then
# Tag 버전으로 태깅
docker build -t 077728726991.dkr.ecr.us-east-2.amazonaws.com/$module:${{ steps.tag_version.outputs.new_tag }} "$module"
docker push 077728726991.dkr.ecr.us-east-2.amazonaws.com/$module:${{ steps.tag_version.outputs.new_tag }}
fi
done
4-2 Helm Repository 수정
이후 Helm Repository의 values-prd.yaml 파일에서 이미지 버전을 신규 버전으로 업데이트합니다.
#Helm Repo checkout
- name: Checkout Helm Repository
uses: actions/checkout@v2
with:
repository: matildalab-private/cmp-project-helm.git
token: ${{ secrets.GIT_ADMIN_TOKEN }}
ref: main
#setup yq
- uses: chrisdickinson/setup-yq@latest
# yq를 통한 yaml 파일 수정
- name: update image version
run: |
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
if [[ "$file" != *".github"* && "$file" != *"helm-chart"* ]]; then
echo $(basename $file)
module=$(basename $file) yq -i '.[env(module)].image.tag= "${{ steps.tag_version.outputs.new_tag }}"' ./helm-chart/values-prd.yaml
#module=$(basename $file) yq -i '.[env(module)].image.tag= "${{ steps.tag_version.outputs.new_tag }}"' ./helm-chart/values-dev.yaml
fi
done
Helm Repository를 Checkout을 통해 내려받습니다. 이때 적절한 권한이 있는 토큰을 사용합니다. 기본적으로 사용할 수 있는 ${{secrets.GIT_TOKEN}}
을 사용 할 경우 권한 문제가 발생합니다. 이후 yq를 사용하여 태그를 변경합니다. docker build/push
, yq
, sed와
같은 명령어는 marketplace에서 관련 라이브러리를 다운로드하여서 사용하면 보다 코드를 깔끔하게 사용할 수 있습니다. 저희는 변경된 모듈을 찾아서, 반복문을 돌려야 하기 때문에 대부분 run
커맨드를 이용사용합니다.
이후 파이프라인이 실행되면 다음과 같이 전체 스텝이 동작되는 것을 확인할 수 있습니다. 사실 좀 더 직관적으로 모니터링하고, 보다 효율적인 관리를 위해서, Job을 각 기능에 맞게 분리해서 관리를 해야 됩니다만, 저희는 변경된 폴더를 변수로 지정해 활용하기 때문에 한 개의 Job에서 모든 스텝을 수행합니다.
4. CD Pipeline 구성
저는 Argo를 prd 클러스터에 구성한 후 개발 클러스터를 추가해 한 곳에서 관리하도록 설정하였습니다. 그렇기 때문에 네트워크의 통신이 가능해야 됩니다.
4-1. Argo CD 클러스터 추가
Argo에서 클러스터를 추가하는 방법은 간단합니다. 우선 Kubeconfig파일에 추가하려는 클러스터의 config 파일을 추가해 context 조회 시 접근 가능해야 합니다.
이후 클러스터 탭에서 확인하면 다음과 같이 클러스터가 추가되는 것을 확인할 수 있습니다.
4-2 Application 추가
Application 추가의 경우 간단하게 설정할 수 있습니다. Helm Chart 레포지토리를 Source에 입력하고 (ArgoCD 설정에서 git을 추가해야 선택할 수 있습니다) 차트가 저장된 디렉터리를 입력합니다. 이후 DESTINATION에 클러스터와 네임스페이스를 입력합니다.
이후 Helm을 선택 후 적용할 values 파일을 선택하면 완료됩니다.
이후 생성 시 애플리케이션이 정상 배포 되는 것을 확인할 수 있습니다.(개발계는 동일한 애플리케이션에서 Source의 브랜치와, values.yaml 파일을 다르게 설정하여 생성하면 됩니다.)
이를 통해 CI/CD 고도화 작업을 마쳤습니다. 다음 구조를 통해 개발/운영 환경을 분리하고, 이미지 버전 관리를 통해 모두 히스토리에 따라 롤백이 가능하도록 변경하였습니다. 다음 글에서는 Kubernetes의 Rollout을 이용하여 배포하는 방법에 대해 정리해 보겠습니다.
'Kubernetes' 카테고리의 다른 글
Kubernetes Pod의 컨테이너는 어떻게 Localhost로 통신 할 수 있을까? (0) | 2024.02.04 |
---|---|
Kubernetes CSI Driver 란? (1) | 2024.01.21 |
ETCD MultiMaster Backup/Restore(CronJob 생성) (1) | 2023.11.14 |
Velero를 이용한 쿠버네티스 Backup (0) | 2023.10.12 |
EFK Stack을 이용한 쿠버네티스 로깅 스택 구축#2 (0) | 2023.10.12 |