[CI/CD] Github Actions + Docker로 CI/CD 환경 구축하기

김시원·2023년 5월 26일
1

👾 CI/CD란?

어플리케이션 개발부터 배포까지 과정을 자동화하여 효율적인 서비스 환경을 만드는 것

  • CI (Continuous Integration, 지속적 통합): 주기적으로 새로 생성되고 수정되는 코드들이 자동으로 머지, 빌드, 테스트되는 과정
  • CD (Continuous Deployment, 지속적 배포): CI를 포함하여 배포가 가능한 환경 구성까지 자동화로 이루어지고, 배포까지 자동화하는 방식

CI/CD의 흐름

  1. Source code
  2. Merge
  3. Test
  4. Build
  5. Ready to Deploy
  6. Automatic Deployment

👾 CI/CD 구축을 위해 필요한 것

  1. 프로젝트 생성, 테스트 코드 및 로직 작성
  2. Dockerfile 작성
  3. GitHub Container Registry 사용을 위한 토큰 발급
  4. GitHub Actions Workflow 작성
  5. AWS EC2 인스턴스 생성
  6. EC2 SSH 환경에 GitHub Actions Runner & Docker 설치

이 순서대로 포스팅을 진행해보도록 하겠다.


👾 1. 프로젝트 생성, 테스트 코드 및 로직 작성

Nest.js 프로젝트를 생성해주고 API 로직과 테스트 코드를 작성해준다. 이때 테스트 코드가 모두 success로 통과해야 GitHub Actions에서 테스트 통과를 할 수 있다.

👾 2. Dockerfile 설정

  1. Docker Desktop을 설치 후 실행한다.
  2. Dockerfile을 작성해준다. (파일명이 틀리면 안됌!)
# 로컬 node 버전과 맞춤
FROM node:16.16.0

# 명령어를 실행할 워크 디렉토리 생성
RUN mkdir /app
WORKDIR /app

# 프로젝트 전체를 워크 디렉토리에 추가
ADD . /app

# 프로젝트에 사용되는 모듈 설치
RUN npm install

# Nest.js 빌드
RUN npm run build

# Port (3000) 개방
EXPOSE 3000

# 서버 실행
ENTRYPOINT npm run start:prod
  1. .dockerignore 파일을 만들어서 빌드에 필요 없는 파일이나 폴더를 지정해준다.
node_modules/
dist/
  1. docker build -t [image name] . 명령어로 도커 이미지를 빌드한 뒤 컨테이너를 띄운다. [image name]에는 사용자가 원하는 이미지의 이름을 넣는다.

  2. docker run --name [container name] -d -p 3002:3000 [image name] 명령어로 해당 [image name] 이미지를 사용하여 컨테이너를 백그라운드에서 실행한다. 또한, 호스트의 포트 3002와 컨테이너 포트 3000을 매핑하여 3002 포트에서 3000 포트에 액세스할 수 있게 설정해준다. [container name]에는 사용자가 원하는 컨테이너의 이름을 넣어준다.

  • 다음 명령어를 입력하고 3002번 포트로 접속해보면 API가 잘 실행되는 것을 확인할 수 있다. (현재는 비어있는 배열을 반환하는 API이다.)

여기까지하면 우리는 컨테이너를 정상적으로 생성한 것이다.

👾 3. GitHub Container Registry 사용을 위한 토큰 발급

GitHub Container Registry란?

깃허브에는 GitHub "Package" Registry 서비스가 있는데, 이는 깃허브에서 제공해주는 깃허프판 패키지 저장소 서비스이다. 즉, 깃허브 사용자 간의 패키지 저장, 공유, 배포 등을 할 수 있는 서비스이다.

GitHub "Container" Registry는 이 Package Registry에서 도커 이미지 관리 기능을 강화한 서비스이다.

  1. 먼저, GitHub Access Token을 발급해야한다.
  2. Generate new token 버튼을 눌러서 classic version으로 들어간다.
  3. 아래와 같이 설정해준다.
  4. 발급된 토큰은 어딘가에 잘 복사해둔다.
  5. 해당 repo > Settings > Secrets and variables > Actions > New repository secret을 눌러주어 환경변수로 access token을 등록해준다.

👾 4. GitHub Actions Workflow 작성

실제 우리의 프로젝트에 CI/CD를 적용할 수 있도록 GitHub Actions Workflow를 작성해주자.
1. 해당 repo > Actions > Node.js를 선택해주면 우리의 repo에 .github/workflows/node.js.yml 파일을 만들 수 있는 창으로 이동할 수 있다. (IDE에서 직접 폴더를 생성하고 yml 파일을 만들어도됌)

2. 이제 이 파일에서 우리의 프로젝트가 어떤 일련의 동작을 하여 CI/CD를 진행할지 커스터마이징을 해주면 된다.

# Workflow title
# 명시하지 않는 경우 파일의 경로가 타이틀이 된다.
name: CodeBLUE-CI-CD

# main.yml에서 사용할 환경변수
env:
  DOCKER_IMAGE: ghcr.io/siwon-kim/vog-nest
  DOCKER_CONTAINER: vog-nest-container

# 이벤트
# main 브랜치에 push나 pull request가 일어난우경우
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

# 이벤트가 일어나면 해야할 작업들을 명시
# 테스트, 빌드, 배포 단위로 명시
# 각각의 job은 runner라는 컨테이너에서 개별적으로 실행
jobs:
  # test code with Jest
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v3
      - name: Setup node.js 16.x
        uses: actions/setup-node@v3
        with:
          node-version: 16.x
          cache: 'npm'
      - name: Load env file
        run: |
          touch .env
          echo "MODE=${{ secrets.MODE }}" >> .env
          echo "PORT=${{ secrets.PORT }}" >> .env
          echo "RDS_DB_NAME=${{ secrets.RDS_DB_NAME }}" >> .env
          echo "RDS_HOSTNAME=${{ secrets.RDS_HOSTNAME }}" >> .env
          echo "RDS_PASSWORD=${{ secrets.RDS_PASSWORD }}" >> .env
          echo "RDS_PORT=${{ secrets.RDS_PORT }}" >> .env
          echo "RDS_USERNAME=${{ secrets.RDS_USERNAME }}" >> .env
      - name: Install dependencies
        run: npm install
      - run: npm run test
  # Docker image build
  build:
    # build가 시작되려면 test를 완료해야함
    needs: test
    runs-on: ubuntu-latest
    steps:
      - name: Checkout Source Code
        uses: actions/checkout@v3
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v2
        with:
          driver-opts: |
            image=moby/buildkit:v0.10.6
      - name: login to ghcr
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.CODEBLUE_TOKEN }}
      - name: Build and push
        id: docker_build
        uses: docker/build-push-action@v3
        with:
          push: true
          tags: ${{ env.DOCKER_IMAGE }}:latest

  # ec2 환경에 배포
  deploy:
    # deploy가 시작되려면 build를 완료해야함
    needs: build
    # ec2에 설치한 Runner로 job 실행
    runs-on: [self-hosted, label-vog]
    # Github container registry 로그인
    steps:
      - name: Login to ghcr
        uses: actions/checkout@v3
      - name: Setup docker build
        id: buildx
        uses: docker/setup-buildx-action@v2
      - name: login to ghcr
        uses: docker/login-action@v2
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.CODEBLUE_TOKEN }}
      - name: Run docker
        run: |
          touch .env
          echo "MODE=${{ secrets.MODE }}" >> .env
          echo "PORT=${{ secrets.PORT }}" >> .env
          echo "RDS_DB_NAME=${{ secrets.RDS_DB_NAME }}" >> .env
          echo "RDS_HOSTNAME=${{ secrets.RDS_HOSTNAME }}" >> .env
          echo "RDS_PASSWORD=${{ secrets.RDS_PASSWORD }}" >> .env
          echo "RDS_PORT=${{ secrets.RDS_PORT }}" >> .env
          echo "RDS_USERNAME=${{ secrets.RDS_USERNAME }}" >> .env
          docker stop ${{ env.DOCKER_CONTAINER }} && docker rm ${{ env.DOCKER_CONTAINER }} && docker rmi ${{ env.DOCKER_IMAGE }}:latest
          docker run --env-file ./.env -d -p 80:3000 --name ${{ env.DOCKER_CONTAINER }} --restart always ${{ env.DOCKER_IMAGE }}:latest

일단 EC2 instance를 생성 및 연결해주지 않았기 때문에 deploy는 실패할 것이다.
또한, test나 build에서 실패한다면 로컬 환경에서 직접 npm 명령어를 통해 어디서 문제가 발생하는지 파악하고 수정해주면 된다.

👾 5. AWS EC2 인스턴스 생성 & Self-Hosted Runner

  1. AWS EC2 Instance를 하나 생성해주고, SSH 환경에 접속할 준비를 해준다.
  2. Self-Hosted Runner를 설치해주어야한다. 이는 GitHub Actions에서 사용되는 개인적인 실행 환경으로, 일반적으로 GitHub 클라우드에서 실행되는 가상 환경 대신에, 사용자가 직접 제어하는 자체 서버 또는 가상 머신에서 실행될 수 있게 해준다. GitHub actions workflows에서 작성한 job이 보통 깃헙에서 이 runner라는 컨테이너에서 실행되지만 원하는 서버, 즉 우리의 EC2 서버에 이 Runner를 설치하여 사용할 수 있도록 설정을 해주어야 한다.
    2-1) Repo에서 Settings > Actions > Runners로 접속한다.
    2-2) New self-hosted runner 버튼을 누르고, runner image는 ec2의 OS가 Linux이므로 Linux를 선택해준다.

2-3) Download, configure, using your self-hosted runner 이 세가지 항목이 나오는데 이 순서대로 SSH 환경에서 커멘드를 실행해주면 된다.

  • 여기서 configure에 접속할때 permission denied가 뜬다면 이 포스팅을 확인해보면 된다.

  • configure: 주의할 부분은 configure 설정을 할 때 label 입력 부분인데, 우리가 workflow 파일에서 deploy job의 runs-on에 2번째 값인 label값과 동일한 문자열을 입력해주어야 한다.

  • configure: 또한, runner를 실행하되 백그라운드에서 실행할 수 있도록 nohup ./run.sh &로 명령어를 실행해준다.

👾 6. EC2 SSH 환경에 GitHub Actions Runner & Docker 설치

  1. EC2 instance에 docker를 설치한다. 공식문서에 명령어가 나와있다.
    또는, 아래와 같은 명령어를 사용할 수 있다.
    1-1) sudo apt update
    1-2) sudo apt install apt-transport-https
    1-3) sudo apt install ca-certificates
    1-4) sudo apt install curl
    1-5) sudo apt install software-properties-common
    1-6) sudo add-apt-repository "deb [arch=amd64] https:download.docker.com/linux/ubuntu bionic stable"
    1-7) sudo apt update
    1-8) apt-cache policy docker-ce
    1-9) sudo apt install docker-ce
    1-10) sudo systemctl status docker - docker 잘 설치됐는지 확인

    1-11) sudo docker pull hello-world - hello-world image 불러오기
    1-12) sudo docker images - 나의 image 목록 보기

    1-13) sudo docker run hello-world - hello-world container 실행해보기

  2. Docker가 EC2 인스턴스에 정상적으로 설치되고, GitHub Runner가 백그라운드에서 잘 실행되고 있다면, 이제 GitHub main branch에 pull request나 push가 일어날때 자동으로 배포에 반영된다.

Troubleshooting

  1. GitHub Actions Build Error

https://velog.velcdn.com/images/c1madang/post/2bdb0091-5330-4624-9819-9ad668ebb750/image.png

https://github.com/docker/build-push-action/issues/761

  • 다음과 같이 설정해주었다.
  1. self-hosted runner 설정 중 permission denied error

  1. 해당 config.sh 파일에 대한 쓰기 권한이 있는지 확인한다.
  • ls -l 명령어 입력 https://velog.velcdn.com/images/c1madang/post/3a1234ec-3c3a-4b03-924b-db76e53e7189/image.png

가장 왼쪽의 -rwxr-xr-x를 해석해보면, 파일 소유자는 읽기, 쓰기, 실행 권한을 가지고 있지만, 그룹 및 기타 사용자는 읽기, 실행 권한만을 가지고 있다는 것이다.

2. sudo chmod +w ./config.sh 명령어로 해당 파일에 대한 쓰기 권한을 부여해준다.
3. 동일한 에러가 떴다. config.sh가 들어있는 디렉토리 자체에 대한 쓰기 권한의 문제일 수도 있다.
4. actions-runner 디렉토리에 sudo chmod +w actions-runner 쓰기 권한을 부여해준다.
5. 동일한 에러가 떴다. 이번엔 사용자 (나)와 그룹에 쓰기 권한을 부여해준다.
6. sudo chown <user>:<group> actions-runner를 입력한다. 이때 user와 group은 각각 whoami와 id -g -n 명령어를 통해 알 수 있다. 나의 경우 ubuntu & ubuntu였다.
7. 다시 config.sh로 접속하니 잘 들어가졌다.

0개의 댓글