전 과정이 궁금하시다면?
Gitlab, Jenkins, Docker, Docker Hub, Nginx, Blue-Green 무중단배포 CI/CD 구축 - 1
지금까지 과정이 많이 힘드셨죠?
생각지도 못한 에러도 많고잉 하지만 우리는 계속 나아가야죠?
Login > Create repository > name setting > create
현재 프로젝트 구조상 backEnd 폴더
우측 클릭하면 Dockerfile 생성할 수 있습니다
알죠? 다들 알지요?~!!
설명은 주석으로 했으니까 알아서들 참고하시고~
혹시 수동 배포가 궁금하다면?
AWS 서버 - Spring Boot 띄우기 (2-.jar)
# open jdk 21 버전의 환경을 구성
FROM openjdk:21
# tzdata 패키지 설치 및 타임존 설정
RUN ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && echo Asia/Seoul > /etc/timezone
# build가 되는 시점에 JAR_FILE이라는 변수 명에 build/libs/*.jar 선언
# build/libs - gradle로 빌드했을 때 jar 파일이 생성되는 경로
ARG JAR_FILE=build/libs/ourClass-0.0.1-SNAPSHOT.jar
# JAR_FILE을 agaproject.jar로 복사
COPY ${JAR_FILE} ourClass.jar
# 운영 및 개발에서 사용되는 환경 설정을 분리
# -Duser.timezone=Asia/Seoul JVM 옵션을 사용하여 애플리케이션 수준에서도 타임존을 설정
ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=dev", "-Duser.timezone=Asia/Seoul", "/ourClass.jar"]
JASYPT를 사용하신다면 ?
: Secret text 등록
Docker Hub 사용하신다면 ?
: Username with password 등록
EC2 Server IP도 감추고 싶다면?
: Secret text 등록
Credential 등록까지.. 또 알려줘야해?
글쓴이 양반 일 제대로 하라구...
네... 알려드리겠습니다...
Docker Hub 설정
Username : Docker Hub ID
Password : Docker Hub PW
ID : pipline Script 에서 사용할 이름 = DOCKER_USER
DOCKER Repository 설정
Username : Docker Hub nameSpace
Password : Docker Hub RepositoryName
ID : pipline Script 에서 사용할 이름 = DOCKER_REPO
Jasypt Key 설정
Secret : Key 값
ID : pipline Script 에서 사용할 이름 = JASYPT_KEY
EC2 IP 설정
Secret : 서버 주소
ID : pipline Script 에서 사용할 이름 = EC2_SERVER_IP
Dashboard > Jenkins 관리 > Plugins > Available plugins > 검색 : [ssh agent] > download
Jenkins Credentials 로 돌아가서~!!!
Kind : SSH Username with private key
Scope : Global
ID : my-ssh-credentials
Description : ssh connect
Username : ubuntu
Private Key > Enter directly Check > Add
.pem (key 내용 붙여넣기)
뭐? .pem 키 내용을 붙여넣으라고?
그게 뭔데 씹덕아?
이것도 알려드립죠..
이제 EC2로 접속해서 무중단 배포를 위한 Nginx 설정을 변경해볼까요?
거의 다 왔다!!!!!!
캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ
캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ
캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ캬캬캬캬캬ㅑㅋㅁ
글쓰는 것도 참 힘드네요..
자! 조금만 더 힘내봅시다!
# 설정 파일 열기
sudo vim /etc/nginx/sites-enabled/default
저는 /api 뒤로 들어오는 요청은 다 Back Restful API 요청으로 받으려고 이렇게 설정했어요
여기서 잠깐! 이건 알고가야지?
proxy_pass 지시어란?
주로 리버스 프록시 설정에서 사용됩니다.
proxy_pass 지시어를 사용하면 특정 요청을 다른 서버로 전달하고,
그 서버의 응답을 클라이언트에게 다시 전송할 수 있습니다.
이를 통해 요청과 응답을 중간에서 조작할 수 있으며
부하 분산, 콘텐츠 캐싱, 보안 개선 등 여러 가지 이점을 누릴 수 있습니다.
그래서?
Nginx 서버에 들어오는 /api로 시작하는 모든 요청을
$service_url 변수에 저장된 URL로 전달합니다.
여기서 $service_url은 동적으로 설정될 수 있는 변수이며,
이 변수의 값은 다른 서버의 URL이 될 수 있습니다.
예를 들어, $service_url이 http://backend1.example.com으로 설정되어 있다면,
/api로 들어온 모든 요청은 http://backend1.example.com으로 전달된다.
sudo vim /etc/nginx/conf.d/service-url.inc
set $service_url http://127.0.0.1:8080;
나중에 이 파일에 있는 port를 배포 스크립트에서 변경하면서 무중단 배포를 진행합니다.
sudo service nginx restart
sudo systemctl reload nginx
pipeline {
agent any
tools {
jdk ("jdk21")
}
stages {
stage('Git Clone'){
steps {
git branch: 'main', credentialsId: 'GitLab_Login', url: 'https://[gitlabUrl]/[Project Directory]/[ProjectName].git'
}
post {
failure {
echo 'Repository clone failure !'
}
success {
echo 'Repository clone success !'
}
}
}
stage('Build') {
steps {
//프로젝트 권한 변경
sh 'chmod +x ./backEnd/gradlew'
//프로젝트 빌드
withCredentials([string(credentialsId: 'JASYPT_KEY', variable: 'JASYPT_KEY')]) {
sh 'cd ./backEnd && ./gradlew clean build -PJASYPT_KEY=$JASYPT_KEY'
}
}
}
stage('Docker Hub Login'){
steps{
withCredentials([usernamePassword(credentialsId: 'DOCKER_USER', passwordVariable: 'DOCKER_PASSWORD', usernameVariable: 'DOCKER_USERNAME')]) {
sh 'echo "$DOCKER_PASSWORD" | docker login -u $DOCKER_USERNAME --password-stdin'
}
}
}
stage('Docker Build and Push') {
steps {
withCredentials([usernamePassword(credentialsId: 'DOCKER_HUB', passwordVariable: 'DOCKER_PROJECT', usernameVariable: 'DOCKER_REPO')]) {
sh 'cd ./backEnd && docker build -f Dockerfile -t $DOCKER_REPO/$DOCKER_PROJECT .'
sh 'cd ./backEnd && docker push $DOCKER_REPO/$DOCKER_PROJECT'
echo 'docker push Success!!'
}
echo 'docker push Success!!'
}
}
stage('Deploy') {
steps {
sshagent(credentials: ['my-ssh-credentials']) {
withCredentials([string(credentialsId: 'EC2_SERVER_IP', variable: 'IP')]) {
sh 'ssh -o StrictHostKeyChecking=no ubuntu@$IP "sudo sh deploy.sh"'
}
}
}
}
}
}
아! 그리고 pipline은 개인 프로젝트 폴더나 설정에 맞게끔 변경하셔야합니다.
이거는 제가 뭘 알려드릴 수 없어요..
Log 보고 하나하나씩 잘 해결하셔요
이 정도는 해결하셔야합니다.
JDK 21 Build 방법
아래 링크를 참조해주세요!
Jenkins JDK 21 빌드하기!
StrictHostKeyChecking=no 옵션은 SSH 클라이언트가 서버에 처음 연결할 때 서버의 호스트 키를 검사하는 과정을 건너뛰게 합니다.
StrictHostKeyChecking=no는 주로 자동화된 스크립트나 배포 프로세스에서 사용됩니다.
이러한 상황에서는 호스트 키 확인 과정이 자동화 작업을 방해할 수 있으므로,
명시적으로 이 옵션을 사용하여 스크립트가 사용자 개입 없이 원활하게 실행되도록 할 수 있습니다.
sudo vim deploy.sh
#deploy.sh
#0
# 이미지 갱신
sudo docker compose -p ulvan-8080 -f /home/ubuntu/docker-compose.ulvan8080.yml pull
sudo docker compose -p ulvan-8081 -f /home/ubuntu/docker-compose.ulvan8081.yml pull
#1
EXIST_GITCHAN=$(sudo docker compose -p ulvan-8080 -f docker-compose.ulvan8080.yml ps | grep Up)
if [ -z "$EXIST_GITCHAN" ]; then
echo "8080 컨테이너 실행"
sudo docker compose -p ulvan-8080 -f /home/ubuntu/docker-compose.ulvan8080.yml up -d --force-recreate
BEFORE_COLOR="8081"
AFTER_COLOR="8080"
BEFORE_PORT=8081
AFTER_PORT=8080
else
echo "8081 컨테이너 실행"
sudo docker compose -p ulvan-8081 -f /home/ubuntu/docker-compose.ulvan8081.yml up -d --force-recreate
BEFORE_COLOR="8080"
AFTER_COLOR="8081"
BEFORE_PORT=8080
AFTER_PORT=8081
fi
echo "${AFTER_COLOR} server up(port:${AFTER_PORT})"
# 2
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
sudo sed -i "s/${BEFORE_PORT}/${AFTER_PORT}/" /etc/nginx/conf.d/service-url.inc
sudo nginx -s reload
echo "Deploy Completed!!"
# 4
echo "$BEFORE_COLOR server down(port:${BEFORE_PORT})"
sudo docker compose -p ulvan-${BEFORE_COLOR} -f docker-compose.ulvan${BEFORE_COLOR}.yml down
# 5
sudo docker image prune -f
---
!!!!!!!필수 참고!!!!!
#2 부분에 curl -s http://127.0.0.1:${AFTER_PORT}/api/health-check 라인 때문에
/api/health-check 엔드포인트에 해당하는 API 를 설계해야함
반환은 if [ "${UP}" != "OK" ]; then 명령어 때문에 OK 를 반환하면 된다.@RestController public class HealthCheckController { @GetMapping("/health-check") public String healthCheck() { return "OK"; } }
sudo vim docker-compose.ulvan8080.yml
version: '3.1'
services:
api:
image: [docker hub namespace]/[docker hub repositoryName:latest]
container_name: ulvan-8080
environment:
- TZ=Asia/Seoul
- LANG=ko_KR.UTF-8
- HTTP_PORT=8080
- jasypt.encryptor.key=[KEY VALUE]
ports:
- '8080:8080'
sudo vim docker-compose.ulvan8081.yml
version: '3.1'
services:
api:
image: [docker hub namespace]/[docker hub repositoryName:latest]
container_name: ulvan-8081
environment:
- TZ=Asia/Seoul
- LANG=ko_KR.UTF-8
- HTTP_PORT=8081
- jasypt.encryptor.key=[KEY VALUE]
ports:
- '8081:8080'
참고로 JASYPT_KEY 설정은 아래 링크를 참조해주세요!
JASYPT를 이용하여 application.yml 파일 암호화 하기
엄청난 희열...
긴글 읽어주셔서 감사합니다!
우리 모두 이제 인프라 고수입니다^^
TIP : 메타모스트 채널 알림 연동은 아래 링크를 참조해주세요!
Jenkins Mattermost 채널 알림 연동하기
ㅋㅋㅋ 김연아 짤 너무 웃기네요