FireDrago

[GithubActions] GithubActions로 Docker CD 구성하기 본문

프로그래밍/배포

[GithubActions] GithubActions로 Docker CD 구성하기

화이용 2025. 7. 29. 18:58

CD (Continuous Delivery/Deployment) : 지속적인 제공/배포

 
 

CD(지속적인 제공/배포)는 코드 변경사항을 테스트 환경 또는 프로덕션 환경에 자동으로 릴리즈하는 과정을 의미한다. CD를 통해 개발자는 배포 과정을 자동화하여 더 빠르고 안정적으로 서비스를 전달할 수 있다.

CD 과정 코드로 확인하기

# 워크플로우의 이름을 'Dev Server CD'로 지정합니다. CD는 Continuous Deployment(지속적 배포)를 의미합니다.
name: Dev Server CD

# 이 워크플로우가 언제 실행될지를 정의합니다.
on:
  # 'pull_request' 이벤트가 발생했을 때 워크플로우를 실행합니다.
  pull_request:
    # pull request가 닫혔을 때(closed)만 동작합니다.
    types: [ closed ]
    # 'develop' 브랜치를 대상으로 하는 pull request에 대해서만 동작합니다.
    branches: [ develop ]
    # 변경된 파일이 'server/' 디렉토리 내에 있을 경우에만 이 워크플로우를 실행합니다.
    paths: [ 'server/**' ]
  # 'push' 이벤트가 발생했을 때도 워크플로우를 실행합니다.
  push:
    # 'develop' 브랜치에 직접 push가 일어났을 때 동작합니다.
    branches:
      - develop

# 워크플로우에서 실행될 작업(job)들의 목록을 정의합니다.
jobs:
  # 'deploy'라는 이름의 작업을 정의합니다.
  deploy:
    # 이 작업을 실행할 조건입니다. 'develop' 브랜치로 pull request가 병합(merged)되었을 경우에만 실행됩니다.
    # 그냥 닫힌(closed) 경우에는 실행되지 않습니다.
    if: github.event.pull_request.merged == true
    # 이 작업이 실행될 가상 머신 환경을 지정합니다. GitHub 제공 환경이 아닌, 'self-hosted'와 'backend' 태그가 붙은 자체 서버에서 실행됩니다.
    runs-on: [self-hosted, backend]
    # 작업이 순차적으로 수행할 단계(step)들의 목록입니다.
    steps:
      # 'Checkout'이라는 이름의 스텝입니다.
      - name: Checkout
        # 'actions/checkout@v4' 액션을 사용해 GitHub 리포지토리의 소스 코드를 가상 머신으로 내려받습니다.
        uses: actions/checkout@v4

      # 'Login to GitHub Container Registry'라는 이름의 스텝입니다.
      - name: Login to GitHub Container Registry
        # 셸 명령어를 직접 실행하여 GitHub 컨테이너 레지스트리(ghcr.io)에 로그인합니다.
        # GITHUB_TOKEN을 표준 입력(stdin)으로 docker login 명령어에 전달하여 안전하게 로그인합니다.
        run: echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin

      # 'Deploy with Docker Compose'라는 이름의 스텝입니다. 배포를 담당하는 핵심 부분입니다.
      - name: Deploy with Docker Compose
        run: |
          # self-hosted runner 서버의 프로젝트 디렉토리로 이동합니다.
          cd /home/ubuntu/2025-moment/server
          # docker-compose.yml에 정의된 'app' 서비스의 최신 이미지를 레지스트리에서 받아옵니다(pull).
          sudo docker compose pull app
          # 'app' 서비스를 새로 받은 이미지로 다시 시작합니다.
          # --no-deps: 의존 관계에 있는 다른 서비스는 재시작하지 않습니다.
          # -d: 백그라운드에서 실행합니다(detached mode).
          sudo docker compose up --no-deps -d app

      # 'Prune old images'라는 이름의 스텝입니다.
      - name: Prune old images
        # 사용되지 않는 오래된 도커 이미지를 삭제하여 서버의 디스크 공간을 확보합니다.
        # -f(--force): 확인 메시지 없이 바로 삭제합니다.
        run: docker image prune -f

트리거 설정은 ci과정을 이해했다면 어렵지 않다.
PR이 단순히 닫힌 경우가 아니라
if: github.event.pull_request.merged == true 머지된 경우에만 작동하도록 설정했다.

Self-hosted-runner를 사용하는 이유

# 이 작업이 실행될 가상 머신 환경을 지정합니다. GitHub 제공 환경이 아닌, 'self-hosted'와 'backend' 태그가 붙은 자체 서버에서 실행됩니다.
runs-on: [self-hosted, backend]

문제는 이 부분이다. self-hosted 설정을 하게되면, GithubActions에서 작업하지 않는다.

자체 서버에서 CD 작업을 실행하게 된다. 즉 내가 설정한 작업들이 'backend' 태그가 붙은 서버에서 실행된다.

1. 제한된 서버 환경의 네트워크 문제 해결 (ec2 인바운드 설정)

AWS EC2와 같은 클라우드 서버는 보안을 위해 특정 IP에서만 접속할 수 있도록 방화벽(인바운드 규칙)이 설정된 경우가 많다.

우테코 EC2는 특정포트만 접속할 수 있도록 인바운드 설정이 되어있다. 

그래서 불특정 IP 대역을 사용하는 GitHub Actions 서버는 이 방화벽에 막혀 우리 서버에 접근할 수 없다.

 

self-hosted-Runner는 우리 서버 내부에서 외부로 요청을 보내(아웃바운드) 작업을 가져오기 때문에,

이 문제를 깔끔하게 해결할 수 있다.

 

아웃바운드 연결이란 내 서버에서 먼저 요청을 보내고 그에 대한 응답을 받는것으로,

AWS 방화벽이 먼저 요청을 건 것을 임시저장하고 응답에 대해서는 인바운드 규칙을 적용하지 않는다.

 

즉 모르는 사람이 먼저 요청을 거는 것은 막지만 (인바운드)

내가 먼저 요청한 경우에는 응답을 막지 않는 것이다.(아웃바운드)


2.  고성능 서버를 활용한 속도 향상

만약 GitHub이 제공하는 기본 서버보다 우리가 가진 서버의 성능이 더 좋다면,
빌드/배포 작업을 자체 서버에서 실행하는 것이 훨씬 빠르다.

프로젝트 규모가 크고 빌드 과정이 복잡할수록 이 성능 차이는 큰 이점으로 작용한다.

이런 경우에도 self-hosted 설정이 유용하다.

 

배포 과정 요약

결론적으로 위 워크플로우는 develop 브랜치에 코드가 병합되면,
self-hosted runner가 설치된 내 서버에서 다음의 작업을 순차적으로 수행한다.

  1. Checkout: 최신 코드 가져오기
  2. Login: Docker 이미지를 받기 위해 컨테이너 레지스트리에 로그인
  3. Deploy: 최신 이미지를 내려받고, Docker Compose를 통해 서비스를 재시작
  4. Prune: 서버 용량을 확보하기 위해 오래된 이미지를 삭제