spring boot로 무중단 배포를 이용하려고 한다.
spring boot도 code deploy, elastic beanstalk 등등 무중단 배포 방법은 많다.
그중에서도 블루/그린 방법으로 docker를 이용하고 nginx도 공부할겸 nginx로 배포하려고 한다.
먼저 배포 과정은 아래와 같다.
(1) gitlab에 push
(2) 8080에 실행되고 있는 spring boot 멈춘 후 업데이트
(3) 8080 업데이트 후 실행
(4) 8081 멈춘후 업데이트
이러한 배포 방식을 블루/그린 이라고 하며
블루 컨테이너를 주로 보여주고 업데이트시에만 그린 컨테이너를 이용하는 방법이다.
그렇게 하여 기존 제공되는 서비스는 멈추지 않고 변경사항을 적용할 수 있다.
나는 업데이트 적용 후 green을 멈추지 않고 열어두어 전체 트래픽을 두곳으로 분산시키도록 설정했다.
1. docker, deploy.sh, nginx
(1) Dockerfile
FROM eclipse-temurin:17
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/app.jar"]
docker file은 위와 같이 설정했다.
나는 이 dockerfile로 이미지를 만든 후 docker-compose 를 통해 green과 blue 컨테이너를 나눠줄 것이다.
docker image를 설정할 때 처음엔 openjdk:17로 사용하려 했지만 지원을 안한다고 한다.
이 링크에서 지원하는 jdk를 찾아 사용하면 될것 같다.
(2) docker-compose.yml
version: "3.9"
services:
green:
container_name: green
image: ${DOCKER_IMAGE}
ports:
- "8080:8080"
environment:
AWS_ACCESS_KEY: ${AWS_ACCESS_KEY}
AWS_SECRET_KEY: ${AWS_SECRET_KEY}
AWS_REGION: ${AWS_REGION}
AWS_BUCKET: ${AWS_BUCKET}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
DB_URL: ${DB_URL}
FRONT_SERVER: ${FRONT_SERVER}
FRONT_TEST_SERVER: ${FRONT_TEST_SERVER}
FRONT_SERVER2: ${FRONT_SERVER2}
FRONT_TEMP: ${FRONT_TEMP}
ACTIVE_PROFILE: ${ACTIVE_PROFILE}
restart: on-failure
blue:
container_name: blue
image: ${DOCKER_IMAGE}
ports:
- "8081:8080"
environment:
AWS_ACCESS_KEY: ${AWS_ACCESS_KEY}
AWS_SECRET_KEY: ${AWS_SECRET_KEY}
AWS_REGION: ${AWS_REGION}
AWS_BUCKET: ${AWS_BUCKET}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
DB_URL: ${DB_URL}
FRONT_SERVER: ${FRONT_SERVER}
FRONT_TEST_SERVER: ${FRONT_TEST_SERVER}
FRONT_SERVER2: ${FRONT_SERVER2}
FRONT_TEMP: ${FRONT_TEMP}
ACTIVE_PROFILE: ${ACTIVE_PROFILE}
restart: on-failure
그린과 블루는 포트만 바꾸고 위에서 Dockerfile로 만든 똑같은 이미지를 사용할 것이다.
여기서 ENV 설정하는 것에 애를 먹었는데 아무 설정 안했으면 그냥 .env를 읽어온다고 한다.
docker compose convert
이 명령어를 통해 docker compose 에 환경변수가 제대로 전달되고 있는지 확인이 가능하다.
또 docker-compose는 restart를 default로 설정되어 있어 꺼지면 다시 실행한다고 한다.
그래도 실패했을때 재시작을 명시해주기위해 적어놨다.
(3) deploy.sh
#!/bin/bash
docker-compose stop green
echo "그린 이미지 받아오기"
docker-compose pull green
echo "그린 실행"
docker-compose up -d green
# health check
while [ 1 = 1 ]; do
echo "헬스 체크 중"
sleep 3
REQUEST=$(curl http://127.0.0.1:8080)
if [ -n "$REQUEST" ]; then
echo "헬스 체크 성공"
break ;
fi
done;
echo "nginx conf 파일 교체"
sudo cp ./nginx.green.conf /etc/nginx/nginx.conf
sudo nginx -s reload
docker-compose stop blue
echo "블루 이미지 받아오기"
docker-compose pull blue
echo "블루 실행"
docker-compose up -d blue
# health check
while [ 1 = 1 ]; do
echo "헬스 체크 중"
sleep 3
REQUEST=$(curl http://127.0.0.1:8081)
if [ -n "$REQUEST" ]; then
echo "헬스 체크 성공"
break ;
fi
done;
echo "nginx conf 파일 교체"
sudo cp ./nginx.default.conf /etc/nginx/nginx.conf
sudo nginx -s reload
aws ec2로 docker-compose.yml 파일과 deploy.sh 파일, nginx설정파일을 복사시키고
deploy.sh만 실행시키면 되도록 파일을 구성했다.
(4) nginx.default.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
upstream springboot {
least_conn;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://springboot;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $http_host;
}
}
include /etc/nginx/conf.d/*.conf;
}
nginx로 로드밸런싱을 구현했다.
로드밸런싱에서 사용하는 알고리즘은 크게 라운드 로빈, 최소 연결, 최소 시간연결로 3가지가 있다.
라운드 로빈
라운드 로빈 알고리즘은 순차적으로 보내는 것이다.
첫번째로 A를 보냈으면 두번째로 B, 다음은 C, 다시 다음은 A로 반복한다.
만약 한군데에서 정체가 일어나면 문제가 발생할 수 있다.
기본 설정을 하지 않으면 default값으로 라운드 로빈으로 설정된다.
최소 연결
가장 최소로 연결되어 있는 곳으로 보내는 알고리즘이다.
이는 균형있게 서버와의 연결을 조절할 수 있다.
최소 시간 연결
사용자가 최소 시간으로 접근할 수 있는 서버로 보내준다.
이 알고리즘은 NGINX PLUS만 사용할 수 있다고 한다.
세개의 알고리즘은 서로 장단점이 존재한다.
그래서 이 세개 중 랜덤으로 2개를 뽑고 그 2개중 최적의 방법을 찾는 "Power of Two Choices" 라는 알고리즘도 있다.
나는 이 중에서 최소 연결 알고리즘을 사용했고 다음엔 "power of two choices"로도 구현해볼까 한다.
upstream springboot {
least_conn;
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 127.0.0.1:8081 max_fails=3 fail_timeout=30s;
}
이 부분이 최소연결 알고리즘을 사용하겠다고 표시한 부분이다.
두개의 서버로 최소연결 알고리즘을 통해 로드밸런싱을 구현했다.
위 링크로 가면 알고리즘에 대한 더 자세한 설명을 볼 수 있다.
이제 ec2 인스턴스에 파일들을 옮기고 deploy.sh만 실행시켜 준다면 모든게 알아서 될것이다.
2. gitlab ci
stages:
- build-dev
- docker
- aws_ec2-dev
build-dev:
stage: build-dev
image: eclipse-temurin:17
before_script:
- chmod +x ./gradlew
script:
- cp $DEV_ENV_FILE ./.env
- ./gradlew clean build
artifacts:
expire_in: 1 hour
paths:
- build/libs/*.jar
- ./.env
- ./docker-compose.yml
- ./Dockerfile
- ./deploy.sh
only:
- dev
docker:
stage: docker
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u $DOCKER_USER -p $DOCKER_PW
script:
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
after_script:
- docker logout
aws_ec2-dev:
stage: aws_ec2-dev
before_script:
- "which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )"
- mkdir -p ~/.ssh
- eval $(ssh-agent -s)
- chmod 600 $AWS_PEM_DEV
- ssh-add $AWS_PEM_DEV
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
script:
- scp ./docker-compose.yml ubuntu@$DEPLOY_SERVER_DEV:/home/ubuntu/docker-compose.yml
- scp ./deploy.sh ubuntu@$DEPLOY_SERVER_DEV:/home/ubuntu/deploy.sh
- scp ./nginx.green.conf ubuntu@$DEPLOY_SERVER_DEV:/home/ubuntu/nginx.green.conf
- scp ./nginx.blue.conf ubuntu@$DEPLOY_SERVER_DEV:/home/ubuntu/nginx.blue.conf
- scp ./nginx.default.conf ubuntu@$DEPLOY_SERVER_DEV:/home/ubuntu/nginx.default.conf
- scp ./.env ubuntu@$DEPLOY_SERVER_DEV:/home/ubuntu/.env
- ssh ubuntu@$DEPLOY_SERVER_DEV "bash deploy.sh"
only:
- dev
gitlab vairable에 ENV파일을 미리 설정해줬다.
그리고 docker 레퍼지토리로 push한 후
aws로 scp를 통해 파일들을 옮겨준후 deploy.sh를 실행 시켜준다.
docker를 사용할때 따로 설정해줘야 할것이 있다.
dind 라는 docker in docker 즉, 도커안에서 도커를 실행시키는 설정을 따로 해줘야 한다.
위 링크에서 dind에 대한 설명이 잘 나와있었다.
나는 깃랩 러너 설정을 다음과 같이 했다.
sudo vi /etc/gitlab-runner/config.toml
[[runners]]
name = "My Docker Runner"
url = "url"
id = 7
token = "토큰"
token_obtained_at = 날짜
token_expires_at = 날짜
executor = "docker"
environment = ["DOCKER_DRIVER=overlay2", "DOCKER_TLS_CERTDIR="]
pre_build_script = "export DOCKER_HOST=tcp://docker:2375"
[runners.docker]
tls_verify = false
image = "docker:stable"
privileged = true
disable_entrypoint_overwrite = false
oom_kill_disable = false
disable_cache = false
volumes = ["/cache"]
shm_size = 0
다음으로 깃허브액션과 비교할때 가장 어려웠던 점은 aws로 접근 설정이다.
ssh 어쩌구 저쩌구 하는데 처음엔 하나도 이해가 안되다가 계속 찾아보니 점점 이해가 갔다.
ssh
ssh란 secure shell 의 약자로 암호화 통신 프로토콜중의 하나이다.
나는 https와 비슷하겠구나 하고 넘어갔다.
aws인스턴스에 접근하려면 ssh를 설정해줘야 한다.
내 private key를 깃랩 변수로 등록하고 그 쌍인 공개키를 aws 인스턴스에 권한키로 추가하여 접근을 허용해야 한다.
- "which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )"
- mkdir -p ~/.ssh
- eval $(ssh-agent -s)
- chmod 600 $AWS_PEM_DEV
- ssh-add $AWS_PEM_DEV
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
이 과정이 내 프라이빗키를 깃랩 러너의 키로 등록하는 과정이다.
이러면 이 깃랩러너는 aws에 접근했을때 내 로컬컴퓨터와 같은 권한을 받을 수 있다.
위 링크에 잘나와있지만 누구의 프라이빗키, 누구의 공개키인지 헷갈려서 따로 많이 찾아보긴 했다.
다시 말하지만, 깃랩 러너는 내 로컬과 같은 권한을 가지도록 해야 한다면,
내 로컬 프라이빗키를 깃랩의 변수로 등록하고 aws에 내 로컬 공개키를 authorized_key로 등록해주면 된다.
추가로 gitlab ci scp에 관한 공식문서는 아래와 같다.
나는 프라이빗키를 따로 설정하지 않고 인스턴스 생성시 주는 pem키를 사용했다.
이 pem키는 프라이빗키고 이에 대한 공개키는 인스턴스 생성시 자동으로 authorized_key 파일에 들어가 있다.
그리고 오타를 조심하자.. gitlab -ci 파일에서 오타때문에 권한 오류가 자꾸 떠서 하루를 삽질했다 ㅎㅎ..
결론
결과적으로 무중단배포를 진행하면서 nginx도 공부해보고 ssh에 대한 이해도도 올라갔다.
처음엔 어떻게 구현할지 막막했지만 막상 하다보니 어려운 개념은 아니었기 때문에 할만했던거 같다.
'개발 > Devops' 카테고리의 다른 글
[Devops] ELK 디스크 부족 이슈 S3로 해결하기 - ubuntu 22.04 (0) | 2023.11.23 |
---|---|
[Devops] ELK Stack 8.x 설치 - ubuntu 22.04 (0) | 2023.11.13 |
[Devops] react 무중단 배포 (gitlab ci, s3, cloudfront) (0) | 2023.06.19 |
[Devops] github actions로 자동 배포 설정하기 (react docker 배포) (1) | 2023.02.16 |
[Devops] github actions로 자동 배포 설정하기 (nodejs 배포) (0) | 2023.02.16 |