https://whitem4rk.tistory.com/62
저번 포스트에서 nginx + github actions만을 사용해서 ec2에서 직접 서버를 실행하는 방식에 대해서 설명했습니다. 이번 포스트에서는 각 서버들을 docker 컨테이너로 관리하고 실행하고자 합니다. 여러개의 컨테이너를 서로 연결하려니 생각보다 어려운점이 많았습니다. 최대한 기억을 되살려서 설명해보겠습니다.
도커에 대해 잘 모르시는 분들은 이 포스트를 보시면 좋습니다.
https://whitem4rk.tistory.com/46#%C2%A0-1
1. 환경
아무래도 쉘 스크립트로 동작하므로 환경이 모르면 따라하기 힘드실거 같아 남깁니다.
ec2, spring resource server prod1(8080), prod2(8081), dev1(8082), dev2(8083), spring cloud config server(8888), mariadb(3306)는 일단은 host에서 관리하도록 했습니다.
dev, prod 둘은 config server 컨테이너와 host서버 db에 연결되어야 했습니다.
Nginx를 사용해서 실행중인 서버의 포트에 매핑해주었습니다.
dev 코드만 올릴 예정입니다.
2. github actions
name: deploy-dev
on:
push:
branches: [ master ]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'adopt'
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build -x test
- name: copy file via ssh
uses: appleboy/scp-action@master
with:
host: ${{ secrets.IBAS_DEV_HOST }}
username: ${{ secrets.IBAS_DEV_USERNAME }}
key: ${{ secrets.IBAS_DEV_SSH_KEY }}
passphrase: ${{ secrets.IBAS_DEV_PASSWORD }}
# port: ${{ secrets.PORT }} # default : 22
source: "docker-compose.yml"
target: ${{ secrets.IBAS_DEV_DEPLOY_PATH }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_STORAGE }}:dev
- name: execute deploy shell script via ssh
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.IBAS_DEV_HOST }}
username: ${{ secrets.IBAS_DEV_USERNAME }}
key: ${{ secrets.IBAS_DEV_SSH_KEY }}
passphrase: ${{ secrets.IBAS_DEV_PASSWORD }}
# port: ${{ secrets.PORT }} # default : 22
script: |
bash ${{ secrets.IBAS_DEV_DEPLOY_PATH }}/deploy.sh
이전 버전과 다른 부분은 크게 없습니다.
1. jar파일을 scp로 넘겨서 java -jar을 하는 방식에서 이제는 docker image를 build하고 docker hub에 저장해서 ec2에서는 docker pull로 이미지를 가져오도록 하였습니다.
2. 배포 경로를 따로 사용할 예정이라 docker-compose.yml를 scp를 사용하여 넘겨주었습니다.
Build and push 부분에서 context: . 를 꼭 넣어주세요.
The git reference will be based on the event that triggered your workflow and will result in the following context: https://github.com/<owner>/<repo>.git#<ref>.
Be careful because any file mutation in the steps that precede the build step will be ignored, including processing of the .dockerignore file since the context is based on the Git reference.
3. docker-compose.yml
version: '3'
services:
config:
image: 이미지 name
container_name: backend-cloud-config
networks:
ibas-network:
ipv4_address: 내부 네트워크 ip
ports:
- "8888:8888"
entrypoint: [ "java", "-jar", "app.jar" ]
dev1:
image: 이미지 name
container_name: backend-dev
networks:
ibas-network:
ipv4_address: 내부 네트워크 ip
ports:
- "8082:8082"
environment:
- SPRING_PROFILES_ACTIVE=dev1
entrypoint: ["java", "-jar", "app.jar", "--spring.profiles.active=dev1"]
dev2:
image: 이미지 name
container_name: backend-dev2
networks:
ibas-network:
ipv4_address: 내부 네트워크 ip
ports:
- "8083:8083"
environment:
- SPRING_PROFILES_ACTIVE=dev2
entrypoint: ["java", "-jar", "app.jar", "--spring.profiles.active=dev2"]
networks:
ibas-network:
external: true
1. 우선 docker network를 생성해주어야 합니다. docker network 설정을 따로 해주지 않으면 bridge 방식을 default로 설정해줍니다. 이 network에 컨테이너들을 넣어서 서로 내부적으로 통신을 할 수 있도록하며 gateway를 통해서 host와도 통신이 가능합니다. 대충 공유기를 하나 추가해서 그 공유기에 컴퓨터를 연결한다고 보시면 됩니다.
docker network create --subnet=(x.x.x.0/16) --gateway=(x.x.x.1) ibas-network
gateway를 따로 설정해주지 않아도 .1 로 설정되는걸로 알고있어서 설정을 따로 안해줬는데 host랑 통신이 안됐습니다... 여러가지 바꾸면서 gateway값도 설정해줬는데 되더라고요. 이것 때문에 된건지는 모르겠지만 일단 안정빵으로 설정해두는게 좋을 것 같습니다.
2. 이제 network안에 컨테이너를 넣어야합니다. docker 명령어로 직접 값을 넘겨서 컨테이너를 띄울 순 있지만 너무나도 번거러운 작업입니다. 여러 컨테이너도 간편하게 관리하도록 docker-compose를 사용하였습니다. 특별한 부분은 없고 ip, port만 잘 매핑해주면 됩니다.
4. config 파일 수정
datasource:
url: jdbc:mariadb://localhost:3306/DB?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
driver-class-name: org.mariadb.jdbc.Driver
username: name
password: pw
기본적인 spring application.yml의 경우 대부분 이렇게 설정해서 많이 사용합니다. 하지만 컨테이너 입장에서 host서버는 더 이상 자신의 localhost가 아닙니다. 따라서 이제는 gateway ip를 넣어줘야합니다. 위에서 설정한 x.x.x.1로 바꿔주시면 됩니다.
5. 방화벽 설정
이 문제 때문에 상당히 헤맸습니다. 기존에 되어있던 설정은 ssh로만 접근하도록 해서 DB에 대한 접근 권한을 localhost로 설정하였고 3306 port를 방화벽으로 막아놨었습니다. 이를 모르고 연결시도를 계속 해서 permission denied 만...
[mysqld] bind-address = 0.0.0.0
일단 외부 접근을 허용하고
GRANT ALL PRIVILEGES ON *.* TO 'user'@'% 또는 docker0 ip';
FLUSH PRIVILEGES;
DB에 접근 권한이 있는 사용자를 추가해줍니다.
6. deploy.sh
#!bin/bash
BASE_PATH=접속 user 경로
DEPLOY_PATH=$BASE_PATH/deploy
# docker hub에서 이미지 가져오기
DOCKERHUB_PATH=이미지 이름
docker pull $DOCKERHUB_PATH
echo "> 현재 구동중인 Set 확인"
RESPONSE=$(curl -s http://localhost:8082/api/dev/profiles)
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
CURRENT_PROFILE=$RESPONSE
else
RESPONSE=$(curl -s http://localhost:8083/api/dev/profiles)
if [ $? -eq 0 ] && [ -n "$RESPONSE" ]; then
CURRENT_PROFILE=$RESPONSE
else
CURRENT_PROFILE="NOT WORKING"
fi
fi
echo "> $CURRENT_PROFILE"
# 쉬고 있는 set 찾기: set1이 사용중이면 set2가 쉬고 있고, 반대면 set1이 쉬고 있음
if [ $CURRENT_PROFILE == dev1 ]; then
CURRENT_CONTAINER_NAME=backend-dev1
IDLE_CONTAINER_NAME=backend-dev2
IDLE_PROFILE=dev2
IDLE_PORT=8083
elif [ $CURRENT_PROFILE == dev2 ]; then
CURRENT_CONTAINER_NAME=backend-dev2
IDLE_CONTAINER_NAME=backend-dev1
IDLE_PROFILE=dev1
IDLE_PORT=8082
else
echo "> 일치하는 Profile이 없습니다. Profile: $CURRENT_PROFILE"
echo "> dev1을 할당합니다. IDLE_PROFILE: dev1"
CURRENT_CONTAINER_NAME=backend-dev2
IDLE_CONTAINER_NAME=backend-dev1
IDLE_PROFILE=dev1
IDLE_PORT=8082
fi
echo "> $IDLE_CONTAINER_NAME 를 가진 container가 있다면 종료"
if docker ps -a | grep -q $IDLE_CONTAINER_NAME; then
echo "> 실행중인 $IDLE_CONTAINER_NAME container가 발견되어 종료합니다."
docker stop $IDLE_CONTAINER_NAME
sleep 5
docker rm $IDLE_CONTAINER_NAME
sleep 5
else
echo "> 현재 구동중인 $IDLE_CONTAINER_NAME container가 없으므로 종료하지 않습니다."
fi
echo "> $IDLE_CONTAINER_NAME container 실행"
docker-compose -f ${DEPLOY_PATH}/docker-compose.yml up -d ${IDLE_PROFILE} > ${DEPLOY_PATH}/temp/${IDLE_PROFILE}.log 2>&1
echo "> $IDLE_PROFILE 10초 후 Health check 시작"
echo "> curl -s http://localhost:$IDLE_PORT/api/actuator/health "
sleep 10
WEBHOOK_URL=웹훅주소
SUCCESS_MESSAGE="${IDLE_PROFILE} (PORT:${IDLE_PORT}) DEPLOYMENT SUCCESS!"
FAILURE_MESSAGE="${IDLE_PROFILE} (PORT:${IDLE_PORT}) DEPLOYMENT FAILED..."
for retry_count in {1..10}; do
response=$(curl -s http://localhost:$IDLE_PORT/api/actuator/health)
up_count=$(echo $response | grep 'UP' | wc -l)
if [ $up_count -ge 1 ]; then # $up_count >= 1 ("UP" 문자열이 있는지 검증)
echo "> Health check 성공"
echo "set \$dev_service_url http://127.0.0.1:${IDLE_PORT};" | sudo tee /etc/nginx/conf.d/dev_service_url.inc
PROXY_PORT=$(curl -s http://localhost:$IDLE_PORT/api/dev/profiles)
echo "> nginx current proxy port: $PROXY_PORT"
echo "> nginx reload"
sudo service nginx reload
break
else
echo "> Health check의 응답을 알 수 없거나 혹은 status가 UP이 아닙니다."
echo "> Health check: ${response}"
fi
if [ $retry_count -eq 10 ]; then
echo "> Health check 실패."
echo "> Nginx에 연결하지 않고 배포를 종료합니다."
PAYLOAD=$(cat <<EOF
{
"content": "$FAILURE_MESSAGE"
}
EOF
)
curl -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL"
exit 1
fi
echo "> Health check 연결 실패. 재시도..."
sleep 10
done
echo "> $CURRENT_CONTAINER_NAME 를 가진 container가 있다면 종료"
if docker ps -a | grep -q $CURRENT_CONTAINER_NAME; then
echo "> 실행중인 $CURRENT_CONTAINER_NAME container가 발견되어 종료합니다."
docker stop $CURRENT_CONTAINER_NAME
sleep 5
docker rm $CURRENT_CONTAINER_NAME
sleep 5
else
echo "> 현재 구동중인 $CURRENT_CONTAINER_NAME container가 없으므로 종료하지 않습니다."
fi
PAYLOAD=$(cat <<EOF
{
"content": "$SUCCESS_MESSAGE"
}
EOF
)
curl -H "Content-Type: application/json" -d "$PAYLOAD" "$WEBHOOK_URL"
docker rmi $(docker images -f "dangling=true" -q) --force
exit 0
마지막 배포과정을 수행할 쉘 스크립트입니다. 이 또한 이전 버전과는 크게 차이가 없고 docker 명령어가 대체되었습니다.
1. docker pull로 이미지 가져오기
2. 실행중이지 않은 profile을 확인
3. profile을 가진 container가 실행중이라면 중지, 삭제
4. 서버 실행
5. 잘 켜졌다면 nginx port 수정, reload
6. 이전 컨테이너 중지 삭제
접속중인 계정이 docker를 실행할 권한이 없다면 권한설정 해주셔야합니다.
7. nginx 설정
nginx는 저번 버전과 동일합니다.
이걸 완벽하게 똑같이 따라하지 않는 이상 에러는 피할 수 없다고 생각합니다 제가 생겼던 모든 에러를 적은것도 아니라서... docker logs나 github actions 에러를 잘 보고 수정해서 사용하셔야 할거에요. 배포는 일단 여기까지 하고 다음 포스트부터는 모니터링, 부하테스트 쪽으로 넘어가려합니다.
'Back-end > IBAS-spring-project' 카테고리의 다른 글
Blue/Green 배포 (Nginx + github actions) without Docker (0) | 2024.06.17 |
---|---|
무중단 배포 전략 (Continuous Deployment) (1) | 2024.06.14 |
JPA 연관관계 (일대일, 일대다, 다대일, 다대다) (0) | 2023.08.25 |
프로세스, 그리고 Docker와 VM (0) | 2023.08.06 |
RESTful API (0) | 2023.07.27 |