https://velog.io/@minjae98/TIL-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC-ap1dw3zk
문제: 개인 프로젝트를 하는데 Docker와 CI/CD를 이용해 자동화 배포를 했다. 그런데 서버를 최신화 하는 코드를 PUSH하면 DOWNTIME이라는 현상이 발생했다.
DOWNTIME이란?
services:
app_blue:
container_name: app_blue
build:
context: ./
dockerfile: Dockerfile
image: minjae981002/blog-api-app:latest
ports:
- '3000:3000'
env_file:
- .env
depends_on:
- db
networks:
- travelplan_blog-net
db:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
ports:
- '3306:3306'
networks:
- travelplan_blog-net
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt/live/xn--9r2b17b.shop:/etc/letsencrypt/live/xn--9r2b17b.shop
- /etc/letsencrypt/archive/xn--9r2b17b.shop:/etc/letsencrypt/archive/xn--9r2b17b.shop
- ./certs:/etc/letsencrypt
ports:
- '8080:80'
- '8443:443'
depends_on:
- app_blue # 초기에는 blue 컨테이너를 사용
networks:
- travelplan_blog-net
networks:
travelplan_blog-net:
driver: bridge
services:
app_green:
container_name: app_green
build:
context: ./
dockerfile: Dockerfile
image: minjae981002/blog-api-app:latest
ports:
- '3001:3000'
env_file:
- .env
depends_on:
- db
networks:
- travelplan_blog-net
db:
image: mysql:5.7
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
ports:
- '3306:3306'
networks:
- travelplan_blog-net
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/conf.d/default.conf
- /etc/letsencrypt/live/xn--9r2b17b.shop:/etc/letsencrypt/live/xn--9r2b17b.shop
- /etc/letsencrypt/archive/xn--9r2b17b.shop:/etc/letsencrypt/archive/xn--9r2b17b.shop
- ./certs:/etc/letsencrypt
ports:
- '8080:80'
- '8443:443'
depends_on:
- app_green # 초기에는 green 컨테이너를 사용
networks:
- travelplan_blog-net
networks:
travelplan_blog-net:
driver: bridge
위 두 파일의 차이점은 port와 container 명이 다를 뿐이다. 또한 네트워크 안에서 컨테이너를 읽을 수 있으므로 네트워크 설정이 필수이다.
1-1. 도커 컨테이너를 실행해준다.
sudo docker-compose -f docker-compose.blue.yml up -d
위 명령어를 통해 실행할 수 있다.
sudo nano /etc/nginx/sites-enabled/default
명령어를 입력하여 수정한다.upstream app {
server app_blue:3000; # Docker container name
server app_green:3001;
}
server {
listen 80;
server_name xn--9r2b17b.shop www.xn--9r2b17b.shop;
location / {
return 301 https://$host$request_uri; # HTTP 요청을 HTTPS로 리다이렉트
}
}
server {
listen 443 ssl;
server_name xn--9r2b17b.shop www.xn--9r2b17b.shop;
ssl_certificate /etc/letsencrypt/live/xn--9r2b17b.shop/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/xn--9r2b17b.shop/privkey.pem;
location / {
proxy_pass http://app; # Nest.js가 Docker에서 실행 중인 서비스
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
위 설정을 통해 Docker 네트워크 안에 있는 컨테이너와 nginx는 상호작용을 할 수 있다.
2-1. nginx 애플리케이션 간의 통신 설정이 필요하다.
sudo nano /etc/hosts
명령어를 통해 설정해준다.
127.0.0.1 app_blue;
127.0.0.1 app_green;
Docker Container name을 정해준다.
nginx 설정을 완료 했으면 테스트를 하고 재실행을 한다.
sudo nginx -t
실행 후 successful 나와야함
sudo systemctl restart nginx
재실행
sudo systemctl status nginx
재실행 후 명령어를 통해 nginx 확인이 가능하다.
CI/CD 파일을 수정해준다.
- name: Deploy to EC2
run: |
ssh -i keypair.pem -o StrictHostKeyChecking=no ubuntu@${{ secrets.EC2_IP }} "
# Docker Hub 로그인
sudo docker login -u '${{ secrets.DOCKER_HUB_USERNAME }}' -p '${{ secrets.DOCKER_HUB_PASSWORD }}' &&
echo 'Pulling the latest Docker image...' &&
sudo docker pull ${{ secrets.DOCKER_HUB_USERNAME }}/blog-api-app:latest &&
# 사용하지 않는 Docker 객체 삭제
echo 'Cleaning up unused Docker images and volumes...' &&
sudo docker system prune -a --volumes -f &&
# 현재 app_blue가 실행 중일 때 app_green 시작
if [ \$(sudo docker ps -q --filter 'name=app_blue') ]; then
echo 'Starting new container app_green...' &&
sudo docker-compose -f /home/ubuntu/TravelPlan/docker-compose.green.yml up -d &&
echo 'Container app_green started.' &&
echo 'Modifying Nginx to point to app_green...' &&
sudo sed -i 's/proxy_pass http:\/\/app_blue:3000;/proxy_pass http:\/\/app_green:3001;/' /etc/nginx/nginx.conf &&
sudo sed -i 's/proxy_pass http:\/\/app_blue:3000;/proxy_pass http:\/\/app_green:3001;/' /etc/nginx/sites-enabled/default &&
sudo nginx -t &&
sudo systemctl reload nginx &&
echo 'Old container app_blue stopped and removed.' &&
sudo docker stop app_blue &&
sudo docker rm app_blue
else
echo 'Starting new container app_blue...' &&
sudo docker-compose -f /home/ubuntu/TravelPlan/docker-compose.blue.yml up -d &&
echo 'Container app_blue started.' &&
echo 'Modifying Nginx to point to app_blue...' &&
sudo sed -i 's/proxy_pass http:\/\/app_green:3001;/proxy_pass http:\/\/app_blue:3000;/' /etc/nginx/nginx.conf &&
sudo sed -i 's/proxy_pass http:\/\/app_green:3001;/proxy_pass http:\/\/app_blue:3000;/' /etc/nginx/sites-enabled/default &&
sudo nginx -t &&
sudo systemctl reload nginx &&
echo 'Old container app_green stopped and removed.' &&
sudo docker stop app_green &&
sudo docker rm app_green
fi
"
배포 단계에서 컨테이너가 app_blue로 실행 중이라면 app_green을 실행하고 app_blue를 종료 하고 app_green을 실행시키는 프로세스이다. 이렇게 되면 새로운 버전이 app_green으로 실행되고 app_blue는 삭제하게 된다.
며칠 밤을 새며 무중단 배포를 구현하고 싶었는데 이렇게 성공해서 뿌듯하고 기분이 좋았다.
자주 사용하는 명령어를 정리해두겠다..
네트워크에 container들이 잘 있는지 확인할 때
docker network ls
docker network inspect
docker-compose.yml 파일을 실행하고 종료할 때
docker-compose.yml
docker-compose up -d
docker-compose down
docker stop docker
모든 도커 컨테이너를 삭제할 때
docker stop $(docker ps -q)
docker rm $(docker ps -a -q)
docker rmi $(docker images -q)
docker network rm $(docker network ls -q)
모든 자원을 삭제할 때
docker system prune -a --volumes
docker-compose.blue.yml 버전을 실행할 때
sudo docker-compose -f docker-compose.blue.yml up -d
nginx 컨테이너 상호작용을 위해
sudo nano /etc/hosts
nginx 파일 수정할 때
sudo nano /etc/nginx/sites-enabled/default
sudo nano nginx.conf
container 로그 확인할 때
docker logs app_blue
nginx 파일 재실행하거나 테스트 할때
sudo nginx -t
sudo systemctl status nginx
sudo systemctl restart nginx
중복된 포트 삭제할 때
sudo kill pid
sudo lsof -i :8080
sudo lsof -i :8443