
저번 프로젝트를 진행하면서 큰 이슈가 있었다.
우리 팀은 지속적인 배포를 하지 않고 프로젝트를 작업했다.
그리고 발표 전날에 배포하니까 배포 오류 때문에 발표를 망친 기억이 있다..
그래서! 이번에 하는 개인 프로젝트는 처음부터 CI/CD 파이프라인을 설계해서 지속적인 코드 통합과 배포 자동화로 그런 문제가 없도록 하는 것이 첫번째 목표였다.

파이프라인은 매우 간단하게 설계하였다.
아직 부족한 부분이 많다고 생각한다.
서버 부하와 속도를 위한 S3 사용, 도커 캐시를 활용한 빌드 속도 향상 등 더 보완할 점이 많지만 이번에는 기본적인 CI/CD가 어떻게 진행되는지 배워가는 생각으로 작성하였다.
개인 프로젝트의 규모가 크진 않지만 실무에서 배포해야할 때는 용량이 크고, 비용 절감과 서버 부하를 줄이기 위해 꼭 사용해야할 기술이라 생각하여 도커를 사용하였다.
그리고 aws 공식 문서에서도 권장하는 방법이기도 하다.

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이다.
이렇게 봐서는 잘 모르겠지만 천천히 위에서부터 보면 이해하기 쉽다.
name: Frontend CD with Docker Hub
on:
push:
branches:
- master # master에 push되었을 때 yml 실행
이 Workflow는 master 브랜치에 push 되었을 때 실행된다.
이렇게 코드를 작성한 이유는 개인 프로젝트라 PR을 작성하지 않기 때문에 간단한 push만으로 실행되게 하였다.
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
도커 push를 위한 로그인 action을 작성해주었다.
도커 로그인 후 env 파일을 만들고 Github에 저장되어 있는 환경변수를 가져온다.
이후 도커를 빌드할 때 Dockerfile을 사용하여 Docker 이미지를 빌드하고 Push 한다.
- 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 해주고 컨테이너를 실행하면 배포가 완료된다!
이번에 CI/CD를 설계하면서 배포까지의 시간을 없애고, 빌드와 타입 테스트까지 Workflow로 진행을 하니까 작업 속도가 빨라진다는 게 확실히 느껴졌다.
이후 시간이 생긴다면 ECS, S3 등 빠르고 가벼운 배포를 위해 다른 기술들도 적용해봐야겠다!