Docker 컨테이너 기반 GitHub Actions CI/CD 파이프라인 구축 방법

기존 서버 프로세스를 도커 컨테이너로 전환하는 과정입니다.


기존 서버 프로세스 도커 컨테이너 전환

도커 패키지 설치

sudo yum update -y
sudo yum install docker -y

도커 서비스 시작

sudo service docker start

시스템 부팅 시 도커 자동 시작 설정

sudo systemctl enable docker

현재 사용자를 docker 그룹에 추가

sudo usermod -a -G docker 사용자명
newgrp docker

sudo 없이 도커 명령어를 실행할 수 있도록 현재 사용자를 docker 그룹에 추가하고 쉘에 즉시 반영합니다.


도커 컨테이너 기반 CI/CD 구축

GitHub Actions 워크플로 파일 생성

name: Node.js CI/CD

on:
  push:
    branches: [ master ] # 마스터 브랜치에 push 이벤트 발생 시마다 워크플로 실행

jobs:
  build-and-push: # 빌드 및 push Job (CI)
    runs-on: ubuntu-latest
    outputs: # 다른 Job에서도 사용할 수 있는 Job Output
      image_tag: $

    steps:
    - uses: actions/checkout@v4 # 리포지토리 체크아웃
    
    - name: Generate version # 현재 커밋의 Git SHA 앞 7자리를 version 값으로 저장
      id: version # Step Output을 참조하기 위한 Step ID
      run: |
        echo "version=${GITHUB_SHA::7}" >> $GITHUB_OUTPUT
    
    - name: Configure AWS credentials # AWS 인증 정보 설정
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: $
        aws-secret-access-key: $
        aws-region: $ # ap-northeast-2 (아시아 태평양. 서울 리전)
    
    - name: Login to Amazon ECR # GitHub Actions Runner가 Amazon ECR 로그인하는 Step
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2

    - name: Set up QEMU # 현재 Runner와 다른 CPU 아키텍처의 프로그램을 실행할 수 있도록 에뮬레이션하는 도구
      uses: docker/setup-qemu-action@v3

    - name: Set up Docker Buildx # Docker Buildx 빌더 설정
      uses: docker/setup-buildx-action@v3
    
    - name: Build and push Docker image # 도커 이미지 빌드 및 AWS ECR Push
      env:
        ECR_REGISTRY: $
        ECR_REPOSITORY: $
        IMAGE_TAG: $
      run: |
        # 멀티 플랫폼 빌드 및 푸시 (Private 이미지 저장소 Amazon ECR에 빌드 결과물 도커이미지 업로드)
        # 프로젝트 경로 Dockerfile 파일 기반으로 패키지 다운 및 애플리케이션 실행하는 도커 이미지 빌드
        docker buildx build \
          --platform linux/amd64,linux/arm64 \
          -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
          -t $ECR_REGISTRY/$ECR_REPOSITORY:latest \
          --push .
          # AMD64, ARM64용 이미지를 동시에 빌드
          # 커밋 SHA 등으로 만든 버전 태그 지정
          # 같은 이미지에 latest 태그도 추가
          # 현재 디렉터리를 빌드 컨텍스트로 사용하고, 빌드 완료 후 ECR로 Push

  deploy: # 배포 Job (CD)
    needs: build-and-push
    if: github.ref == 'refs/heads/master' # 마스터 브랜치인 경우에만 deploy가 실행되도록 설정
    runs-on: ubuntu-latest
    
    steps:
    - name: Configure AWS credentials # AWS 인증 정보 설정
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: $
        aws-secret-access-key: $
        aws-region: $
    
    - name: Login to Amazon ECR # GitHub Actions Runner가 Amazon ECR 로그인하는 Step
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v2
    
    - name: Create SSH directory # Runner 홈 디렉터리에 SSH 설정용 폴더 생성
      run: mkdir -p ~/.ssh
    
    - name: Add SSH key # EC2 SSH 접속용 Private Key 파일 저장
      run: |
        echo "$" > ~/.ssh/id_rsa
        chmod 600 ~/.ssh/id_rsa
    
    - name: Add EC2 host to known_hosts # EC2 서버 SSH 호스트 키를 known_hosts에 미리 등록
      env:
        EC2_HOST: $
      run: |
        ssh-keyscan -H $EC2_HOST >> ~/.ssh/known_hosts
    
    - name: Deploy to EC2 # EC2 서버에 도커 컨테이너 배포
      env:
        ECR_REGISTRY: $
        ECR_REPOSITORY: $
        IMAGE_TAG: $
        EC2_HOST: $ # EC2 서버 public IP
        EC2_USERNAME: $ # ec2-user
      run: |
        # 디버깅을 위한 환경 변수 출력
        echo "ECR_REGISTRY: ${ECR_REGISTRY}"
        echo "ECR_REPOSITORY: ${ECR_REPOSITORY}"
        echo "IMAGE_TAG: ${IMAGE_TAG}"
        echo "전체 이미지 경로: ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG}"

        # EC2 서버에 SSH 접속하여 AWS 자격 증명 및 설정 파일 생성
        # EC2 서버에서 Private Amazon ECR에 접근하기 위한 AWS 인증 정보 설정
        # 실무에서는 EC2 IAM Role을 부여하여 Access Key를 서버에 직접 저장하지 않는 방식 권장
        ssh -i ~/.ssh/id_rsa $EC2_USERNAME@$EC2_HOST "mkdir -p ~/.aws"
        echo "[default]
        aws_access_key_id=$
        aws_secret_access_key=$
        region=$" | ssh -i ~/.ssh/id_rsa $EC2_USERNAME@$EC2_HOST "cat > ~/.aws/credentials"
        
        # 신규 도커 이미지를 Private 이미지 저장소 Amazon ECR에서 pull 받고,
        # 기존 컨테이너 제거 후 신규 컨테이너 실행
        ssh -i ~/.ssh/id_rsa $EC2_USERNAME@$EC2_HOST "\
          source ~/.bashrc && \
          aws ecr get-login-password --region $ | docker login --username AWS --password-stdin ${ECR_REGISTRY} && \
          docker pull ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG} && \
          (docker rm -f 컨테이너명 || true) && \
          docker run -d --name 컨테이너명 -p 3000:3000 ${ECR_REGISTRY}/${ECR_REPOSITORY}:${IMAGE_TAG} && \
          docker image prune -a -f"
          # 현재 어떤 컨테이너에서도 사용하지 않는 도커 이미지 전체 삭제

프로젝트폴더/.github/workflows 경로에 위와 같은 .yml 파일을 생성하고 커밋 후 Push 하면 자동 실행됩니다.
프로젝트 내부 코드를 변경하고 정상적으로 서버에 반영되었는지 확인하면 좋습니다.

Node.js, golang은 5분 내로 배포되는 경우가 많지만, 크기가 큰 모바일 앱은 1시간도 걸릴 수 있습니다.
프로젝트 크기가 커질수록 CI/CD 시간도 길어지고 작은 변경에도 전체를 다시 배포해야 합니다.
서비스별 독립적인 개발·배포·확장이 가능하도록 마이크로서비스 아키텍처(MSA)가 발전하게 되었습니다.

하나의 워크플로 내에서 각 Job은 독립된 Runner 환경에서 실행되며,
필요한 경우 Artifact를 통해 파일을 공유할 수 있습니다.
같은 Job에 포함된 Step들은 동일한 Runner에서 순차적으로 실행됩니다.

빌드 Job에서 빌드 결과물 Artifacts를 업로드하고, 배포 Job에서 다운로드하여 사용할 수도 있습니다.

AWS ECR 컨테이너 레포지토리 생성 방법
AWS > ECR 검색 > Amazon ECR 선택 > Private registry 메뉴 > Repositories 메뉴 > 우측 상단 Create repository 버튼 > Repository name : 네임스페이스명/레포지토리명 입력 > Create

AWS IAM Users 인증 정보 확인 방법
AWS > IAM 검색 > IAM 선택 > Access management 메뉴 > Users 메뉴 > Administrator 유저 선택 > Security credentials 탭 선택 > Access Keys > Create access key 버튼 > Use case : Command Line Interface (CLI) 선택 > Confirmation 체크 > Next > Create access key > 생성된 Access key, secret access key 복사 및 저장 > Done