[V1] Backend CD - 100-hours-a-week/KTB4-10th-Cloud GitHub Wiki
[ BE CD 구축 ]
CD구조
- `GitHub Actions → SSM → EC2 → Docker Compose`
- Blue/Green 같은 별도의 배포 미사용
고려사항
- 운영 및 설정 복잡성을 고려하여 배포 명령 전달은 SSM을 사용
- 기존 문서에서의 변경점
- iam role의 credential 저장 → OIDC
사전 작업
- EC2 SSM 권한 (IAM Role설정)
- OIDC구성
- GHCR pull 토큰 → github repo secret
- CI → CD로 이미지 태그 전달 가능하게 처리 →(기본의 ci에 deploy추가)
[ 1. OIDC(OpenId Connect) ]
-
SSM권한은 EC2의 SSM Agent가 시스템 매니저와 통신할 권리를 얻기 위한 추가
-
github action에서 SSM 명령을 보낼 권한이 있는가?
⇒ 이때 사용하는것이 OIDC
- 기존에는 보안의 위험을 감수하더라도 credential을 github secret에 저장을 고려 → 추가적인 비용 없고 보안상 이점이 있는 OIDC를 도입하기로 변경
https://docs.github.com/ko/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws
1.1. AWS에 id 공급자 추가
- IAM에 OIDC 공급자 등록
1.2. 역할 생성
- ssm:SendCommand
- 백엔드 ec2에서 이 명령 실행
- ssm:GetCommandInvocation
- 방금 실행한 명령이 성공했는지
- ssm:ListCommandInvocation
- 명령 실행 상태/결과
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "SendCommandToBackend",
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": [
"arn:aws:ec2:ap-northeast-2:742875367096:instance/i-0ecb626c01830b39b",
"arn:aws:ssm:ap-northeast-2::document/AWS-RunShellScript"
]
},
{
"Sid": "ReadCommandResult",
"Effect": "Allow",
"Action": [
"ssm:GetCommandInvocation",
"ssm:ListCommandInvocations"
],
"Resource": "*"
}
]
}
1.3. 1.2에서 나온 Deploy-role을 깃헙에 등록
- arn:aws:iam::742875367096:role/kgb-github-deploy-role
- 비밀정보 아님 → variable에 등록
- 등록의 이유?
- actions에서 aws에서 어떤 iam role 사용할지 넣어주는 것
[ 2. CI-CD.yml ]
- healthcheck api의 주소는 아직 미확정
- actuator 사용여부도 미확정(일단 사용으로 가정) => actuator/health로 확정 (09/23)
#CI-CD 변경
#최종 Push때에만 해당 파이프라인 작동, lint 검사또한 진행
name: KGB Backend CI/CD
on:
# main Merge 이후 CI/CD 실행
push:
branches:
- main
# GitHub Actions 권한
permissions:
contents: read
packages: write
# GitHub OIDC -> AWS IAM Role Assume에 필요
id-token: write
jobs:
# 1. main Merge 이후 CI 검증
# ==================================================
ci:
name: CI
runs-on: ubuntu-latest
steps:
# Repository Checkout
- name: Checkout
uses: actions/checkout@v4
# JDK 설정
- name: Set up JDK
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '21'
# Gradle Wrapper 실행 권한
- name: Grant Gradle Permission
run: chmod +x gradlew
# Build / Compile 검증
# 실패 시 Image Build / 배포 차단
- name: Gradle Build
run: ./gradlew build
# Checkstyle
# 위반 내용 표시
# build.gradle의 ignoreFailures = true 적용 시 CI 차단 X
- name: Checkstyle
run: ./gradlew checkstyleMain
# SpotBugs
# Rank 5~20 제외
# Rank 1~4 발견 시 실패
- name: SpotBugs
run: ./gradlew spotbugsMain
# 2. 실제 배포용 Docker Image 생성 및 GHCR Push
# ==================================================
build-image:
name: Build and Push Image
# CI 성공 이후 실행
needs: ci
runs-on: ubuntu-latest
# 생성한 IMAGE_TAG를 CD Job으로 전달
outputs:
image_tag: ${{ steps.image.outputs.tag }}
steps:
# Merge 완료된 main Checkout
- name: Checkout
uses: actions/checkout@v4
# GHCR Login
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Image Tag 생성
# 형식: Commit SHA 앞 7자리-생성일
# 예: a81c29f-20260921
- name: Generate Image Tag
id: image
run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
DATE=$(date -u +%Y%m%d)
echo "tag=${SHORT_SHA}-${DATE}" >> "$GITHUB_OUTPUT"
# 실제 배포용 Docker Image Build
# Docker Build 자체가 실패하면 이후 Push / CD 차단
- name: Build Docker Image
run: |
docker build \
-t ghcr.io/${{ github.repository_owner }}/ktb4-10-be:${{ steps.image.outputs.tag }} \
.
# GHCR Push
- name: Push Docker Image
run: |
docker push \
ghcr.io/${{ github.repository_owner }}/ktb4-10-be:${{ steps.image.outputs.tag }}
# 3. Backend EC2 배포
# ==================================================
deploy:
name: CD
# Image Build / Push 성공 이후 실행
needs: build-image
runs-on: ubuntu-latest
steps:
# GitHub OIDC를 통해 AWS IAM Role Assume
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ vars.AWS_DEPLOY_ROLE_ARN }}
aws-region: ap-northeast-2
# SSM을 통해 Backend EC2에 배포 명령 전달
# ==================================================
- name: Send Deploy Command
id: deploy
env:
# build-image Job에서 생성한 Image Tag
IMAGE_TAG: ${{ needs.build-image.outputs.image_tag }}
# GHCR Repository Owner
GHCR_OWNER: ${{ github.repository_owner }}
# Private GHCR Pull 인증
GHCR_USERNAME: ${{ vars.GHCR_USERNAME }}
GHCR_TOKEN: ${{ secrets.GHCR_TOKEN }}
run: |
COMMAND_ID=$(aws ssm send-command \
--instance-ids "i-0ecb626c01830b39b" \
--document-name "AWS-RunShellScript" \
--parameters "commands=[
\"set -e\",
\"cd /opt/kgb\",
\"echo '${GHCR_TOKEN}' | docker login ghcr.io -u '${GHCR_USERNAME}' --password-stdin\",
\"GHCR_OWNER=${GHCR_OWNER} IMAGE_TAG=${IMAGE_TAG} docker compose pull backend\",
\"GHCR_OWNER=${GHCR_OWNER} IMAGE_TAG=${IMAGE_TAG} docker compose up -d backend\",
\"docker compose ps backend\",
\"for i in {1..12}; do if curl -fsS http://localhost:8080/actuator/health; then exit 0; fi; sleep 5; done; exit 1\"
]" \
--query "Command.CommandId" \
--output text)
echo "command_id=${COMMAND_ID}" >> "$GITHUB_OUTPUT"
# SSM 명령 완료 대기
# ==================================================
- name: Wait for Deploy Command
run: |
aws ssm wait command-executed \
--command-id "${{ steps.deploy.outputs.command_id }}" \
--instance-id "i-0ecb626c01830b39b"
# 배포 결과 확인
# ==================================================
- name: Check Deploy Result
run: |
STATUS=$(aws ssm get-command-invocation \
--command-id "${{ steps.deploy.outputs.command_id }}" \
--instance-id "i-0ecb626c01830b39b" \
--query "Status" \
--output text)
echo "Deploy status: ${STATUS}"
if [ "${STATUS}" != "Success" ]; then
echo "Backend deployment failed."
exit 1
fi
echo "Backend deployment succeeded."