도커를 활용한 CI/CD 파이프라인

임동·2024년 9월 15일

서론

저번 프로젝트를 진행하면서 큰 이슈가 있었다.
우리 팀은 지속적인 배포를 하지 않고 프로젝트를 작업했다.
그리고 발표 전날에 배포하니까 배포 오류 때문에 발표를 망친 기억이 있다..

그래서! 이번에 하는 개인 프로젝트는 처음부터 CI/CD 파이프라인을 설계해서 지속적인 코드 통합과 배포 자동화로 그런 문제가 없도록 하는 것이 첫번째 목표였다.

파이프라인


파이프라인은 매우 간단하게 설계하였다.

  • Code Push
  • Workflow에서 Type, Build Test
  • Docker Push
  • EC2 ssh 접속
  • Docker Pull & Run

아직 부족한 부분이 많다고 생각한다.
서버 부하와 속도를 위한 S3 사용, 도커 캐시를 활용한 빌드 속도 향상 등 더 보완할 점이 많지만 이번에는 기본적인 CI/CD가 어떻게 진행되는지 배워가는 생각으로 작성하였다.

도커 사용 이유

개인 프로젝트의 규모가 크진 않지만 실무에서 배포해야할 때는 용량이 크고, 비용 절감과 서버 부하를 줄이기 위해 꼭 사용해야할 기술이라 생각하여 도커를 사용하였다.
그리고 aws 공식 문서에서도 권장하는 방법이기도 하다.

Workflow

name: Frontend CD with Docker Hub
on:
  push:
    branches:
      - master # master에 push되었을 때 yml 실행

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18' # Node.js 버전 설정

      - name: Install dependencies
        run: npm install

      - name: Run TypeScript type check
        run: npm run type-check # 타입 체크 스크립트 실행

      - name: Run build
        run: npm run build # 빌드 스크립트 실행

      - name: Login to DockerHub
        if: success() # 타입 체크와 빌드 테스트가 완료되면 실행
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Get Env
        run: |
          touch ./.env.local
          echo "${{secrets.ENV}}" > ./.env.local

      - name: Build and Release Docker Image
        run: |
          docker build -t ${{ secrets.DOCKERHUB_REPO }} .
          docker tag ${{ secrets.DOCKERHUB_REPO }}:latest ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        id: deploy
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          script: |
            sudo docker rm -f $(docker ps -aqf "name=^${{ secrets.DOCKERHUB_REPO }}") || true
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker run -d -p 3000:3000 --name ${{ secrets.DOCKERHUB_REPO }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker image prune -f

파이프라인을 설계한 Workflow이다.
이렇게 봐서는 잘 모르겠지만 천천히 위에서부터 보면 이해하기 쉽다.

1. Workflow 실행

name: Frontend CD with Docker Hub
on:
  push:
    branches:
      - master # master에 push되었을 때 yml 실행

이 Workflow는 master 브랜치에 push 되었을 때 실행된다.
이렇게 코드를 작성한 이유는 개인 프로젝트라 PR을 작성하지 않기 때문에 간단한 push만으로 실행되게 하였다.

2. Node 설치 및 테스트

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      - name: Set up Node.js
        uses: actions/setup-node@v2
        with:
          node-version: '18' # Node.js 버전 설정

      - name: Install dependencies
        run: npm install

      - name: Run TypeScript type check
        run: npm run type-check # 타입 체크 스크립트 실행

      - name: Run build
        run: npm run build # 빌드 스크립트 실행

타입과 빌드 테스트를 위해 노드를 설치하고 실행한다.
테스트를 하지 않고 배포를 하게 된다면 배포된 애플리케이션에서 에러가 발생할 수 있기 때문에 테스트 후 다음 코드를 실행해주었다.

3. 도커 로그인 및 빌드

- name: Login to DockerHub
        if: success() # 타입 체크와 빌드 테스트가 완료되면 실행
        uses: docker/login-action@v1
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}

      - name: Get Env
        run: |
          touch ./.env.local
          echo "${{secrets.ENV}}" > ./.env.local

      - name: Build and Release Docker Image
        run: |
          docker build -t ${{ secrets.DOCKERHUB_REPO }} .
          docker tag ${{ secrets.DOCKERHUB_REPO }}:latest ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
          docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest

도커 push를 위한 로그인 action을 작성해주었다.
도커 로그인 후 env 파일을 만들고 Github에 저장되어 있는 환경변수를 가져온다.
이후 도커를 빌드할 때 Dockerfile을 사용하여 Docker 이미지를 빌드하고 Push 한다.

4. EC2 접속 및 배포

      - name: Deploy to server
        uses: appleboy/ssh-action@master
        id: deploy
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          script: |
            sudo docker rm -f $(docker ps -aqf "name=^${{ secrets.DOCKERHUB_REPO }}") || true
            sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker run -d -p 3000:3000 --name ${{ secrets.DOCKERHUB_REPO }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO }}:latest
            sudo docker image prune -f

배포를 위해서 ec2로 ssh 접속해주었다.
그리고 기존에 있던 컨테이너를 삭제 후 최신 이미지를 Pull 해주고 컨테이너를 실행하면 배포가 완료된다!


배포 자동화를 하지 않고 진행했을 때는 main 브랜치의 코드가 업데이트 될때마다 배포를 해야했고, 그렇게 되면 너무 시간이 오래 걸리는 작업이었다.

이번에 CI/CD를 설계하면서 배포까지의 시간을 없애고, 빌드와 타입 테스트까지 Workflow로 진행을 하니까 작업 속도가 빨라진다는 게 확실히 느껴졌다.

이후 시간이 생긴다면 ECS, S3 등 빠르고 가벼운 배포를 위해 다른 기술들도 적용해봐야겠다!

profile
FRONTEND DEV.

0개의 댓글