Heeyaa

기록

Github CI/CD 도입

2026. 4. 2.

이번 팀프로젝트에선 인프라와 간단한 API 구현을 맡았습니다.

EC2 안에 도커를 설치해서 간단하게 배포해본적은 있지만.. 인프라에 대한 지식이 별로 없어 공부하며 CI/CD까지 구현했던 과정을 공유해보겠습니다.

CI/CD에 대해 개념은 알았지만, 구현해본적은 없었기에 흐름을 익혀둘 필요가 있었습니다.

이번 프로젝트는 Github Action을 통해 CI/CD 파이프라인 구축을 진행했습니다.

CI/CD 흐름

인텔리제이에서 푸쉬를하면 Github Action을 통해 도커 이미지로 푸쉬를 하고 도커 이미지를 ECR로 푸쉬, ECS에서 최신 이미지를 ECR에서 pull해서 서비스를 무중단 배포를 계획했습니다.

그냥 EC2에 도커 올려서 배포를할까 생각했지만, 해보지 않았던 ECR → ECS 배포를 경험해보는 것도 좋을 것 같아서 진행하게 되었습니다.

위 방식을 생각해서 다이어그램을 그려봤습니다.

대략 이런식의 흐름이 될 것 같습니다. DB쪽은 다른 팀원이 따로 관리하기 때문에 배포와 모니터링이 주된 역할이 될 것 같습니다.

배포와 모니터링을 맡았으니 부하 테스트를 해보고 여러 경험을 해보고 싶은데 시간이 될지 모르겠습니다..

workflows 설정을 알아보겠습니다.

workflow.yml 설정

workflow를 설정할 때 트리거를 먼저 생각해봤습니다.

저희는 develop 브랜치에 푸쉬와 머지후 배포할 때 main 브랜치를 푸쉬하는 걸로 얘기를 했습니다.

그렇기에 develop, main 브랜치에 Pull-Request를 올릴 때는 테스트들을 진행하고, 도커 빌드까지 테스트하는 방법을 선택했습니다.

위의 이유는 안정성 때문인데요, PR 단계에서 기능 변경이나 코드 추가가 기존 시스템의 동작에 영향을 끼치는지 자동으로 검사해 코드의 안정성을 검증하고 배포는 리뷰와 테스트를 마친 main 브랜치에 한정하고 싶었기 때문입니다.

정리를 하면

main, develop 브랜치에 PR을 올리거나 develop 브랜치에 push시 테스트를 진행

main 브랜치에 push시 AWS를 통한 배포 진행

이 흐름으로 만들었습니다.

코드를 보면

on:
  pull_request:
    branches: [ main, develop ]
    types: [ opened, synchronize ]
  push:
    branches: [ main, develop ]  
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up JDK 17
        uses: actions/setup-java@v4
        with:
          java-version: '17'
          distribution: 'temurin'

      - name: Cache Gradle packages
        uses: actions/cache@v3
        with:
          path: |
            ~/.gradle/caches
            ~/.gradle/wrapper
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}
          restore-keys: |
            ${{ runner.os }}-gradle-

      - name: Grant execute permission for gradlew
        run: chmod +x gradlew

      - name: Run tests
        run: ./gradlew test

      - name: Test Docker build
        run: docker build -t test-image .

  # main 브랜치 push 시에만 배포
  deploy:
    needs: test-and-validate
    runs-on: ubuntu-latest
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ap-northeast-2

      - name: Login to Amazon ECR
        uses: aws-actions/amazon-ecr-login@v2

      - name: Generate image tags
        id: meta
        run: |
          echo "IMAGE_TAG=latest" >> $GITHUB_OUTPUT
          echo "COMMIT_TAG=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
          echo "ECR_REGISTRY=${{ secrets.AWS_ACCOUNT_ID }}.dkr.ecr.ap-northeast-2.amazonaws.com" >> $GITHUB_OUTPUT

      - name: Build and push Docker image to Amazon ECR
        uses: docker/build-push-action@v5
        with:
          context: .
          platforms: linux/amd64
          push: true
          tags: |
            ${{ steps.meta.outputs.ECR_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:latest
            ${{ steps.meta.outputs.ECR_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}:${{ steps.meta.outputs.COMMIT_TAG }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Substitute environment variables in task definition
        run: |
          envsubst < ecs-task-definition.json > final-task-def.json
        env:
          AWS_S3_ACCESS_KEY: ${{ secrets.AWS_S3_ACCESS_KEY }}
          AWS_S3_SECRET_KEY: ${{ secrets.AWS_S3_SECRET_KEY }}
          DATASOURCE_URL: ${{ secrets.DATASOURCE_URL }}

      - name: Deploy to Amazon ECS
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: final-task-def.json
          service: ootd-service
          cluster: ootd-cluster
          wait-for-service-stability: true

deploy는

if: github.event_name == 'push' && github.ref == 'refs/heads/main'

이런식으로 main에 push일 때만 배포하게 트리거를 설정했습니다.

민감한 정보들은 github secret에 따로 저장을 해 사용합니다.

위의 흐름으로 CI/CD를 진행하게 됐습니다.

처음 인프라를 맡았을 때 인프라에 대해 아는게 많이 없어 걱정을 했는데 배포를 해보니 기능 구현할 때와 다른 재미가 있어 흥미가 좀 생기게 됐는데

개인적으로 팀원들이랑 서비스 배포 후에 부하를 걸고 성능 개선까지 같이 해봤으면 좋겠습니다.

다음은 그라파나와 프로메테우스를 로컬에 먼저 연결해보고 설정한 다음, 기능구현이 어느정도 끝나면 도커 컴포즈를 통해 EC2에 배포 후 서비스에 연결해볼 생각입니다.

그라파나와 프로메테우스도 저번 팀프로젝트에서 사용했지만 제가 연결하고 설정한 것이 아니기에..

A ~ Z 까지 인프라 구축을 해보는 프로젝트가 될 것 같습니다.