[Github Actions] CI, CD

초이지수·2023년 10월 28일

Infra

목록 보기
1/1
post-thumbnail

처음으로! CI, CD 파이프라인을 구축해 보았다! 나의 기억력을 믿지 못하기 때문에... 기록해보았다!


🧐 CI, CD란?

CI/CD의 기본 개념은 지속적인 통합, 지속적인 서비스 제공, 지속적인 배포이다. CI/CD는 새로운 코드 통합으로 인해 개발 및 운영팀에 발생하는 문제(일명 "통합 지옥(integration hell)")를 해결하기 위한 솔루션이다!

나는 EC2에 Docker container를 띄우고, main branch에 push 했을 때 배포 자동화 되도록 만들었다.
테스트 코드 통과 시 배포 되도록 하고 싶었지만... 지금 회사 상황상 테스트 코드까지는 무리..이다


📌 1. 어떤 도구를 사용하지?

도구를 선택하기 전에 했던 고민들은 다음과 같다.

1. 프로젝트 규모

커뮤니티 관련 웹 서비스라 규모가 아주 크지 않았고, 초기 서비스라 사용자도 많지 않을 것으로 예상했다.

2. 팀이 익숙한 도구 or 내가 익숙한 도구

지금 재직 중인 회사에는 배포 자동화가 구축되어 있지 않다.
일단 내가 혼자 구축해낼 수 있는, 빨리 배워서 성공시킬 수 있는 도구를 선택 해야겠다고 생각했다.

3. 통합 가능성

현재 사용 중인 코드 호스팅 플랫폼(Github), 클라우드 서비스(AWS RDS, AWS S3)와의 통합을 고려했다.

4. 비용

스타트업이다보니 아무리 기술적으로 확장성이 높고 사용하기 편하더라도 오버스팩은 ... 비용적으로 부담이다.
Docker Container도 하나(혹은 두 개? 정도만)만 띄울 예정이고 애플리케이션 규모도 그리 크지 않아, 쿠버네티스와 같은 기술은 지금 당장은 필요하지 않았다.


📎 1-1. 각각 도구는 어떤 것들이 있지?

🖇 CI, CD

  1. main branch에 push가 일어날 경우 Docker 이미지 빌드하는 CI 실행
  2. CI 통과 시, 빌드된 이미지를 사용하여 Docker 컨테이너 띄우는 CD 실행

⭕️ 1. Github Actions

  • 장점 : Github과 통합되어 있는 도구이고 사용하기 간편해 처음 CI, CD 파이프라인을 구축할 때 많이 사용한다고 한다.

2. Jenkins

  • 장점 : 유연성 및 확장성
    다양한 빌드 및 배포 시나리오에 맞게 구성할 수 있다.

  • 단점 : 설정 복잡성 및 초기 학습 곡선


🖇 Docker Container

⭕️ 1. EC2

EC2 가상 서버에 컨테이너를 띄우고 관리한다.

  • 장점 : 사용해 보았기에 대략 비용이 예상되고 초기 학습이 필요 없다. 건너 건너 물어보니.. 왠만하면 EC2로 할 수 있으면 EC2가 제일 저렴하다고 하셨다

2. Fargate

  • 장점 : 관리 수준 및 자동 확장, 리소스 관리
    - 서버리스 컨테이너 실행 환경을 제공하며 EC2 인스턴스 관리 없이 컨테이너를 실행한다.
    - 컨테이너의 CPU 및 메모리 요구 사항을 기반으로 자동으로 필요한 컴퓨팅 리소스를 할당한다. 편리하지만, 리소스 할당에 따라 비용이 발생할 수 있다..

  • 단점 : 비용 및 초기 학습 곡선
    CPU 및 메모리 요구 사항, 실행 시간 및 사용된 컨테이너 수에 따라 결정된다. 사용량에 따라 비용이 크게 다를 수 있다. 아직.. Fargate를 잘 몰라서 사용량에 따라 비용이 다르다고 하면 무섭다...


🖇 추가 옵션 (EC2와 Fargate 위에 ECS, EKS가 동작)

1. ECS (Elastic Container Service) : 단순한 관리 및 AWS 환경 내에서의 빠른 시작

  • AWS의 관리형 컨테이너 오케스트레이션 서비스로, EC2 및 Fargate와 함께 사용할 수 있다.
  • AWS의 다른 서비스와 통합이 잘 되어 있어서, AWS 인프라스트럭처와 조화롭게 사용하기 용이하다.
  • Fargate(서버리스 컨테이너 환경을 활용하고자)를 사용했을 때 Fargate 옵션을 사용할 수 있다.
  • EC2 인스턴스를 백엔드로 사용할 경우 EC2 비용이 청구되고, Fargate를 백엔드로 사용하면 사용한 vCPU와 메모리 시간에 따라 비용이 청구된다.

2. EKS (Elastic Kubernetes Service) : 고급 기능 및 확장성

  • AWS에서 관리되는 Kubernetes 클러스터 서비스로, 쿠버네티스를 사용하여 컨테이너를 관리하고 오케스트레이션할 수 있다.
  • 대규모, 복잡한 애플리케이션 또는 마이크로서비스 아키텍처를 구축하는 경우 Kubernetes의 스케일링 및 오케스트레이션 능력을 활용할 수 있다.
  • Kubernetes는 멀티 클라우드 환경 또는 온프레미스와의 통합을 지원한다.(이런 상황에서 사용된다)
  • Kubernetes를 사용하면 더 높은 커스터마이제이션 및 제어 수준을 가질 수 있다.

Fargate를 사용하지 않으니 ECS를 사용할 필요가 없었고, 복잡한 배포나 확장성까지는 아직 필요가 없어 EKS도 필요하지 않았다.


2. 📌 자! 이제 도커라이징해서 CI, CD를 해보자!

  1. Dockerfile을 작성해 image를 Docker Hub에 올린다
  2. EC2 인스턴스에 Docker를 설치하고, EC2에 컨테이너를 띄운다.
  3. Github Actions을 사용해 main branch에 push가 일어날 경우, 배포 자동화를 시켜준다.

📎 2-1. DockerFile 작성

node.js 버전을 내가 로컬에서 사용하고 있던 18버전으로 해주었다.
가벼운 알파인 버전으로 이미지를 빌드해주었다!

🖇 DockerFile

# Base image
FROM node:18-alpine

# Create app directory
WORKDIR /usr/src/app

# A wildcard is used to ensure both package.json AND package-lock.json are copied
COPY package*.json ./

# Install app dependencies
RUN npm install

# Bundle app source
COPY . .

# Creates a "dist" folder with the production build
RUN npm run build

# --------------------------------------------------------------

ENV NODE_ENV=production

EXPOSE 8000

# Start the server using the production build
CMD [ "node", "dist/main.js" ]

🖇 .dockerignore

Dockerfile
.dockerignore

.git
.gitignore
.cache

*.md

📎 2-2. EC2 인스턴스 생성

EC2 인스턴스는 기본적인 설정만 해주었다. 이 글에서는 생략하겠다

🖇 ssh - i 명령어로 EC2에 접속하고 다음 명령어를 입력해주자

# root 계정으로 전환 + 현재 환경변수 사용
$ sudo su

# 패키지 버전 최신화
$ sudo apt update
$ sudo apt-get upgrade -y

📎 2-3. Github Runner(self-hosted) 생성

CI, CD를 적용하려고 하는 Github repository의
Settings > Actions > runner > New self-hosted runner를 클릭하면 가이드라인이 나온다!

나는 EC2 인스턴스를 ubuntu 환경으로 만들었기 떄문에 Linux 환경으로 설정해주었다.

🖇 Download

# Create a folder
$ mkdir actions-runner && cd actions-runner

# Download the latest runner package
$ curl -o actions-runner-linux-x64-2.311.0.tar.gz -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz

# Optional: Validate the hash
$ echo "29fc8cf2dab4c195bb147384e7e2c94cfd4d4022c793b346a6175435265aa278  actions-runner-linux-x64-2.311.0.tar.gz" | shasum -a 256 -c

# Extract the installer
$ tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz

🖇 Configure

# Create the runner and start the configuration experience

$ ./config.sh --url https://github.com/<repository주소> --token <토큰>

# Last step, run it!
$ ./run.sh
$ ./run.sh : 세션이 종료되어도 runner 유지하는 명령어


📎 2-4. Github Actions ci.yml 작성

테스트 통과 시, 배포가 되는 과정이... 정석이지만
현재 나의 회사는... 테스트 코드를 작성하기 어려운 상황이다,, 일단 주석으로 처리 해놓았다... 추후에 꼭 테코 작성할 수 있길 바라며

# 해당 워크플로우의 타이틀
name: CI Pipeline

#이벤트
# main 브랜치에 한하여 push가 일어날 경우
on:
  push:
    branches: [ "main" ]

# jobs

# 이벤트가 발생하면 해야할 작업을 명시
# 테스트, 빌드, 배포 단위로 명시 (테스트는 생략 했음)
# 각각의 job은 Runner라는 컨테이너에서 개별적으로 실행
jobs:

  # # 프로젝트 테스트 코드 With Jest
  # test:
  #   # ubuntu-latest 환경의 Runner에서 실행
  #   runs-on: ubuntu-latest

  build:

    # # build가 시작되려면 test가 완료되어야 함
    # needs: test
    runs-on: ubuntu-latest

    steps:
    - name: Checkout Source Code
      uses: actions/checkout@v3
    - name: Set up node.js 18.16.1
      uses: actions/setup-node@v3 
      with:
        node-version: 18.16.1
        cache: 'npm'
    - name: Install dependencies
      run: npm install
    # - runs: npm run test:e2e

    # 도커 Hub 로그인
    - name: Login Dockerhub
      env:
        DOCKER_USERNAME: ${{secrets.DOCKERHUB_USERNAME}}
        DOCKER_PASSWORD: ${{secrets.DOCKERHUB_PASSWORD}}
      run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD      

    # Docker 이미지 빌드
    - name: Build the Docker image
      run: docker build -t <이미지이름> .
    # 이미지 태깅
    - name: taging
      run: docker tag <이미지이름>:latest <dockerhub계정이름>/<이미지이름>:latest 
    # Dockerhub에 푸시 
    - name: Push to Dockerhub
      run: docker push <dockerhub계정이름>/<이미지이름>:latest 

📎 2-5. Github Actions cd.yml 작성

Github Runner(self-hosted) 생성 가이드 라인에 다음과 같이 나와있어
runs-on: self-hosted 로 설정해주었다!

이미지를 Docker Hub에서 Pull하고,
이전 컨테이너를 삭제한 뒤,
새로운 컨테이너를 실행한다.

name: CD Pipeline

on:
  workflow_run:
    workflows: ["CI Pipeline"]
    types:
        - completed

jobs:

  build:

    runs-on: self-hosted

    steps:
    - name: Login Dockerhub
      env:
        DOCKER_USERNAME: ${{secrets.DOCKERHUB_USERNAME}}
        DOCKER_PASSWORD: ${{secrets.DOCKERHUB_PASSWORD}}
      run: docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD      

    # Docker 이미지는 변경되지 않아야 하기 때문에, env 파일 설정은 CD 단계에서 처리
    - name: .env setting
      run: |
        touch .env
        echo "DB_HOST=${{secrets.DB_HOST}}" >> .env
        echo "DB_PORT=${{secrets.DB_PORT}}" >> .env
        echo "DB_NAME=${{secrets.DB_NAME}}" >> .env
        echo "DB_PASSWORD=${{secrets.DB_PASSWORD}}" >> .env
        echo "DB_USERNAME=${{secrets.DB_USERNAME}}" >> .env
        echo "AWS_ACCESS_KEY=${{secrets.AWS_ACCESS_KEY}}" >> .env
        echo "AWS_SECRET_KEY=${{secrets.AWS_SECRET_KEY}}" >> .env
        echo "AWS_S3_BUCKET_NAME=${{secrets.AWS_S3_BUCKET_NAME}}" >> .env
        echo "THUMBNAIL_OUTPUT_PATH=${{secrets.THUMBNAIL_OUTPUT_PATH}}" >> .env
        echo "FFMPEG_PATH=${{secrets.FFMPEG_PATH}}" >> .env
        echo "FFPROBE_PATH=${{secrets.FFPROBE_PATH}}" >> .env
        echo "JWT_SECRET=${{secrets.JWT_SECRET}}" >> .env
        echo "JWT_EXPIRES_IN=${{secrets.JWT_EXPIRES_IN}}" >> .env
        echo "KAKAO_REST_API_KEY=${{secrets.KAKAO_REST_API_KEY}}" >> .env
        echo "KAKAO_REDIRECT_URI=${{secrets.KAKAO_REDIRECT_URI}}" >> .env
        echo "CORS_ORIGIN_EC2=${{secrets.CORS_ORIGIN_EC2}}" >> .env

    - name: Pull Docker image
      run: sudo docker pull <dockerhub계정이름>/<이미지이름>:latest 
    - name: Delete Old docker container
      run: sudo docker rm -f web-api-container || true
    - name: Run Docker Container
      run: sudo docker run -d -p 8080:8080 --env-file .env --name web-api-container <dockerhub계정이름>/<이미지이름>:latest 

📎 2-6. EC2에 Docker 설치

Install Docker Engine on Ubuntu

# Add Docker's official GPG key:
$ sudo apt-get update

$ sudo apt-get install ca-certificates curl gnupg

$ sudo install -m 0755 -d /etc/apt/keyrings

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the repository to Apt sources:
$ echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

$ sudo apt-get update

$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

📌 3. 확인해봅시다!

모두 완료 되었으면,
main branch에 push를 하고! CI, CD가 잘 완료 되었는지 확인해보자!


  • EC2에 Docker Container가 잘 띄워졌는지 확인해보자!
$ sudo dockr ps -a


  • EC2 주소로 들어가 Nest.js 애플리케이션(docker container)이 잘 띄워졌는지도 확인해보쟈!

끄읏!!!!!! 😊👍


💡 트러블 슈팅

1. 사용하지 않는 이미지/컨테이너는 삭제해주기

실제로는... 한 번에 CI/CD 환경이 쨘! 하고 구축되지 않았다...

docker 이미지와 컨테이너를 몇 번이고.. 만들었는데 사용하지 않는 이미지와 컨테이너가 디스크를 차지하고 있어, 새로운 docker 이미지와 컨테이너가 생성되지 않았었다.

이럴 때는

$ docker system prune 

명령어를 입력해주면 사용하지 않는 이미지, 컨테이너들을 한 번에 삭제시킬 수 있다!

삭제 이후에 바로 docker 이미지 및 컨테이너를 생성하니 문제 없이 생성되었다!


2. Github Actions Secret (env 설정)

2-5번에는 바로 .env 설정을 해준 코드를 올려놓았지만, 처음에는 .env 설정을 따로 해주어야 하는지 몰랐다.

Github Actions로 CI/CD가 완성되었다고 떴지만, docker log를 찍어보니, 아래와 같이 env 파일을 찾지 못해 AWS S3 Bucket 연결이 되지 않고 있었다.

구글링을 해보니 Github Actions로 도커라이징을 해줄 때 .env 파일은 github repository에 코드가 올라가 있지않아, 따로 github secret 설정을 해주어야 한다는 것을 알게 되었다.

2-5번에 나와있는 코드대로 필요한 env 설정을 해주고,
secret을 등록하고 싶은 GitHub Repository로 들어가 Settings > Secrets and analysis > Actions > New repository secret 에서 하나씩 등록해주면 된다!

Name에는 env변수명을 Secret에는 값을 넣어주면 된다!


참고했던 사이드

profile
닫혀 있어서 벽인 줄 알고 있지만, 사실은 문이다.

0개의 댓글