개발일지

GitHub Actions, Docker, EC2로 CI/CD 구축하기 본문

Infra, AWS, Linux

GitHub Actions, Docker, EC2로 CI/CD 구축하기

lyjin 2023. 3. 14.

개요

이전까지는 CI/CD 없이 EC2만으로 배포를 진행했습니다. 배포되기까지 다음과 같은 과정을 거칩니다.

1) EC2 인스턴스에서 git clone
2) 로컬에서 commit > PR > merge
3) EC2에서 애플리케이션 종료 > git pull > 애플리케이션 실행

 

코드가 수정될 때마다 위의 과정을 거치다보니 많은 시간이 소요됐습니다. 또한 직접 작업하는 만큼 실수할 가능성이 존재했고 배포 과정에 문제가 생겼을 때 어디서 발생한 건지 알기 힘들 때도 있었습니다. 따라서 이번 프로젝트에서는 배포 과정을 자동화하여 불필요한 반복을 줄일 수 있도록 CI/CD 환경을 구축했습니다.


GitHub Actions, Docker 적용

Jenkins, TravisCI 등 다양한 CI/CD 툴이 존재하는데 이번 프로젝트에서는 GitHub Actions를 사용했습니다. 또한 도커를 사용해 빌드된 이미지가 실행될 수 있도록 구현했습니다. 구현할 workflow를 간단히 표현하자면 다음과 같습니다.

 

 

시작하기에 앞서 진행 과정은 다음과 같습니다. EC2 인스턴스 생성 단계는 생략하겠습니다.

1) AWS EC2 인스턴스 생성하기
2) Dockerfile 작성하기
3) GitHub Actions 스크립트 생성하기
4) EC2 서버에 Self-hosted Runner 설정하기
5) EC2 서버에 Docker 설치하기

 

Dockerfile 작성하기

이미지가 빌드될 때 적용할 Dockerfile을 작성합니다. Dockerfile은 프로젝트 최상단에 위치해야합니다.

 

FROM node:16                # 베이스가 될 이미지
WORKDIR /usr/src/app        # 작업할 디렉토리

# 호스트 파일 > 컨테이너 안의 경로로 복사
COPY package.json yarn.lock /usr/src/app/
RUN yarn install            # 커맨드 실행

COPY . .                    # 모든 파일 복사
RUN yarn build                # 커맨드 실행

EXPOSE 3000                    # 빌드된 이미지에서 열어줄 포트
CMD ["yarn", "start:prod"]    # 컨테이너가 생성될 때 실행할 커맨드

 

dockerignore 파일도 작성합니다. 이미지를 빌드할 때 포함하지 않을 항목들을 적어줍니다.

 

.git*
.env*

Dockerfile
node_modules
dist

Image Layer

작성하면서 많은 사람들이 왜 COPY를 두 단계에 거쳐서 실행하는지 궁금했고 이에 대해 알아봤습니다. 도커 이미지가 생성될 때 각 명령어들은 layer로 나뉘어 저장됩니다. 그리고 이미 생성된 layer는 변경이 없으면 재사용이 가능합니다(Layer Caching). 따라서 수정이 자주 일어나지 않는 항목들은 따로 layer를 구성하는 것이 좋습니다.

 


GitHub Actions 생성하기

이제 GitHub Actions 스크립트 파일을 생성하겠습니다. GitHub repository > Actions에서 yml 파일을 생성할 수 있습니다.

 

# .github/workflows/main.yml

name: CICD Docker

# 환경변수
env:
  DOCKER_IMAGE: ghcr.io/${{ github.actor }}/vote-api-server-img
  DOCKER_CONTAINER: vote-api-server

# 이벤트
on:
  pull_request:
    branches: ['main']
  push:
    branches: ['main']

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      # 1. 코드를 CI 서버 브랜치로 체크아웃
      - name: Checkout source code
        uses: actions/checkout@v3

      # 2. 도커 빌더 설치
      - name: Set up docker buildx
        uses: docker/setup-buildx-action@v1

      # 3. GitHub Container Registry 로그인
      - name: Login ghcr
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_TOKEN }} # GitHub Access token와 동일

      # 4. 이미지 빌드, GHCR에 업로드
      - name: Build and Push
        id: docker_build
        uses: docker/build-push-action@v2
        with:
          push: true
          tags: ${{ env.DOCKER_IMAGE }}:latest

  deploy:
    needs: build
    runs-on: [self-hosted, label-vote]     # runner label

    steps:
      # 5. env 파일 생성
      - name: Make envfile
        #...후에 추가

      # 6. GitHub Container Registry 로그인
      - name: Login ghcr
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GHCR_TOKEN }}

      # 7. 도커 컨테이너 생성 및 실행
      - name: Run docker
        run: |
          docker stop ${{ env.DOCKER_CONTAINER }} && docker rm ${{ env.DOCKER_CONTAINER }} && docker rmi ${{ env.DOCKER_IMAGE }}:latest
          docker run -d -p 80:3000 --env-file ./.env --name ${{ env.DOCKER_CONTAINER }} ${{ env.DOCKER_IMAGE }}:latest

개념 정리

  • GitHub Actions의 액션(Action)이란?
    CI/CD 워크플로우를 구성하다보면 반복적인 과정이 존재합니다. 액션은 이러한 반복 단계를 재사용하기 용이하도록 GitHub Actions에서 제공하는 일종의 직업 공유 매커니즘입니다. GitHub Marketplace를 통해 공유된 액션을 쉽게 검색하고 사용할 수 있습니다.
  • GitHub Checkout; GitHub에 올려둔 코드를 CI 서버로 내려받은 후 특정 브랜치로 전환하는 행위를 말합니다.
  • GitHub Container Registry; Registry란 컨테이너 이미지를 저장하고 액세스하는데 사용하는 저장소로 시스템 간 이미지를 공유하는 중개자 역할을 합니다. GitHub Container Registry는 GitHub에서 제공하는 도커 이미지 저장소 입니다.

env 파일 적용하기

이번 프로젝트에서 환경변수는 env 파일로 관리했습니다. 따라서 도커 이미지를 생성할 때도 env 파일과 함께 빌드되어야 환경변수가 적용되고 배포에 문제가 없을텐데 문제는 env 파일은 gitignore 되어 github 저장소에 올라가지않습니다.
고민하다가 docker run --env-file 옵션으로 컨테이너가 실행될 때 사용할 env 파일을 설정해줄 수 있다는 것을 알게되었습니다. 이 옵션을 사용해 배포 서버에서 env 파일을 생성하고 생성한 파일을 컨테이너에 적용할 수 있도록 구현했습니다.


스크립트 수정

env 파일 생성 단계를 워크플로우에 추가해봅시다. 위의 yml 파일 5번 과정에 해당합니다. 저는 GitHub Marketplace에 올라와있는 SpicyPizza/create-envfile@v1.3 액션을 사용했습니다. 자세한 내용은 여기를 참고해주세요.

 

steps:
  # 5. env 파일 생성
  - name: Make envfile
    uses: SpicyPizza/create-envfile@v1.3
    with:
      envkey_NODE_ENV: 'production'
      envkey_ACCESS_TOKEN_SECRET_KEY: ${{ secrets.ACCESS_TOKEN_SECRET_KEY }}
      envkey_ACCESS_TOKEN_EXPIRATION_TIME: ${{ secrets.ACCESS_TOKEN_EXPIRATION_TIME }}
      #...
      file_name: .env
      fail_on_empty: true

 

이제 도커 컨테이너 실행 단계에서 .env 파일을 환경변수로 설정해줄 수 있습니다.

 

docker run -d -p 80:3000 --env-file ./.env --name ${{ env.DOCKER_CONTAINER }} ${{ env.DOCKER_IMAGE }}:latest
 


Self-hosted Runner 설정하기

 

GitHub Actions Runner

GitHub Actions은 Runner에서 동작하고 이 Runner는 워크플로우의 작업(job)들을 실행시킵니다. GitHub Actions 러너에는 github-hosted runner와 self-hosted runner 두가지 종류가 있습니다. Github-Hosted Runner는 GitHub 서버에서 동작하는 러너입니다. 서버를 빌려 사용하는 만큼 제한 사항이 있거나 해외 접근을 차단하는 등 정상적으로 동작하지 않을 수 있습니다. 따라서 self-hosted runner를 세팅하고 사용해보도록 하겠습니다.


Self-Hosted Runner

Self-Hosted Runner는 GitHub Actions이 사용자가 지정한 서버 자원으로 동작하도록 설정하는 기능입니다. Agent가 Listen 하는 형태가 아닌 GitHub 서버로 접근하는 방식이기 때문에 개인 환경 IP를 노출할 필요도 없습니다.


이제 실제 배포 서버에 self-hosted runner를 설정해보겠습니다. GitHub repository > Settings > Actions > Runners에서 self-hosted runner를 생성할 수 있습니다.

 


 

OS에 따라 설정하는 방법이 설명되어있습니다. EC2 서버에 접속하고 설명에 따라 그대로 수행하면 됩니다. 마지막에 $ ./run.sh을 실행하고 엔터를 누르다 보면 label을 설정하라고 합니다. 이때 yml 파일에 설정한 라벨과 동일하게 설정해주면 됩니다.

 

deploy:
  needs: build
  runs-on: [self-hosted, label-vote] 

 

완료 후 GitHub에 들어가보면 Runner가 생성된 것을 확인할 수 있습니다.

 

 


EC2 서버에 Docker 설치하기

마지막으로 EC2 서버에서도 docker 커맨드를 사용할 수 있도록 docker를 설치해줍니다. 설치 후 sudo 없이 docker 커맨드를 사용할 수 있다면 설치 성공입니다.

 

# 1. yum 업데이트
$ sudo yum update -y

# 2. 도커 설치
$ sudo yum install docker -y

# 3-1. 도커 그룹에 sudo 추가
$ sudo usermod -aG docker ec2-user

# 3-2. permission denined 에러 발생 하면 실행
$ sudo chmod 666 /var/run/docker.sock

# 4. 도커 테스트
$ docker run hello-world

 

이제 main 브랜치에 push 또는 PR이 발생하면 등록된 워크플로우가 실행됩니다. Git repository > Actions에서 진행사항을 확인할 수 있습니다.

 

)

 


마무리

항상 시도만 하다가 처음으로 CI/CD 구축에 성공했는데 왜 이제서야 깨달았나싶을 정도로 편리한 기능 같습니다. 다음에는 다른 툴을 사용해서 CI/CD를 구축해보고싶습니다. 그리고 Redis를 적용하고나서 배포 시간이 굉장히 늘어났는데 속도를 어떻게 개선할 수 있을 지 고민해봐야할 거 같습니다.

 


참고한 글

🔗GitHub Actions의 체크아웃(Checkout) 액션으로 코드 내려받기
🔗도커의 환경변수 설정
🔗Github Actions에 Self-hosted Runner 등록하기
🔗맥북을 Self-hosted Github action runner로 만들기
🔗EC2 Linux에 Docker 설치하기