๐ CICD ๋ ?
CI (Continuous Integration) : ์ง์์ ํตํฉ. ์์ฑ ๋ฐ ์์ ์ฝ๋๋ค์ด ์๋์ผ๋ก Test → Build ํ๋ ๊ณผ์ ์ ์๋ํ
CD (Continuous Deploy) : ์ง์์ ๋ฐฐํฌ. CI ์ดํ ๋ฐฐํฌ ํ๊ฒฝ๊น์ง ์ ๋ฌํด ์๋ ๋ฐฐํฌํ๋ ๊ณผ์ ์ ์๋ํ
๋ชฉ์ : Testํ์ฌ ์ ์์ ์ธ ์ฝ๋๊ฐ Deploy ๋๋ ๊ฒ์ธ์ง ํ์ธํ๋ ์ ์ฐจ๋ฅผ ๊ฑฐ์น๊ณ → Buildํ๊ณ → ghcr ์ Deploy ๋ ์ด๋ฏธ์ง๋ฅผ Github Action Runner๋ฅผ ์ ์ฉํด ์๋ ๋ฐฐํฌ๋๋๋ก ๊ตฌ์ฑํ์์ค.
๐ Dockerfile ๋ง๋ค๊ณ ํ
์คํธํด๋ณด๊ธฐ
Dockerfile ์์ฑ
# Match with my local Node Version
FROM node:16-alpine
# RUN mkdir -p /app
WORKDIR /app
# My . Dir -> /app Directory
ADD . /app/
# Dependency Install
RUN npm install
# Build
RUN npm run build
# PORT (8080) Expose
EXPOSE 8080
# START
ENTRYPOINT npm run start:prod
Dockerignore ๋ง๋ค๊ธฐ
node_modules/
dist/
Build Docker Image๋ฅผ ๋ง๋ค๊ธฐ
-t ์ด๋ฏธ์ง ํ๊ทธ๋ช
์ต์
์ผ๋ฐ์ ์ผ๋ก -t ์ด๋ฏธ์ง๋ช
์นญ:๋ฒ์ ์ผ๋ก ๋ช
์ ๋ฒ์ ๋ฏธ์ง์ ์ latest๋ก ์๋ ์ง์ ํจ.
docker build **-t my-nest** .
์ด๋ฏธ์ง ์คํ
-d ๋ฐฑ๊ทธ๋ผ์ด๋ ์คํ
-p ํฌํธํฌ์๋ฉ ํฌํธ ์ง์ (๋ก์ปฌํฌํธ : ์ปจํ
์ด๋ ํฌํธ)
-v ์ฌ์ฉ ๋ณผ๋ฅจ (์ ์ฅ์)์ค์
--network ์ฌ์ฉ ๋คํธ์ํฌ ์ค์ (host, bridge)
๋งจ๋ค์ ์ด๋ฏธ์ง ํ๊ทธ๋ช
์์ฑํด ์คํ
docker run --name my-nest-test -d -p 5000:8080 --network host my-nest
๐ Github Action
Github Token ๋ฐํ
๊ฐ์ธ Settings์์ Token ๋ฐํ ์งํ
๋จ, workflow write, delete๊ถํ์ ์ฃผ์ด์ผํ๋ค.
Actions Secret ์ค์ (Organization)
Github Repository → Settings ์์ ์ฌ์ฉํ ํ๊ฒฝ ๋ณ์ ๋ฑ๋ก ์งํ
Github Action Workflow์์ ์ฌ์ฉํ ํ๊ฒฝ๋ณ์ ๋ฑ๋ก
๋์ปค์ด๋ฏธ์ง๋ฅผ ghcr์ ์ฌ๋ฆฌ๊ธฐ
์ํ๋ ์๋ฒ ์ด๋ฏธ์ง๋ฅผ ์ ์ํด์ ghcr์ ์ฌ๋ฆฐ๋ค.
#ghcr Login & Password ์
๋ ฅํ๋ผ๋ ํ์๊ฐ ๋์ค๋๋ฐ, ํ ํฐ ์
๋ ฅ
**docker login** ghcr.io -u <USERNAME>
# ์ด๋ฏธ์ง ๋น๋
**docker build** -t ghcr.io/<๊นํ๋ธ์์ด๋>/<์ด๋ฏธ์ง๋ช
:๋ฒ์ > .
# ์ด๋ฏธ์ง ํธ์
**docker push** ghcr.io/blockmonkey1992/test:latest
Github Action ์ ์ฉ Workflow ์์ฑ
์๋ ํด๋์ ์์ฑํ๋ฉด ๊นํ๋ธ์์ Action ์ํ์ ์๋์ ์ผ๋ก ์ฐพ์์ ์คํํ๋ค.
jobs ์ ๊ฐ ๋จ์๋ ๋ณ๋ ฌ ์คํ์ ํ๋, needs: build์ ๊ฐ์ด ํ์ํด์ ๋๊ธฐ์ ์คํ์ด ๊ฐ๋ฅ
# .github/workflows/cicd.yml
name: NEST-CICD
# Config : Event
on:
push:
branches: ["main"]
# Env
env:
DOCKER_IMAGE: ghcr.io/blockchain-lighthouse/lighthouse-server
DOCKER_CONTAINER: nest-server
jobs:
# Config Test Before Build
test:
runs-on: ubuntu-20.04
steps:
- name: Checkout Source Code
uses: actions/checkout@v3
- name: Nodejs 16
uses: actions/setup-node@v3
with:
node-version: 16.19.0
cache: 'npm'
- name: Setting .env
run: |
echo "SERVER_PORT=${{ secrets.SERVER_PORT }}" >> .env
echo "DATABASE_DB=${{ secrets.DATABASE_DB }}" >> .env
echo "DATABASE_HOST=${{ secrets.DATABASE_HOST }}" >> .env
echo "DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }}" >> .env
echo "DATABASE_PORT=${{ secrets.DATABASE_PORT }}" >> .env
echo "DATABASE_USER=${{ secrets.DATABASE_USER }}" >> .env
echo "REDIS_IP=${{ secrets.REDIS_IP }}" >> .env
echo "REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}" >> .env
echo "REDIS_PORT=${{ secrets.REDIS_PORT }}" >> .env
echo "SWAGGER_ID=${{ secrets.SWAGGER_ID }}" >> .env
echo "SWAGGER_PW=${{ secrets.SWAGGER_PW }}" >> .env
cat .env
- run: npm install
- run: npm run test
# Config : Build & Push to GHCR Docker Image
build:
needs: test
runs-on: ubuntu-20.04
steps:
- name: Checkout Source Code
uses: actions/checkout@v3
- name: Set up 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.BLOCKMONKEY_TOKEN }}
- name: Build and Push
id: docker_build
uses: docker/build-push-action@v3
with:
push: true
tags: ${{ env.DOCKER_IMAGE }}:latest
# Config : Deploy To Cloud Server
deploy:
needs: build
runs-on: [self-hosted, blk]
steps:
- name: Login to ghcr
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.BLOCKMONKEY_TOKEN }}
# Docker Start! (Del Current Container & Image)
- name: Running Docker
run: |
echo "SERVER_PORT=${{ secrets.SERVER_PORT }}" >> .env
echo "DATABASE_DB=${{ secrets.DATABASE_DB }}" >> .env
echo "DATABASE_HOST=${{ secrets.DATABASE_HOST }}" >> .env
echo "DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }}" >> .env
echo "DATABASE_PORT=${{ secrets.DATABASE_PORT }}" >> .env
echo "DATABASE_USER=${{ secrets.DATABASE_USER }}" >> .env
echo "REDIS_IP=${{ secrets.REDIS_IP }}" >> .env
echo "REDIS_PASSWORD=${{ secrets.REDIS_PASSWORD }}" >> .env
echo "REDIS_PORT=${{ secrets.REDIS_PORT }}" >> .env
echo "SWAGGER_ID=${{ secrets.SWAGGER_ID }}" >> .env
echo "SWAGGER_PW=${{ secrets.SWAGGER_PW }}" >> .env
cat .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:8080 --name ${{ env.DOCKER_CONTAINER }} --restart always ${{ env.DOCKER_IMAGE }}:latest
ํธ์คํ
์๋ฒ ์ค์ ์งํ
$ sudo apt update
$ sudo apt-get install -y ca-certificates \\
curl \\
software-properties-common \\
apt-transport-https \\
gnupg \\
lsb-release
$ sudo mkdir -p /etc/apt/keyrings
$ curl -fsSL <https://download.docker.com/linux/ubuntu/gpg> | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
$ echo \\
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] <https://download.docker.com/linux/ubuntu> \\
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt install docker-ce docker-ce-cli containerd.io
$ docker version
Github Action Runner ์
ํ
Repository์์ → Settings → Actions → Runner์์ ๋ฌ๋ ์ถ๊ฐํ๊ธฐ ํ๋ฉด ๋๋ค.
ํธ์คํ
์๋ฒ ํฐ๋ฏธ๋์ ์ ์ํ์ฌ ์๋ ๋ด์ฉ์ ์งํํ๋ฉด ๋๋ค.
Network ๋ณด์ ๊ทธ๋ฃน ์ค์ ํ ์ ์ํด๋ณด๋ฉด ์ ์์ ์ผ๋ก ๋์ค๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
// Must not run with sudo ์๋ฌ ๋ง๋๋ฉด ? - ์๋ ๋ช
๋ น์ด ์น๊ณ , ๋ค์ ํด๋ณด์์ค.
export RUNNER_ALLOW_RUNASROOT="1"
Action Runner ์คํ ๋ช
๋ น์ด
์ฑ๊ณตํ๋ฉด
๐ Nginx ์ค์ ๋ฐ ์ ์ฉ
Nginx ๋?
Nginx๋ ๊ฒฝ๋ ์น ์๋ฒ.ํด๋ผ์ด์ธํธ๋ก๋ถํฐ ์์ฒญ์ ๋ฐ์์ ๋ ์์ฒญ์ ๋ง๋ ์ ์ ํ์ผ์ ์๋ตํด์ฃผ๋ HTTP Web Server๋ก ํ์ฉ๋๊ธฐ๋ ํ๊ณ , Reverse Proxy Server๋ฅผ ํ์ฉํ์ฌ WAS ์๋ฒ์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ ๋ก๋ ๋ฐธ๋ฐ์ ๋ก ํ์ฉ๋๊ธฐ๋ ํจ.
Nginx Docker File ์์ฑ
FROM nginx:latest
#RUN rm /etc/nginx/conf.d/default.conf
RUN rm /etc/nginx/conf.d/default.conf
#COPY ../../ngingx.conf to /etc/nginx/conf.d
COPY ../../nginx.conf /etc/nginx/conf.d
#EXPOSE port 80
EXPOSE 80
#nginx start
CMD ["nginx", "-g", "daemon off;"]
upstream nest { # nest ๋ผ๋ upstream ์๋ฒ๋ฅผ ์ ์
#์ผ๋ฐ์ ์ธ ํ๋ก์ ๊ตฌ์กฐ์์ ์์ฒญ์ ๋ฐ๋ ์ชฝ์ upstream, ์๋ต์ ๋ฐ๋ ์ชฝ์ downstream์ด๋ผ๊ณ ํ๋ค.
server 172.17.0.1:8080; # 8080์ ์ฐ๊ฒฐ (๋์ปคํ์ )
}
server {
listen 8080; # 8080๋ฒ ํฌํธ๋ฅผ ์ด์ด์ค๋ค. ๋ค์ด์ค๋ฉด -> ์๋ ProxyPass๋ก ์ ๋ฌ
server_name server.bclh.link; # ๋๋ฉ์ธ
location / { # "/" ๋๋ฉ์ธ์ ๋๋ฌํ๋ฉด ์๋์ proxy๋ฅผ ์ํํ๋ค.
return 301 https://$host$request_uri;
}
}
#172.17.0.1 < ์ด ์ฃผ์๋ docker0 interface ์ ์๋ํ ๋น๋ ip ์ฃผ์
#์ปจํ
์ด๋๊ฐ ์ธ๋ถ๋ก ํต์ ํ ๋๋ ๋ฌด์กฐ๊ฑด docker0interface๋ฅผ ์ง๋์ผ ํ๋ค.
#์ปจํ
์ด๋๊ฐ ์ฌ๋ฌ๊ฐ์ผ ๊ฒฝ์ฐ ๋ชจ๋ container๋ 172.17.XX.YY ๋์ญ์์ IP๋ฅผ ํ๋์ฉ ํ ๋น๋ฐ๊ฒ ๋๋ค.
Github Workflow ํ์ผ ์
๋ฐ์ดํธ
Nginx ์ค์ ๋ด์ฉ์ ์์ฃผ ๋ณ๋์ฌํญ์ด ๋ฐ์ํ์ง ์์ผ๋ฏ๋ก test๋ build ๋จ๊ณ์์ ํ์์๋ ๋ถ๋ถ์ด๋ฏ๋ก deploy ๋ถ๋ถ์๋ง ์คํํ ๋ ์ถ๊ฐํด์ค๋ค.
env:
DOCKER_NEST_IMAGE: ghcr.io/blockchain-lighthouse/lighthouse-server
DOCKER_NEST_CONTAINER: nest-server
DOCKER_NGINX_IMAGE: ghcr.io/blockchain-lighthouse/lighthouse-nginx
DOCKER_NGINX_CONTAINER: nginx-server
# Config : Deploy To My Cloud Server
deploy:
needs: build
runs-on: [self-hosted, blk]
steps:
- name: Login to ghcr
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.BLOCKMONKEY_TOKEN }}
# Docker Start! (Del Current Container & Image)
- name: Running Nginx
run: |
docker stop ${{ env.DOCKER_NGINX_CONTAINER }} && docker rm ${{ env.DOCKER_NGINX_CONTAINER }} && docker rmi ${{ env.DOCKER_NGINX_IMAGE }}:latest
docker run -d -p 80:80 --name ${{ env.DOCKER_NGINX_CONTAINER }} --restart always ${{ env.DOCKER_NGINX_IMAGE }}:latest
๐ Https ์ ์ฉํ๊ธฐ
ํธ์คํธ ์๋ฒ์ ์ง์
→ Certbot ์ค์น → ์ธ์ฆ์ ๋ฐ๊ธ
# Certbot Install (๋จ, 80๋ฒ ํฌํธ๊ฐ ๋์์ค์ด๋ฉด ์คํ๋์ง ์์ผ๋, Nginx Container๋ฅผ ์ ๊น ๊บผ์คฌ๋ค)
sudo snap install --classic certbot
# ์ธ์ฆ์ ๋ฐ๊ธ
certbot certonly --standalone
# ์ฑ๊ณต ๋ฉ์ธ์ง๋ ๋ค์๊ณผ ๊ฐ์ด ์ถ๋ ฅ๋๋ค.
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/server.bclh.link/fullchain.pem
Key is saved at: /etc/letsencrypt/live/server.bclh.link/privkey.pem
This certificate expires on 2023-06-20.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.
# ๋ฐ๊ธ ๋ ์ธ์ฆ์ ํ์ธ
cd etc/letsencrypt/live/server.bclh.link
## ๋ค์๊ณผ ๊ฐ์ ํ์ผ๋ค์ ๋ณผ ์ ์๋ค. ์์ธํ ๋ณด๋ฉด ์ฌ๊ธฐ ์๋ณธํ์ผ์ด ์๊ณ , archive์ ์ฌ๋ณธ ํ์ผ์ด ์๋ ๊ฒ์ ํ์ธํ ์ ์์.
lrwxrwxrwx 1 root root 40 Mar 22 08:04 cert.pem -> ../../archive/server.bclh.link/cert1.pem
lrwxrwxrwx 1 root root 41 Mar 22 08:04 chain.pem -> ../../archive/server.bclh.link/chain1.pem
lrwxrwxrwx 1 root root 45 Mar 22 08:04 fullchain.pem -> ../../archive/server.bclh.link/fullchain1.pem
lrwxrwxrwx 1 root root 43 Mar 22 08:04 privkey.pem -> ../../archive/server.bclh.link/privkey1.pem
๋ฐ๊ธ๋ ์ธ์ฆ์ ํ์ผ๋ค์ Nginx Docker Container์ ์ฐ๊ฒฐํ์
Docker Volumn๊ณผ ์ฐ๊ฒฐํด์ฃผ๊ธฐ ์ํด ์์์ ์์ฑํ ์ธ์ฆ์ ํ์ผ์ /cert ํด๋๋ฅผ ๋ง๋ค์ด ๋ด์์ค๋ค.
cd /
mkdir cert
sudo cp /etc/letsencrypt/archive/backend.bclh.link/fullchain1.pem /cert/fullchain1.pem
sudo cp /etc/letsencrypt/archive/backend.bclh.link/privkey1.pem /cert/privkey1.pem
Nginx Config ํ์ผ์ ์์ ํด์ค๋ค.
443์ ๋ํดํธ๋ก ๋ฆฌ์ค๋ํ๋๋ก ์์ ํ์ผ๋ฉฐ, Redirect ์ค์ , ssl certificate & key์ ๊ฒฝ๋ก๋ฅผ ์ก์์ค๋ค.
์์ ํ ํ์ผ์ ๋ค์ build → Push To Ghcr ๊ณ ๊ณ !
upstream nest {
server 172.17.0.1:8080;
}
**server {
listen 80;
server_name backend.bclh.link; # ๋๋ฉ์ธ์ผ๋ก ๋ณ๊ฒฝ
# Redirect
location / {
return 301 https://$host$request_uri;
}
}**
server {
listen 443 ssl;
server_name backend.bclh.link;
location / {
proxy_pass <http://nest>;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
**ssl_certificate /etc/nginx/cert/fullchain1.pem;
ssl_certificate_key /etc/nginx/cert/privkey1.pem;**
}
443 ํฌํธ ์ค์ ์ ๋ณด์๊ทธ๋ฃน์ ์ถ๊ฐํด์ค๋ค.
Github Action cicd.yml ํ์ผ์ Port 443 ์ถ๊ฐ & Volumn ์ค์
- name: Run Nginx
run: |
docker stop ${{ env.DOCKER_NGINX_CONTAINER }} && docker rm ${{ env.DOCKER_NGINX_CONTAINER }} && docker rmi ${{ env.DOCKER_NGINX_IMAGE }}:latest
docker run -d -p 80:80 -p 443:443 **-v /cert:/etc/nginx/cert** --name ${{ env.DOCKER_NGINX_CONTAINER }} --restart always ${{ env.DOCKER_NGINX_IMAGE }}:latest
๐ Reference