본문 바로가기

Monew

CD(지속적 배포) 작성

.github/workflows/cd.yml

 

name: CD
run-name: ${{ github.event.workflow_run.name }}

 

맨 위의 name은 workflow의 이름임, Actions탭에서 보이는 라벨 이름이 CD로 나옴

 

run-name은 Github Actions 실행 인스턴스의 이름을 정해줌

${{ github.event.workflow_run.name }} 은 GitHub Context의 변수를 사용한 것이다.

이 CD Workflow의 트리거인 이전 Workflow를 가져오는데, 그걸 우리가 CI로 지정한다.

따라서 이 CD라는 이름의 workflow가 실행되면 이름이 CI에서 정한 CI Workflow같은걸 그대로 가져온다.

 

일반적으로 CI와 CD는 나뉘어져있다.

CI는 코드를 빌드하고, 테스트 및 커버리지 확인 등을 하며 ECR 등에 Docker 이미지를 푸시하는 것까지가 CI이다.

CD는 CI가 끝나면 그걸 트리거로 실행되며, ECS 서비스를 업데이트하고 EC2 서버에 배포하는 것까지가 CD이다.

 

그래서 on.workflow_run을 써서 CI Workflow가 성공하면 CD Workflow를 실행시키는 구조가 일반적이라고 한다.

 

 

 

 

on:
  workflow_run:
    workflows: [ "CI" ]
    types:
      - completed
    branches: [ dev ]

 

on : 은 GitHub Actions의 Workflow 실행조건을 지정하는 부분이라고 한다.

workflow_run은 다른 워크플로우가 실행되고 나서 트리거되는 이벤트라고 함

 

workflows: [ "CI" ] 에서 CI는 .github/workflows/ci.yml 안에 있는 name을 지정한다.

name: CI라고 적혀있으면 그 CI 부분을 바라보게됨

즉 "CI"라는 이름의 workflow가 실행을 끝내면, 이 워크플로우가 시작된다.

 

types: - completed 는 어떤 상태일때 트리거할 것인가? 를 지정한다

completed는 CI workflow가 성공하든, 실패하든 취소되든 어떤 결과로든 '끝나면' 이 워크플로우를 실행시킨다.

일반적으로는 conclusion == 'success'라는 조건을 step 레벨에 추가해서 CI가 성공할 경우에만 CD를 실행되도록 한다.

 

branches: [ dev ] 는 어떤 브랜치에서 workflow가 실행될 때 트리거할 지 제한한다고 한다.

우리가 컨벤션에서 dev 브랜치에서 CI가 '끝났을 때'에 CD를 실행시키기로 했으니 dev를 적었다. 일단 돌아가는지 봐야하고, 개발중이니까..

따라서 main은 영향을 주지 않으며 dev에서 개발할 수 있게 된다.

 

 

 

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      ECS_CLUSTER: <ECS 클러스터>
      ECS_SERVICE: <ECS 서비스>
      ECS_TASK_FAMILY: <ECS 태스크>
      CONTAINER_NAME: <컨테이너 이름>
      IMAGE: <ECR에 올라가는 이미지명>
      AWS_REGION: <지역명>

 

cd.yml도 마찬가지로 on: 과 jobs: 가 하나씩 필요하다

여러개의 job을 지정했으며, deploy라는 이름으로 하나만 정의했다. 실제로는 여러개의 job을 정의할 수 있다고 한다.

deploy는 job의 이름을 의미한다.

 

runs-on: ubuntu-latest는 job이 실행된 러너 환경을 지정한다.

ubuntu-latest는 GitHub에서 제공해주는 최신 우분트 리눅스 VM을 의미한다.

여기에서 Docker, AWS, ecs-cli 같은 명령어를 실행한다고 함

 

env:는 이 cd.yml job 전체에서 사용할 환경변수를 정의한다.

이후 스탭들에서  ${{ env.ECS_CLUSTER }} 와 같은 형태나 $ECS_CLUSTER 형태로 참조할 수 있다.

 

다른건 익숙했지만 CONTATINER_NAME에 Task Definition 안에 정의된 '컨테이너 이름'을 실수했었다.

컨테이너 이름이 아니라 태스크 정의 이름을 적어버려서 제대로 태스크 정의가 적용되지 않았었음

 

 

     steps:
      - uses: actions/checkout@v4

      - name: AWS ECS Configure
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

 

job 안에 있는 step들에 대해서

 

uses: actions/checkout@v4는 GitHub Actions의 checkout 액션을 사용하겠다는 것임

내 로컬저장소의 코드를 runner로 가져온다.

이걸 사용해줘야 Dockerfile, taskdef.json 같은 배포에 필요한 파일들을 runner에서 사용할 수 있다.

일반적으로 모든 워크플로우의 첫번째 스탭에서 사용된다

 

uses: aws-actions/configure-aws-credentials@v4 는 마찬가지로 GitHub 공식 AWS Action임

이걸 사용하면 GitHub Actions runner 안에서 AWS CLI, SDK에 사용할 자격증명이 자동으로 세팅된다

이걸 해줘야 AWS와 관련된 작업을 러너가 할 수 있음

 

 

      - name: Stop running ECS tasks
        run: |
          CLUSTER="${{ env.ECS_CLUSTER }}"
          SERVICE="${{ env.ECS_SERVICE }}"
          TASKS=$(aws ecs list-tasks --cluster $CLUSTER --service-name $SERVICE --desired-status RUNNING --query 'taskArns' --output text)
          if [ -n "$TASKS" ]; then
            echo "Stopping running tasks: $TASKS"
            for TASK in $TASKS; do
              aws ecs stop-task --cluster $CLUSTER --task $TASK
            done
          else
            echo "No running tasks found."
          fi

 

여기 꽤나 중요한데, 기존에 ECS에서 실행중인 태스크를 중지하는 단계이다.

그래야 새 태스크 정의를 배포할 때 기존 태스크와 혼선이 없어져서 ECS가 깔끔해진다.

 

우선 CLUSTER= , SERVICE= 명을 작성해서 클러스터명, 서비스명을 기입해준다

 

TASKS=$(aws ecs list-tasks 

--cluster $CLUSTER 

--service-name $SERVICE 

--desired-status RUNNING 

--query 'taskArns' 

--output text)

aws ecs list-tasks 명령어를 통해서 현재 실행중인 TASK의 ARN목록을 가져온다.

-- cluster는 어떤 클러스터에서 찾을지, --service-name은 어떤 서비스의 Task에 대해서만 볼지,

--desired-status RUNNING은 '실행'중인 Task만 조회하며,

--query 'taskArns'는 쿼리로 JSON에서 task ARN만 뽑는다.

 --output text는 문자열로 반환한다는 의미.

이걸 통해 조회되는게.. arn:aws:ecs: 어쩌고 저쩌고.. 하는게 조회된다

 

아래는 조건문, 반복문 형태로 읽으면 편하다

if [ -n "$TASKS" ]; then 만약 [-n "$TASKS"]  <<  변수에 값이 있다면, 즉 실행중인 태스크가 있다면? 
echo "Stopping running tasks: $TASKS"   <<  이걸 출력하면서?
for TASK in $TASKS; do  <<  for문을 TASK:$TASK로 돌리는데?
aws ecs stop-task --cluster $CLUSTER --task $TASK  <<  해당(실행중)되는 태스크를 모두 종료시킨다
done  <<  for문 끝
else  <<  그 외에는?
echo "No running tasks found."  <<  이걸 출력시킴
fi  <<  if문 끝

 

aws ecs stop-task --cluster $CLUSTER --task $TASK  이걸 통해서 자동으로 새 태스크를 띄우고 기존 태스크를 내리는게 중요하다.

 

 

      - name: Task Definition file Rendering
        id: render
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: ecs/taskdef-base.json
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ env.IMAGE }}:latest

 

aws-actions/amazon-ecs-render-task-definition@v1는 GitHub에서 제공하는 ECS Helper Action이다.

JSON 형태의 ECS Task Definition 파일을 받은 후 특정 컨테이너의 이미지를 원하는 값으로 치환해서 새로운 태스크 정의 JSON을 만들어줌

여기선 우리 컨벤션에 맞게 ecs/taskdef-base.json 파일을 받게 해서 컨테이너 이름과 이미지에 맞게 태스크 정의를 만들었다.

 

 

      - name: ECS Deploy
        uses: aws-actions/amazon-ecs-deploy-task-definition@v2
        with:
          task-definition: ecs/taskdef-base.json
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

 

마지막으로 ECS에 새 태스크 정의를 배포한다.

 

aws-actions/amazon-ecs-deploy-task-definition@v2 는 Github AWS 배포 액션임

새로운 태스크 정의를 ECS에 등록하고, 지정된 클러스터나 서비스에 적용해서 실제 배포를 진행시킨다.

배포 과정을 자동화해주기 때문에 직접 aws ecs register-task-definition + aws ecs update-service를 사용하지 않아도 된다.

 

wait-for-service-stability: true 이건 새 태스크 정의를 적용시키고 ECS 서비스가 안정 상태(Healty)가 될 때까지 기다린다.

이 과정에서 새 컨테이너들이 정상 가동되어 헬스체크를 통과할 때까지 워크플로우는 블로킹된다.

false라면 배포 명령만 던지고 바로 끝난다고 함..

 

아래는 cd.yml 전문

 

name: CD
run-name: ${{ github.event.workflow_run.name }}

on:
  workflow_run:
    workflows: [ "CI" ]
    types:
      - completed
    branches: [ dev ]

jobs:
  deploy:
    runs-on: ubuntu-latest
    env:
      ECS_CLUSTER: <ECS 클러스터>
      ECS_SERVICE: <ECS 서비스>
      ECS_TASK_FAMILY: <ECS 태스크>
      CONTAINER_NAME: <컨테이너 이름>
      IMAGE: <ECR에 올라가는 이미지명>
      AWS_REGION: <지역명>
     steps:
      - uses: actions/checkout@v4

      - name: AWS ECS Configure
        uses: aws-actions/configure-aws-credentials@v4
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Stop running ECS tasks
        run: |
          CLUSTER="${{ env.ECS_CLUSTER }}"
          SERVICE="${{ env.ECS_SERVICE }}"
          TASKS=$(aws ecs list-tasks --cluster $CLUSTER --service-name $SERVICE --desired-status RUNNING --query 'taskArns' --output text)
          if [ -n "$TASKS" ]; then
            echo "Stopping running tasks: $TASKS"
            for TASK in $TASKS; do
              aws ecs stop-task --cluster $CLUSTER --task $TASK
            done
          else
            echo "No running tasks found."
          fi

      - name: Task Definition file Rendering
        id: render
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: ecs/taskdef-base.json
          container-name: ${{ env.CONTAINER_NAME }}
          image: ${{ env.IMAGE }}:latest

      - name: ECS Deploy
        uses: aws-actions/amazon-ecs-deploy-task-definition@v2
        with:
          task-definition: ecs/taskdef-base.json
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true

'Monew' 카테고리의 다른 글

개인 개발 리포트  (1) 2025.09.22
S3에 백업한 뉴스 기사를 '복구'하는 기능  (0) 2025.09.19
S3 버킷에 데이터를 '백업'하는 로직  (0) 2025.09.16
CI(지속적 통합) 작성  (0) 2025.09.12
build.gradle  (0) 2025.09.04