Webhook을 이용한 Blue-Green 배포 구현
시작하기전
GitLab webhook과 Docker Registry가 필수로 구현되어 있어야합니다.
https://jwinjection.tistory.com/286
Jenkins | GitLab webhook설정
Jenkins | GitLab webhook설정최종브렌치인 main 브렌치에 push를 하면GitLab webhook 이 push이벤트를 감지한 후업데이트 된 최신 main 브렌치를 jenkins내부로 clone 후 jar파일로 build 하는 작업을해보도록 하겠
jwinjection.tistory.com
https://jwinjection.tistory.com/288
Docker | 자체 Docker Registry 생성하기
Docker Registry 생성하기Docker Hub에서 제공하는 Private Registry는 유료입니다.무료로 사용하기 위해서는 자체적으로 Docker 이미지의 프라이빗 저장소 역할을 하는Docker Registry를 자신의 서버에 직접 설
jwinjection.tistory.com
1. 전체 구조
2. Jenkins 파이프라인
Jenkins웹에서 아래 빨간네모 부분에 적는 내용입니다.
Dash > 자신의 파이프라인 > Configuration > General 탭으로들어가 스크롤하다보면 아래쪽에 위치합니다.
파이프 라인 스크립트내용
파이프라인은 제일 마지막단계에서 다시 하도록 하겠습니다.
일단은 대강 어떤 흐름인지 쓱 봐주세요.
pipeline {
agent any
environment {
LOCAL_REGISTRY = 'localhost:5000/이미지명' // Docker 레지스트리 주소
}
stages {
stage('Git Clone'){
steps {
git branch: 'main', credentialsId: '자신의 credentialsid', url: 'gitlab git프로젝트 url.git'
sh 'chmod +x gradlew' // 클론 후 실행 권한 부여
}
post {
failure {
echo 'Repository clone failure !'
}
success {
echo 'Repository clone success !'
}
}
}
stage('Build') {
steps {
sh './gradlew build -x test'
}
}
stage('Build Docker Image') {
steps {
script {
// Docker 이미지를 빌드하여 로컬 레지스트리에 태그
sh 'docker build -t $LOCAL_REGISTRY:latest .'
}
}
}
stage('Push Docker Image') {
steps {
script {
// Docker 이미지를 로컬 레지스트리에 푸시
sh 'docker push $LOCAL_REGISTRY:latest'
}
}
post {
success {
echo 'Docker image pushed successfully!'
}
failure {
error 'Failed to push Docker image. Stopping pipeline.'
}
}
}
stage('Deploy to Server') {
steps {
script {
// SSH를 통해 호스트 서버에서 배포 스크립트 실행
sshagent(credentials: ['자신의 ssh credential명']) {
sh 'ssh -o StrictHostKeyChecking=no -v 자신의 username@주소 "sudo -n /opt/프로젝트명/deploy-script.sh"'
}
}
}
}
}
}
이번 포스트는 위 파이프라인 중간부분인 Build docker image 부터의 내용을 다룹니다.
제 webhook 포스팅을 보고 따라하시면 그 윗쪽부분까지는 구현이 다 됩니다.
아무튼
위 파이프라인 스크립트 내에서 사용되는 dockerfile이나 yml, 쉘스트립트파일 등
필요한 내용들을 싹다 만들어놓고
위 파이프라인을 저장하면 CICD구현 끝입니다.
그럼 지금부터 필요한 파일들을 먼저 작성해보겠습니다.
3. blue-green배포
배포전략은 blue green 방식을 사용하겠습니다.
먼저 호스트 리눅스 환경에서 작업을 시작합니다.
1) 디렉토리 구조
/opt/
your-project/
│
├── blue/
│ └── docker-compose.8081.yml
│
├── green/
│ └── docker-compose.8082.yml
│
├── Dockerfile
│
└── deploy-script.sh
opt 폴더로 이동합니다.
cd /opt
자신의 프로젝트를 clone합니다.
clone 프로젝트git주소.git
그리고 자신의 프로젝트 폴더안에
위의 디렉토리 구조와 같이 폴더와 파일들을 생성할 것입니다.
2) Dockerfile 작성
clone이 완료됐다면 자신의 프로젝트폴더 안으로 들어옵니다.
cd myproject
프로젝트 폴더안에서 Dockerfile 파일을 생성합니다.
vi Dockerfile
# 1. Base Image
FROM openjdk:자기버전-jdk-slim
# 2. Volume (optional, but can be useful for temporary files)
VOLUME /tmp
# 3. Copy the JAR file
COPY build/libs/jar파일이름.jar 앱이름.jar
# 4. Specify the entry point
ENTRYPOINT ["java", "-jar", "/앱이름.jar"]
도커파일 설명
1. Base Image
FROM openjdk:자기버전-jdk-slim
- FROM: Dockerfile에서 **기본 이미지(Base Image)**를 설정합니다.
- 이 예시에서는 openjdk:자기버전-jdk-slim 이미지를 사용하고 있습니다. openjdk는 Java 개발 환경을 포함한 이미지를 제공하는 공식 Docker 이미지입니다.
- 자기버전: 실제로 사용할 Java 버전(예: openjdk:11-jdk-slim, openjdk:8-jdk-slim)을 여기에 지정해야 합니다.
- -jdk-slim: "slim"은 경량화된 버전으로, 필요 없는 의존성이나 패키지를 제거하여 이미지를 더 작게 만듭니다. 따라서 Java 런타임 환경을 사용할 수 있지만, 불필요한 파일들은 제외하여 효율적인 이미지를 생성할 수 있습니다.
이 줄은 이미지의 기본 환경을 설정하며, 이후의 명령들은 이 이미지를 기준으로 실행됩니다.
2. Volume (optional, but can be useful for temporary files)
VOLUME /tmp
- VOLUME: 이 명령은 호스트와 컨테이너 간에 데이터를 공유할 수 있는 볼륨을 설정합니다.
- **/tmp**는 컨테이너의 임시 디렉토리로, 주로 임시 파일을 저장하는데 사용됩니다.
- Optional: VOLUME 명령어는 선택사항입니다. 임시 파일을 외부에서 저장할 필요가 없으면 이 명령어는 생략해도 됩니다. 하지만 컨테이너 종료 후에도 데이터를 유지해야 하는 경우 유용하게 사용될 수 있습니다.
- 예를 들어, /tmp 디렉토리에 저장된 파일들은 컨테이너가 종료되더라도 호스트의 디스크에 남아 있을 수 있습니다.
3. Copy the JAR file
COPY build/libs/jar파일이름.jar 앱이름.jar
- COPY: 이 명령은 로컬 시스템에서 컨테이너로 파일을 복사하는 역할을 합니다.
- build/libs/jar파일이름.jar: 로컬 경로에서 복사할 파일을 지정합니다. 예를 들어, build/libs/myapp.jar와 같이 build 폴더 내의 libs 디렉토리에서 빌드된 JAR 파일을 의미합니다.
- 앱이름.jar: 컨테이너 내부 경로로 복사한 파일을 지정합니다. 컨테이너에서 이 파일은 **/앱이름.jar**로 저장되어 실행됩니다.
이 줄은 애플리케이션의 JAR 파일을 컨테이너로 복사하여, 컨테이너 내에서 실행할 수 있도록 준비하는 역할을 합니다.
4. Specify the entry point
ENTRYPOINT ["java", "-jar", "/앱이름.jar"]
- ENTRYPOINT: 이 명령은 컨테이너가 시작될 때 실행할 명령을 지정합니다. 여기서는 Java 애플리케이션을 실행하도록 설정하고 있습니다.
- java: Java 명령어를 사용해 Java 애플리케이션을 실행합니다.
- -jar: -jar 옵션은 Java의 JAR 파일 실행을 의미합니다.
- /앱이름.jar: 컨테이너 내부에서 실행할 JAR 파일의 경로입니다. 앞에서 COPY 명령어로 복사된 JAR 파일을 실행하도록 설정합니다.
ENTRYPOINT는 컨테이너가 시작될 때마다 실행되는 명령이기 때문에, 이 명령어는 애플리케이션을 실행하기 위한 기본 명령어로 설정됩니다.
여기서 jar파일이름.jar 은 jenkins 컨테이너로 들어가서 아래 명령어로 확인할 수 있습니다.
plain이 붙지 않은 파일명을 사용하세요.
sudo docker exec -it jenkins /bin/bash
cd /var/jenkins_home/workspace/자신의프로젝트/build/libs
예시
FROM openjdk:17-jdk-slim
VOLUME /tmp
COPY build/libs/TryCatchBackEnd-0.0.1-SNAPSHOT.jar trycatch_backend.jar
ENTRYPOINT ["java", "-jar", "/trycatch_backend.jar"]
3) docker-compose.yml 생성
이번엔 프로젝트 폴더안에 blue랑 green 이라는 폴더를 만듭니다.
아래 명령어를 입력하면 두개의 폴더를 한번에 생성합니다.
mkdir blue green
docker-compose를 blue폴더랑 green폴더에 각각 생성합니다
blue
vi ./blue/docker-compose.8081.yml
version: '3.1'
services:
api:
image: localhost:5000/이미지명:latest # 로컬 레지스트리에서 이미지를 가져옵니다.
container_name: 컨테이너명 # 컨테이너 이름
environment:
- TZ=Asia/Seoul
- LANG=ko_KR.UTF-8
- HTTP_PORT=8081
ports:
- '8081:8080'
예시
version: '3.1'
services:
api:
image: localhost:5000/trycatch_backend:latest # 로컬 레지스트리에서 이미지를 가져옵니다.
container_name: trycatch-blue # 컨테이너 이름
environment:
- TZ=Asia/Seoul
- LANG=ko_KR.UTF-8
- HTTP_PORT=8081
ports:
- '8081:8080' # 8081 포트를 매핑
green
vi ./green/docker-compose.8081.yml
version: '3.1'
services:
api:
image: localhost:5000/이미지명:latest # 로컬 레지스트리에서 이미지를 가져옵니다.
container_name: 컨테이너명 # 컨테이너 이름
environment:
- TZ=Asia/Seoul
- LANG=ko_KR.UTF-8
- HTTP_PORT=8082
ports:
- '8082:8080'
예시
version: '3.1'
services:
api:
image: localhost:5000/trycatch_backend:latest # 로컬 레지스트리에서 이미지를 가져옵니다.
container_name: trycatch-green # 컨테이너 이름
environment:
- TZ=Asia/Seoul
- LANG=ko_KR.UTF-8
- HTTP_PORT=8082
ports:
- '8082:8080' # 8082 포트를 매핑
4) deploy-script.sh 작성
vi deploy-script.sh
#!/bin/bash
# 0. Docker Compose 파일에서 새로운 이미지를 Pull
sudo docker compose -p blue-8081 -f /opt/프로젝트폴더/blue/docker-compose.8081.yml pull
sudo docker compose -p green-8082 -f /opt/프로젝트폴더/green/docker-compose.8082.yml pull
# 1. 현재 실행 중인 컨테이너 확인
EXIST_BLUE=$(sudo docker compose -p blue-8081 -f /opt/프로젝트폴더/blue/docker-compose.8081.yml ps | grep Up)
if [ -z "$EXIST_BLUE" ]; then
echo "blue(8081) 컨테이너 실행"
sudo docker compose -p blue-8081 -f /opt/프로젝트폴더/blue/docker-compose.8081.yml up -d --force-recreate
BEFORE_COLOR="green"
AFTER_COLOR="blue"
BEFORE_PORT=8082
AFTER_PORT=8081
else
echo "green(8082) 컨테이너 실행"
sudo docker compose -p green-8082 -f /opt/프로젝트폴더/green/docker-compose.8082.yml up -d --force-recreate
BEFORE_COLOR="blue"
AFTER_COLOR="green"
BEFORE_PORT=8081
AFTER_PORT=8082
fi
echo "${AFTER_COLOR} 서버 실행 완료 (포트: ${AFTER_PORT})"
# 2. 새로운 서버의 Health Check
for cnt in `seq 1 10`;
do
echo "서버 응답 확인 중... (${cnt}/10)"
UP=$(curl -s http://127.0.0.1:${AFTER_PORT}/api/health-check)
if [ "${UP}" != "OK" ]; then
sleep 10
continue
else
break
fi
done
if [ $cnt -eq 10 ]; then
echo "서버에 문제가 발생했습니다."
exit 1
fi
# 3. NGINX 설정 파일 업데이트 및 리로드
sudo sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -s reload
echo "배포 완료!"
# 4. 이전 서버 중지
echo "${BEFORE_COLOR} 서버 중지 (포트: ${BEFORE_PORT})"
sudo docker compose -p ${BEFORE_COLOR}-${BEFORE_PORT} -f /opt/프로젝트폴더/${BEFORE_COLOR}/docker-compose.${BEFORE_PORT}.yml down
# 5. 사용하지 않는 Docker 이미지 정리
sudo docker image prune -f
sudo chmod +x /opt/프로젝트/deploy-script.sh
5) nginx 설정
nginx설치여부
아래 명령어를 통해 설치여부를 확인할 수 있으며, 설치안돼있으면 nginx를 설치해야합니다.
sudo systemctl status nginx
nginx설치
sudo apt update
sudo apt install nginx
sudo systemctl start nginx
sudo systemctl start nginx
nginx폴더로 이동
cd /etc/nginx
nginx default 파일 수정
nginx.conf파일에 nginx default 파일을 include하는 구문이 있습니다.
그래서 nginx.conf파일은 건들지 않고 default파일만 수정하면됩니다.
sudo vim /etc/nginx/sites-enabled/default
server {
listen 8080 default_server;
listen [::]:8080 default_server;
root /var/www/html;
# Add index.php to the list if you are using PHP
index index.html index.htm index.nginx-debian.html;
server_name _;
include /etc/nginx/conf.d/service-url.inc;
location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;
}
location /api {
proxy_pass $service_url;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
proxy_read_timeout 20m;
}
}
service-url.inc 파일 추가
sudo vi /etc/nginx/conf.d/service-url.inc
아래와 같이 작성합니다.
set $service_url http://127.0.0.1:8081;
이 service_url이 파라미터 역할이 되어 nginx.conf 파일의 $service_url 자리에 전달되게 됩니다.
그래서 service-rul.inc에 있는 포트 번호만 스크립트를 통해 변경함으로써 CICD가 진행되게 됩니다.
nginx 리로드
nginx를 리로드 해줍니다.
sudo nginx -s reload
6) ssh agent 설정
Jenkins웹에 접속합니다.
Dashboard > Jenkins관리 > Plugins > Available plugins에서
ssh agent 를 검색하여 설치합니다.
Dashboard > Jenkins 관리 > Credentials 에 들어가 아무 (global)이나 옆을 눌러서 Add credentials 버튼을 눌러줍니다.
Kind : SSH Username with private key를 선택합니다
Scope : Global을 선택합니다
ID : my-ssh-credentials (원하는대로 입력합니다)
Description : ssh connect (원하는대로 입력합니다)
Username : 리눅스 접속할때 사용하는 계정이름을 작성합니다.
Private Key : Add 버튼을 눌러 private key를 붙여넣습니다. private key 찾는법은 바로아래에서 알려드리겠습니다.
private key찾는법
기본적으로 SSH키는 사용자의 ~/.ssh/ 디렉토리에 저장됩니다.
개인키 파일은 보통 id_rsa 또는 id_ed25519 등의 이름을 가지고 있습니다.
cd ~/.ssh/
cat id_rsa
안에 내용을 싹다( ---- begin --- --- end ---이런것까지 싹다) 복사해서 위 private key에 붙여넣습니다.
없으면 만들면됩니다.
https://jwinjection.tistory.com/291
Ubuntu | SSH Key 접속 설정
SSH Key 접속 설정SSH를 비밀번호로 접속하게 되면미국, 멕시코, 중국쪽에서 거의 분단위로 brute force를 하기때문에 언젠간 해킹당할 위험이 있습니다.그래서 key를 통한 접속을 하면 안전합니다.
jwinjection.tistory.com
7) 파이프라인 수정
jenkins웹으로 이동하여 해당 부분을 자신의 정보에 맞게 수정합니다.
pipeline {
agent any
environment {
LOCAL_REGISTRY = 'localhost:5000/이미지명' // Docker 레지스트리 주소
}
stages {
stage('Git Clone'){
steps {
git branch: 'main', credentialsId: '자신의 credentialsid', url: 'gitlab git프로젝트 url.git'
sh 'chmod +x gradlew' // 클론 후 실행 권한 부여
}
post {
failure {
echo 'Repository clone failure !'
}
success {
echo 'Repository clone success !'
}
}
}
stage('Build') {
steps {
sh './gradlew build -x test'
}
}
stage('Build Docker Image') {
steps {
script {
// Docker 이미지를 빌드하여 로컬 레지스트리에 태그
sh 'docker build -t $LOCAL_REGISTRY:latest .'
}
}
}
stage('Push Docker Image') {
steps {
script {
// Docker 이미지를 로컬 레지스트리에 푸시
sh 'docker push $LOCAL_REGISTRY:latest'
}
}
post {
success {
echo 'Docker image pushed successfully!'
}
failure {
error 'Failed to push Docker image. Stopping pipeline.'
}
}
}
stage('Deploy to Server') {
steps {
script {
// SSH를 통해 호스트 서버에서 배포 스크립트 실행
sshagent(credentials: ['자신의 ssh credential명']) {
sh 'ssh -o StrictHostKeyChecking=no -v 자신의 username@주소 "sudo -n /opt/프로젝트명/deploy-script.sh"'
}
}
}
}
}
}
environment에 이미지 명은 3)에서 작성한 이미지명을 사용하면됩니다.
git clone stage에서 자신의 credential id는
Jenkins웹 > Dashboard > Jenkins 관리 > Credentials > System > Global credentials 에
Kind가 Username with passworld 인 행의 ID 값을 입력하시면 됩니다.
자신의 SSH credential명은
6) 에서 설정했던 부분중 ID에 해당되는 내용인데
저와 동일하게 하셨다면
my-credentials-ssh 가 되겠네요!
작성을 완료하셨다면 저장버튼을 누릅니다.
8) 테스트
지금까지의 내용들을 COMMIT 하신 후 push를 합니다.
현재 내프로젝트에 아래 파일들이 다 들어가있어야합니다.
/opt/
your-project/
│
├── blue/
│ └── docker-compose.8080.yml
│
├── green/
│ └── docker-compose.8081.yml
│
├── Dockerfile
│
└── deploy-script.sh
git status
git add .
git commit -m "cicd structure"
git push
41트만에 성공...
다했는데도 웹 접속이 안된다면
방화벽 확인
sudo firewall-cmd --zone=public --add-port=8080/tcp --permanent
sudo firewall-cmd --reload
sudo firewall-cmd --list-ports
'DevOps > 🛠️ CICD' 카테고리의 다른 글
Jenkins | GitLab webhook설정 (1) | 2024.10.01 |
---|---|
DuckDNS로 무료 도메인 등록하기 (0) | 2024.10.01 |
윈도우환경에서 nginx활용한 초간단 CICD 구현(2) (0) | 2024.09.06 |
윈도우환경에서 nginx활용한 초간단 CICD 구현(1) (1) | 2024.09.05 |
리버스 프록시란? (0) | 2024.09.05 |