Docker와 Docker hub를 이용한 무중단 배포글을 찾는데 힘이 들어 ..
나같이 Docker와 Docker hub를 이용해서 무중단 배포를 하려는 사람들을 위해 도움을 주고자 글을 써보려고 한다..
아직 Docker초보이고..아마도 더 좋은 방법이 있을지도 모르니까! 그냥 이렇게 했구나~ 정도로만 봐주시면 좋겠다!
Travis CI - Github 연동과정, AWS EC2 생성 과정은 생략한다!
(참고로 나는 ubuntu 20.4 인스턴스를 생성했다)
빌드한 코드를 저장할 버킷을 생성하자.
AWS - S3 Bucket 에들어가서 버킷만들기 클릭
버킷 이름을 정하고 나머지 설정은 그대로 둔 채 버킷만들기를 해준다.
리전은 서울 ap-northeast-2로 설정한다
Travis CI가 S3, Code Deploy를 사용 할수 있도록 Travis CI에 AWS 계정을 부여해준다
AWS - IAM - 사용자 추가
발급받은 액세스키 ID 와 비밀 엑세스키를 저장한다.
이제는 배포에 사용할 EC2와 Code Deploy에게 역할을 부여해보자.
AWS - IAM - 액세스 관리 - 역할 - 역할만들기
EC2가 아닌 CodeDeploy를 검색해서 나오는 CodeDeploy를 선택해야한다. (이것 때문에 한참 해맸다 ㅋ)
이제 우리 EC2 인스턴스에 아까 첫번째로 만든 역할을 연결해준다
AWS - EC2 - 인스턴스 (서울)
역할을 연결해주기 전에 Code Deploy 애플리케이션을 생성 해주자
애플리케이션을 생성했다면 배포그룹을 생성해줘야 한다.
애플리케이션 - 배포그룹 생성
아까 2번째로 만든 Code Deploy역할을 연결시켜준다
EC2 인스턴스에 터미널로 접속한다.
먼저 터미널에 awscli를 설치한다.
sudo apt-get install awscli
aws cli설치 후 aws configure 설정. 아까 IAM만들때 받은.csv파일에서 aws access key, aws secret access key를 설정.
$ aws configure
AWS Access Key ID [None]:
AWS Secret Access Key [None]:
Default region name [None]:
Default output format [None]: json
CodeDeploy agent 설치
sudo apt-get update
sudo apt install ruby-full
sudo apt install wget
aws s3 cp s3://aws-codedeploy-ap-northeast-2/latest/install . --region ap-northeast-2
chmod +x ./install
CodeDeploy 실행중인지 확인
sudo service codedeploy-agent status
ec2 서버에 도커를 설치 한다.
curl -fsSL https://get.docker.com/ | sudo sh
sudo 없이 Docker를 실행 하려면 다음과 같이 입력
sudo usermod -aG docker ubuntu
docker-compose를 이용해 docker container를 띄울 것이므로 docker-compose도 linux서버에 설치해준다.
sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose version
nginx를 통해 80포트로 요청을 받아 3001, 3002 포트로 리버스 프록시를 만들어 줘야한다. (나는 ssl관련은 aws elastic load balancer로 하고 있기 때문에 nginx에서는 80포트만 listen하도록 하였다)
우선 nginx설치를 하자.
https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04
sudo apt-get update && sudo apt-get install nginx
80포트만 허용한다
sudo ufw allow 'Nginx HTTP'
sudo ufw status
nginx가 구동되고 있는지 확인
systemctl status nginx
서버블록 만들기 (dimelo라는 이름으로 만듬)
sudo vi /etc/nginx/sites-available/dimelo
upstream 원하는 이름 {
least_conn;
server 127.0.0.1:3001 weight=5 max_fails=3 fail_timeout=10s;
server 127.0.0.1:3002 weight=10 max_fails=3 fail_timeout=10s;
}
server {
server_name 서버의 도메인이나 IP;
location / {
proxy_set_header HOST $host;
proxy_pass http://원하는 이름;
}
}
80포트로 들어오는 요청을 3001, 3002포트로 로드밸런싱하도록 설정한다.
/etc/nginx/sites-enabled 와 방금 만든 서버블록 연결하기
sudo ln -fs /etc/nginx/sites-available/dimelo /etc/nginx/sites-enabled/
nginx 문법 확인 후 nginx 재시작
sudo nginx -t
sudo service nginx restart
-- backend/
|-- dist/
|-- node_modules/
|-- src/
ㄴ-- main.ts
|-- package.json
|-- Dockerfile(앞으로 만들 파일)
|-- .dockerignore(앞으로 만들 파일)
|-- Dockerfile.dev(앞으로 만들 파일)
|-- .travis.yml(앞으로 만들 파일)
|-- appspec.yml(앞으로 만들 파일)
|-- docker-compose.blue.yml(앞으로 만들 파일)
|-- docker-compose.green.yml(앞으로 만들 파일)
|-- deploy.sh(앞으로 만들 파일)
ㄴ-- execute-deploy.sh(앞으로 만들 파일)
VS Code로 돌아와 다음과 같이 파일을 만들 예정이다.
vscode에서 각자의 프로젝트에 맞게 Dockerfile
을 만들어 준다.
Dimelo프로젝트는 nest.js 프로젝트이고 PM2를 사용하여 무중단 운영한다.
그리고 alpine이미지를 사용하기 때문에 bcrypt같은 라이브러리 사용을 위해 python을 추가로 설치해주고 있다.
이미지 경량화를 위해 multi stage build를 하였다.
방법이 정확하지 않을 수 있으니 참고로만 봐주면 좋겠다 !
Dockerfile
FROM node:12-alpine3.11 As development
WORKDIR /usr/src/app
COPY package*.json ./
# pm2 설치
RUN npm install --global pm2
# bcrypt 사용을 위해 python 설치
RUN apk add --no-cache --virtual .gyp python make g++ pkgconfig pixman-dev cairo-dev pango-dev
RUN npm install
COPY . .
RUN npm run build
FROM node:12-alpine3.11 as production
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --global pm2
RUN apk add --no-cache --virtual .gyp python make g++ pkgconfig pixman-dev cairo-dev pango-dev
RUN npm install --only=production
COPY . .
COPY --from=development /usr/src/app/dist ./dist
EXPOSE 3000
CMD ["pm2-runtime", "dist/main.js"]
Docker image를 build 시킬때 node_modules은 넣고 싶지 않으므로 .dockerignore
파일을 만들어서 다음과 같이 작성해준다
.dockerignore
node_modules
도커배포환경 이미지를 빌드하기 전에 테스트를 위한 운영환경을 위한 Dockerfile.dev 파일을 만들어주었다 (*필수 아님)
Dockerfile.dev
FROM node:12-alpine3.11 As development
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install --global pm2
RUN apk add --no-cache --virtual .gyp python make g++ pkgconfig pixman-dev cairo-dev pango-dev
RUN npm install
COPY . .
CMD ["npm", "run", "start:dev"]
가장 중요한 부분이다! .travis.yml
파일을 만들어 어플을 테스트하고 Docker image를 빌드하고 travisCI는 스스로 배포를 하지 못하기 때문에 provider에 s3, codedeploy를 작성해줘야한다.
.travis.yml
language: generic
sudo: required
services:
- docker #docker 환경임을 명시
branches:
- main
before_install:
- docker build -t {docker hub아이디}/dimelo -f Dockerfile.dev . #운영버전 이미지빌드
script:
- docker run -e CI=true {docker hub아이디}/dimelo npm run test # 운영버전이미지로 테스트
after_success:
- docker build -t {docker hub아이디}/dimelo . #배포환경 이미지 빌드
- echo "$DOCKER_HUB_PASSWORD" | docker login -u "$DOCKER_HUB_ID" --password-stdin #Docker hub로그인, 추후에 travis에서 설정
- docker push {docker hub아이디}/dimelo #Docker이미지 hub에 푸쉬
before_deploy:
- zip -r dimelo * #dimelo라는 이름으로 zip파일 생성
- mkdir -p deploy #deploy폴더 생성
- mv dimelo.zip deploy/dimelo.zip #만든 zip파일을 deploy폴더로 이동
deploy:
- provider: s3
access_key_id: $AWS_ACCESS_KEY #추후에 travis에서 설정
secret_access_key: $AWS_SECRET_ACCESS_KEY #추후에 travis에서 설정
region: ap-northeast-2
bucket: dimelo-ci-cd #s3버킷 설정
skip_cleanup: true
local_dir: # deploy 디렉터리에 있는 파일을 s3로 업로드 하겠다는 의미
wait-until-deployed: true
on:
branch: main
repo: dimelo-project/backend #본인의 git repository
- provider: codedeploy
access_key_id: $AWS_ACCESS_KEY
secret_access_key: $AWS_SECRET_ACCESS_KEY
bucket: dimelo-ci-cd
key: dimelo.zip
bundle_type: zip
application: dimelo-deploy #codedeploy 애플리케이션
deployment_group: dimelo #codedeploy 배포그룹
region: ap-northeast-2
wait-until-deployed: true
on:
repo: dimelo-project/backend #본인의 git repository
branch: main
notifications:
email:
on_success: always
travis CI 사이트로 와서 github와 연동한 프로젝트에 Settings에 들어간다.
.travis.yml파일에서 설정한 변수에 대해서 값을 넣어 준다.
travis ci는 깃허브의 코드를 s3에 업로드를 하고 code Deploy에게 이벤트를 발생 시킨다. appspec.yml
파일은 CodeDeploy가 이벤트가 발생했을 때 어떻게 행동할지 정의하는 파일이다.
appspec.yml
version: 0.0
os: linux
files:
- source: /
destination: /home/ubuntu/dimelo # S3에서 가지고 온 파일을 서버에 저장할 장소
hooks:
AfterInstall: # 배포가 끝나면 실행
- location: execute-deploy.sh
timeout: 240
CodeDeploy는 s3에서 코드를 받아 EC2서버에 전달하고 직접 명령어 실행은 하지 못한다. 그렇기 때문에 루트 디렉터리에 쉘 스크립트를 만들고 appspect.yml이 해당 쉘 스크립트를 실행 하도록 해야한다.
프로젝트 루트디렉터리에 execute-deploy.sh
파일을 생성한다.
execute-deploy.sh
#!/bin/bash
cd /home/ubuntu/dimelo
sudo chmod +x ./deploy.sh
sudo ./deploy.sh > /dev/null 2> /dev/null < /dev/null &
배포 스크립트가 있는 곳으로 이동 -> 배포 스크립트를 실행할 권한 부여 -> 배포 스크립트 실행
execute-deploy.sh 스크립트가 최종 배포 스크립트를 실행 하도록 했다면 이제 진짜 최종 배포 스크립트를 만들어 보자. 이 부분을 다른 블로그와 조금 다르게 설정하였다. 프로젝트 루트에 deploy.sh
파일을 만든다.
deploy.sh
#!/bin/bash
DOCKER_APP_NAME=nest-app
EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then
echo "blue up"
docker pull {이미지 이름}
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml up -d
sleep 10
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml down
docker image prune -af #사용 하지 않는 이미지 삭제
else
echo "green up"
docker pull {이미지 이름}
docker-compose -p ${DOCKER_APP_NAME}-green -f docker-compose.green.yml up -d
sleep 10
docker-compose -p ${DOCKER_APP_NAME}-blue -f docker-compose.blue.yml down
docker image prune -af
fi
blue, green 중 어떤 container가 실행 중인지 확인하고 이에 반대대는 컨테이너를 띄우고(up) 실행 중이었던 컨테이너는 다시 종료(down)하는 스크립트이다. docker-compose를 통해 컨테이너를 실행하기 전에 docker hub에서 해당 image를 pull 받고 docker-compose를 통해 container를 실행, 종료 시키고 이전 컨테이너를 종료 시키면 이전에 사용 하던 docker image를 삭제한다.
이제 마지막이다 !! docker-compose를 통해 blue, green컨테이너를 띄울 것이기 때문에 docker-compose.blue.yml
docker-compose.green.yml
파일을 만들어 준다.
#blue
version: '3'
services:
nest-app:
image: {이미지 이름}:latest
restart: unless-stopped
ports:
- 3001:3000
env_file:
- /home/ubuntu/env/.env
#green
version: '3'
services:
nest-app:
image: {이미지 이름}:latest
restart: unless-stopped
ports:
- 3002:3000
env_file:
- /home/ubuntu/env/.env
deploy.sh 에서 Pull 받은 이미지 중 가장 최신 버전을 사용 할 것을 image에 명시한다. 그리고 컨테이너에 환경 변수를 넣어 주기 위해 우분투 서버에 환경 변수를 위한 디렉터리를 만들고 sudo mkdir env
.env 파일을 만들어 sudo vi .env
vscode의 .env 환경변수와 똑같이 입력하고 저장한다.:wq!
docker-compose env_file에다 해당 환경변수 디렉터리를 전달한다.
ports는 blue는 3001, green은 3002로 실행한다. (참고로 프로젝트 기본은 3000으로 실행된다, 각자 프로젝트에 맞게 설정하면 된다)
main 브랜치에서 git push를 하면 travis CI가 감지하고 이미지를 빌드하고 코드는 S3 -> Code Deploy -> EC2로 전달 된다.
Code Deploy에서 성공했음을 볼 수 있다.
터미널에서 docker ps를 통해 port가 성공적으로 바뀌었는지도 확인해보자!
막막했던 무중단 배포에 정말 도움을 많이 받은 글
https://velog.io/@jeff0720/Travis-CI-AWS-CodeDeploy-Docker-%EB%A1%9C-%EB%B0%B0%ED%8F%AC-%EC%9E%90%EB%8F%99%ED%99%94-%EB%B0%8F-%EB%AC%B4%EC%A4%91%EB%8B%A8-%EB%B0%B0%ED%8F%AC-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0-2