배포 예제 네 가지
작성 수정
프로젝트마다 남겨 둔 배포 설정이다. 어떤 상황에서 어떤 조합을 썼는지 함께 적는다.
1. Cloud Build 로 Cloud Run 에 올리기 (Node 서비스)
커밋 해시를 이미지 태그로 써서 빌드, 푸시, 배포를 Cloud Build 가 한 번에 한다.
FROM node:20
WORKDIR /usr/src/app
# package.json 과 package-lock.json 을 먼저 복사해야 코드만 바뀔 때 npm install 을 다시 하지 않는다
COPY package*.json ./
RUN npm install --omit=dev
COPY ./*.js ./
CMD [ "npm", "start" ]
# cloudbuild.yaml
steps:
- name: 'gcr.io/cloud-builders/docker'
args: [ 'build', '-t', 'gcr.io/my-project/merge-user:$COMMIT_SHA', '.' ]
- name: 'gcr.io/cloud-builders/docker'
args: [ 'push', 'gcr.io/my-project/merge-user:$COMMIT_SHA' ]
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args: [ 'run', 'deploy', 'merge-user',
'--image', 'gcr.io/my-project/merge-user:$COMMIT_SHA',
'--region', 'us-central1', '--platform', 'managed',
'--no-cpu-throttling', '--concurrency', '1000' ]
images:
- gcr.io/my-project/merge-user:$COMMIT_SHA
dev 용 cloudbuild_dev.yaml 은 서비스 이름을 merge-user-dev 로 바꾸고 --update-env-vars NODE_ENV=development 를 더한 것이다. package.json 의 스크립트로 둘을 가른다.
"scripts": {
"start": "node index.js",
"deploy:dev": "gcloud builds submit --config cloudbuild_dev.yaml --substitutions=COMMIT_SHA=\"$(git rev-parse HEAD)\"",
"deploy:prod": "gcloud builds submit --config cloudbuild.yaml --substitutions=COMMIT_SHA=\"$(git rev-parse HEAD)\""
}
gcloud builds submit 의 옵션은 배포 환경 종류 끝에 적어 두었다.
2. nginx 로 Next.js 와 Node 를 한 포트에 묶기
프런트(Next.js, 3000)와 API(Node, 8081)를 컨테이너 하나에서 띄우고 nginx 가 8080 에서 받아 경로로 나눈다.
events {}
http {
server {
listen 8080;
# location 블록의 순서대로 요청을 맞춰 본다
# /node 로 시작하면 8081 로
location /node {
proxy_pass http://localhost:8081;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 나머지는 Next.js 로
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
이 프로젝트의 이미지는 Makefile 로 빌드해서 Artifact Registry 에 올리고 kubectl set image 로 교체했다.
3. OS 이미지 하나에 Python, Node, MongoDB 를 다 넣기 (watcher)
Node 가 처리한 결과를 Flask 화면으로 확인하는 백오피스다. 컨테이너 하나에 셋을 넣었다. 패키지 설치가 오래 걸리는 부분은 base 이미지로 떼어 두고 앱은 그 위에 얹는다.
# Dockerfile_base
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y software-properties-common curl gnupg lsb-release
# tzdata 를 KST 로
RUN echo 'tzdata tzdata/Areas select Asia' | debconf-set-selections && \
echo 'tzdata tzdata/Zones/Asia select Seoul' | debconf-set-selections && \
apt-get install -y tzdata
# Python 3.12, Node 20, MongoDB 7
RUN add-apt-repository ppa:deadsnakes/ppa && apt-get update && \
apt-get install -y python3.12 python3.12-venv python3.12-dev python3-pip
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs
RUN curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc | apt-key add - && \
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu $(lsb_release -cs)/mongodb-org/7.0 multiverse" \
| tee /etc/apt/sources.list.d/mongodb-org-7.0.list && \
apt-get update && apt-get install -y mongodb-org
RUN mkdir -p /data/db
CMD ["mongod"]
# Dockerfile
FROM base_ubuntu_python312_node20_mongodb7_240531
WORKDIR /work
COPY package.json requirements.txt /work/
RUN npm install
RUN pip install -r requirements.txt
COPY watcher.js admin_server.py run.sh /work/
COPY templates /work/templates
RUN chmod +x run.sh
CMD ["/work/run.sh"]
# run.sh — 데몬 둘을 띄우고 마지막 프로세스가 컨테이너를 붙잡는다
mongod --fork --logpath /var/log/mongodb.log
sleep 5
gunicorn --timeout 60 -D -w 4 -b 0.0.0.0:8001 --log-file /var/log/gunicorn.error.log admin_server:app
node watcher.js
배포 스크립트는 날짜 태그로 빌드하고, 기존 컨테이너를 멈춘 뒤, --network host 와 호스트 볼륨으로 새 컨테이너를 띄우고, docker logs -f 를 파일로 받아 둔다. 전체 스크립트와 Flask 쪽 설명은 Flask 관리자 서버를 Docker 한 장에 담기에 있다.
같은 폴더에 pnpm 모노레포용 2단 Dockerfile 도 있다. base 단계에서 pnpm-workspace.yaml, pnpm-lock.yaml 과 각 패키지의 package.json 만 복사해 pnpm install -r 을 하고, 앱 단계에서 소스를 복사해 빌드한다. 의존성 레이어가 소스와 분리되어 코드만 바뀌면 설치를 건너뛴다.
FROM node:18-alpine
RUN npm install -g pnpm
WORKDIR /app
COPY pnpm-workspace.yaml pnpm-lock.yaml package.json ./
RUN mkdir packages server
COPY packages/package.json packages/
COPY server/package.json server/
RUN pnpm install -r
4. GitHub Actions 에서 빌드하고 SSH 로 서버에 배포하기
release 브랜치에 푸시하면 러너가 이미지를 빌드해 보고, 서버에 SSH 로 들어가 명령을 돌린다. 서버 주소와 키는 전부 Secrets 에 둔다.
name: Production Deployment
on:
push:
branches: [ release ]
jobs:
deploy:
name: Deploy to Production
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # git pull 과 같은 역할
- name: Build Docker image locally
uses: docker/build-push-action@v5
with:
context: .
load: true
tags: junha-today:latest
- name: Execute remote SSH commands
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.REMOTE_IP }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.REMOTE_PRIVATE_KEY }}
port: ${{ secrets.REMOTE_SSH_PORT }}
script: |
# 서버에서 pull 하고 compose 로 올린다
이 워크플로가 지금 junha.kr 과 이 블로그를 배포하는 CI 의 원형이다.