어플리케이션 개발부터 배포까지 과정을 자동화하여 효율적인 서비스 환경을 만드는 것
이 순서대로 포스팅을 진행해보도록 하겠다.
Nest.js 프로젝트를 생성해주고 API 로직과 테스트 코드를 작성해준다. 이때 테스트 코드가 모두 success로 통과해야 GitHub Actions에서 테스트 통과를 할 수 있다.
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
.dockerignore
파일을 만들어서 빌드에 필요 없는 파일이나 폴더를 지정해준다.node_modules/
dist/
docker build -t [image name] .
명령어로 도커 이미지를 빌드한 뒤 컨테이너를 띄운다. [image name]
에는 사용자가 원하는 이미지의 이름을 넣는다.
docker run --name [container name] -d -p 3002:3000 [image name]
명령어로 해당 [image name]
이미지를 사용하여 컨테이너를 백그라운드에서 실행한다. 또한, 호스트의 포트 3002와 컨테이너 포트 3000을 매핑하여 3002 포트에서 3000 포트에 액세스할 수 있게 설정해준다. [container name]
에는 사용자가 원하는 컨테이너의 이름을 넣어준다.
여기까지하면 우리는 컨테이너를 정상적으로 생성한 것이다.
깃허브에는 GitHub "Package" Registry 서비스가 있는데, 이는 깃허브에서 제공해주는 깃허프판 패키지 저장소 서비스이다. 즉, 깃허브 사용자 간의 패키지 저장, 공유, 배포 등을 할 수 있는 서비스이다.
GitHub "Container" Registry는 이 Package Registry에서 도커 이미지 관리 기능을 강화한 서비스이다.
실제 우리의 프로젝트에 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 명령어를 통해 어디서 문제가 발생하는지 파악하고 수정해주면 된다.
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 &
로 명령어를 실행해준다.
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 실행해보기
Docker가 EC2 인스턴스에 정상적으로 설치되고, GitHub Runner가 백그라운드에서 잘 실행되고 있다면, 이제 GitHub main branch에 pull request나 push가 일어날때 자동으로 배포에 반영된다.
https://github.com/docker/build-push-action/issues/761
ls -l
명령어 입력 가장 왼쪽의 -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
로 접속하니 잘 들어가졌다.