S3 Deploy , jenkins , Travis 를 사용하지 않고 Github Actions만 사용한 방식에 대한 레퍼런스가 없어, 제가 꼭 성공해서 그에 대한 기술 블로그를 작성하고 싶었는데요. 드디어 할 수 있게 되었습니다 하하
blue/green 배포 방식에 대한 이해
먼저, blue/green 배포 방식에 대한 이해가 필요합니다.
blue/green 배포 방식은 트래픽을 한번에 구버전에서 신버전으로 옮기는 방식으로 blue와 green을 나란히 구성해 두 상태로 배포 시점에 트래픽을 blue에서 green으로 일제히 전환시킵니다.
현재 blue 컨테이너 8081포트를 바라보고 있지만
green 컨테이너가 활성화 되는 동안에도 요청은 blue 컨테이너로 reverse proxy 되기에 서비스는 중단되지 않습니다.
green 컨테이너가 활성화 되면, blue 컨테이너로 보내던 요청을 green으로 향하도록 바꾸고 nginx를 reload 시켜줍니다.
따라서 nginx는 green 컨테이너 를 바라보고 reverse proxy 시켜줍니다.
이를 통해 서버가 로드되는 시간을 nginx가 reload 되는 시간 만큼으로 줄일 수 있습니다.
-> 실제 실행시켜보니 reload 되는 약 30초 동안만 api 서버가 502 gateway 에러로 사용 불가능하고, reload 끝난 후 바로 사용 가능합니다
source 디렉토리 안에는 /source/build/libs/BE-0.0.1-SNAPSHOT.jar
aws-linux2의 로컬 nginx 설정.
nginx까지 도커에 올리고 싶었지만 그건 다음을 기약하며 .. EC2 서버의 내장 nginx 이용하여 프록시 진행
nginx 설치
/etc/nginx/sites-available/nginx.conf
수정하기. 없으면 만들기
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
server_name _;
location / {
try_files $uri $uri/ =404;
}
}
sites-enabled에 symlink 만들기
cd /etc/nginx/sites-enabled
sudo ln -s /etc/nginx/sites-available/nginx.conf /etc/nginx/sites-enabled/
sudo nginx -t
sudo service nginx restart
nginx 를 설정하는 중에 잘못되었다고 생각하여 다시 처음부터 다시 설정하고 싶었다. 챗 지피티한테 물어본 결과 알려준 방법이다 우아우 체고 ..
sudo systemctl stop nginx
sudo apt-get purge nginx
sudo apt-get purge nginx nginx-common
sudo apt-get autoremove
docker와 docker-compose 의 차이
Docker는 single container를 관리하는 것
Docker-compose는 yml 파일 기반으로 multi container를 관리하는 것
docker-compose의 구성요소
각 컨테이너의 Dockerfile을 작성한다.
docker-compose.yml을 작성하고 독립된 컨테이너의 실행을 정의한다.
docker-compose up 으로 컨테이너를 개시한다.
docker-compose up
docker-compose build
docker-compose up -it
push 할 때 dockerhub에 같은 이름의 레포 있어야함.
다음은 뒤에서 실행할 entry.sh 를 해석한 내용이다.
docker-compose ps를 통해 blue가 실행중인지 확인.
실행중이 아니라면
blue up,
before-compose-color=green, after-compose-color=blue
실행중이라면
green up
before-compose-color=blue, after-compose-color=green
새로운 컨테이너 띄운 후에 서버(aws-linux2) 의 nginx.conf 파일 수정
sudo nginx reload
이전 컨테이너 종료
위의 로직이 담긴 쉘 스크립트 명령어 파일을 actions.yml에서 실행.
초반에, docker에서 nginx 설정까지 구성하는 레퍼런스를 참고하며 애를 많이 먹었다. nginx까지 도커에 올려 실행하면 좋지만, 굳이? (다음에 해보겠다) 라는 생각이 들었고 ec2 서버의 nginx를 이용해 프록시를 진행하려한다.
그렇기 위해서는
그래서 docker가 8081,8082 포트로 연결해주고,
ec2의 nginx가 실행중인 8081,8082 포트를 80으로 연결해주는것.
[ docker가 8081,8082 포트로 연결해주고, ] 이 과정은 docker-compose에서 진행하고,
[ nginx가 실행중인 8081,8082 포트를 80으로 연결해주는것. ] 은 /etc/nginx/nginx.conf 파일 변경을 통해 이루어 진다고 보면 된다.
이 일련의 과정은 모두 entry.sh에서 수행된다.
그래서 처음 시작 파일 구조에서 말한 바와 같이, /home/ec2-user에 다음 파일이 담겨있다.
# Dockerfile
FROM openjdk:11
ARG JAR_FILE=./source/build/libs/BE-0.0.1-SNAPSHOT.jar
ARG SPRING_CONFIG=./src/main/resources/application.yml
COPY ${JAR_FILE} app.jar
COPY ${SPRING_CONFIG} application.yml
ENTRYPOINT ["java","-jar","/app.jar","--spring.config.location=file:/application.yml"]
# docker-compose.blue.yml
version: '3'
services:
blue:
build:
context: .
dockerfile: Dockerfile
ports:
- "8081:8080"
# docker-compose.green.yml
version: '3'
services:
green:
build:
context: .
dockerfile: Dockerfile
ports:
- "8082:8080"
# nginx.blue.conf
events {
worker_connections 1024;
}
http {
upstream backend {
server {public IP}:8081; # blue
}
access_log /var/log/nginx/access.log;
server {
listen 80;
location / {
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;
}
}
}
# nginx.green.conf
events {
worker_connections 1024;
}
http {
upstream backend {
server {public IP}:8082; # green
}
access_log /var/log/nginx/access.log;
server {
listen 80;
location / {
proxy_pass http://localhost:8082;
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;
}
}
}
# entry.sh
DOCKER_APP_NAME=meetup
# Blue 를 기준으로 현재 떠있는 컨테이너를 체크한다.
EXIST_BLUE=$(docker-compose -p ${DOCKER_APP_NAME}-blue-1 -f docker-compose.blue.yml ps | grep Up)
# 컨테이너 스위칭
if [ -z "$EXIST_BLUE" ]; then
echo "blue up"
docker-compose -p ${DOCKER_APP_NAME}-blue-1 -f docker-compose.blue.yml up -d
BEFORE_COMPOSE_COLOR="green"
AFTER_COMPOSE_COLOR="blue"
else
echo "green up"
docker-compose -p ${DOCKER_APP_NAME}-green-1 -f docker-compose.green.yml up -d
BEFORE_COMPOSE_COLOR="blue"
AFTER_COMPOSE_COLOR="green"
fi
sleep 10
# 새로운 컨테이너가 제대로 떴는지 확인
EXIST_AFTER=$(docker-compose -p ${DOCKER_APP_NAME}-${AFTER_COMPOSE_COLOR}-1 -f docker-compose.${AFTER_COMPOSE_COLOR}.yml ps | grep Up)
if [ -n "$EXIST_AFTER" ]; then
# nginx.config를 컨테이너에 맞게 변경해주고 reload 한다
cp ./nginx.${AFTER_COMPOSE_COLOR}.conf /etc/nginx/nginx.conf
sudo nginx -s reload
# 이전 컨테이너 종료
docker-compose -p ${DOCKER_APP_NAME}-${BEFORE_COMPOSE_COLOR}-1 -f docker-compose.${BEFORE_COMPOSE_COLOR}.yml down
echo "$BEFORE_COMPOSE_COLOR down"
fi
일단 먼저 로컬에서 실행을 해봤다. ./entry.sh 를 실행하면 다음과 같다. 우아우
로컬에서 실행하려면 똑같이 프로젝트 파일 최상위에 Dockerfile, docker-compose.blue.yml .. 등 있어야한다.
이제 github actions 에서 jar 파일 build, ec2로 파일 전송, entry.sh 파일 자동 실행 되도록한다.
로컬에서 실행할 때는 ./entry.sh 했는데, github actions에선 sh /entry.sh 해야한다.
# actions.yml
name: Deploy
on:
push:
branches:
- feat/docker
jobs:
build:
runs-on: ubuntu-latest
env :
working-directory: ./
APPLICATION: ${{ secrets.APPLICATION }}
steps:
- uses: actions/checkout@v2
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
java-version: '11'
distribution: 'adopt'
- name: Cache Gradle packages
uses: actions/cache@v2
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-
- uses: actions/checkout@v2
- run: |
mkdir ./src/main/resources
cd ./src/main/resources
touch ./application.yml
echo "${{env.APPLICATION}}" > ./application.yml
- uses: actions/upload-artifact@v2
with:
name: application.yml
path: ./src/main/resources/application.yml
- name: gradlew 실행권한 주기
run: chmod +x gradlew
- name: 스프링부트 애플리케이션 빌드
run: ./gradlew clean build
- name: Login to DockerHub
run:
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
- name: **entry.sh push to ec2**
uses: appleboy/scp-action@master
with:
host: ${{ secrets.EC2_SERVER_HOST }}
username: ec2-user
key: ${{ secrets.PRIVATE_KEY }}
source: "build/libs/*.jar"
target: "source"
rm: true
- name: **entry.sh**
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.EC2_SERVER_HOST }}
username: ec2-user
key: ${{ secrets.PRIVATE_KEY }}
script: |
ls -al
chmod 777 entry.sh
sh /home/ec2-user/entry.sh
다 했고, 다 했는데. 80포트로 접근이 안됐다.
포트도 활짝 열어두었고, nginx 설정도 모두 다 해두었는데, 자꾸 연결확인, 방화벽 확인 에러가 떴다.
근데 심지어
EC2 터미널에서 curl localhost 해도 됨. curl localhost/test 해도 됐다.
Postman 에서 {public IP} GET 접속하거나 그냥 크롬으로 접근하면 안됐다. 왜 안될까 ..
여러가지 방법을 시도해보았다.
nginx.conf 파일중 upstream 서버 부분을 원래
server localhost:8082; 이렇게 해뒀었다. 이게 문제였을까? 일단 퍼블릭 IP로 변경.
upstream backend {
server {public IP}:8082; # green
}
방화벽을 부셔버렸다
지난번에도 같은 오류를 겪은 적이 있었다. 왜 내 컴퓨터는 매번 방화벽이 문제일까.
80 포트가 열려있는지 확인하는 명령어로 포트의 상태를 확인한다.
telnet localhost 80
닫혀있었다. 그래서 이번에도 방화벽 문제가 아닐까? 하고 방화벽을 부셔보았다. 생각보다 간단하다.
systemctl stop firewalld
systemctl disable firewalld
방화벽을 부시고 나니 80포트에서도 접근 가능했다. 그렇지만 이는 너무 위험하기에, 다시 방화벽을 시작하고 80포트의 방화벽만 풀어주기로한다.
systemctl start firewalld
firewall-cmd --state
firewall-cmd --add-port=80/tcp
그럼 이제 진짜 80포트에서 접근 가능하다.
nginx config 변경되는 약 30초 동안만 nginx 502 gateway 에러가 나고 nginx config 변경 직후 바로 배포되어 사용 가능하다. 최고!