2023.03.24 - [Spring] - [Spring] MSA 프로젝트 만들기 (3)
지금까지 MSA 구조로 프로젝트를 만들고 Spring Cloud Gateway에서 JWT 토큰의 유효성 검사 처리와 CORS 설정 그리고 요청을 처리할 수 있는 마이크로 서비스로 로드밸런싱 하는 것을 알아보았고 이전 글에서는 RestTemplate과 FeignClient를 사용하여 마이크로 서비스들간에 내부통신하는 방법까지 알아보았습니다. 이번 글에서는 각각의 마이크로 서비스에서 도커파일을 생성하고 젠킨스와 도커를 사용하여 CI-CD 파이프라인을 구축하는 방법과 배포할 때 작성했던 스크립트 코드를 보면서 특정 브랜치에 푸쉬 했을때 변경사항이 있는 마이크로 서비스들만 재배포하는 방법에 대해서 알아보겠습니다.
2023.02.03 - [Server] - Docker 컨테이너로 젠킨스 설치하기
배포를 위한 환경으로 도커와 젠킨스는 설치가 되어있다고 가정하겠습니다. 도커, 젠킨스 설치는 위 글을 통해서 진행해주시면 되겠습니다. 위 글과 다른 점은 배포 과정은 깃랩과 연동하여 진행되니 gitlab 플러그인을 설치해 주시고 컨테이너 형태로 설치된 젠킨스 안에서 docker 명령어를 실행해야 하기 때문에 아래 명령어를 통해 젠킨스에 접속하신 후 docker를 설치해줍니다.
docker exec -it [젠킨스 컨테이너 이름] /bin/bash
설치 방법은 아래 링크에서 확인하실 수 있습니다.
https://blog.opendocs.co.kr/?p=704
1. 도커 허브 로그인 및 네트워크 생성
먼저 도커 이미지를 관리하기 위해 dockerhub 사이트에 접속하셔서 회원가입을 한 후 배포하고자 하는 환경에서 터미널을 사용해 docker login 명령어로 로그인해줍니다.
그런다음 컨테이너 이름으로 통신하기 위해 docker network create --gateway [ip주소] --subnet [ip주소] [네트워크 이름] 명령어를 입력해서 네트워크를 생성해줍니다.
2. 도커 파일 생성
배포하고자 하는 마이크로 서비스 프로젝트 최상단에서 위와 같이 도커 파일을 생성해줍니다.
- FROM : 기본 이미지
- VOLUME : 외부 마운트 포인트 생성
- COPY : 파일 또는 디렉토리 추가
- ENTRYPOINT : 컨테이너 기본 실행 명령어
3. 개인키 발급
젠킨스 공식 홈페이지를 보면 아래와 같이 젠킨스가 credentials로 사용할수 있는 타입이 명시되어있습니다. 젠킨스에서 명시한대로 credential을 생성하기 위해 gitlab 개인키를 발급 받습니다.
https://www.jenkins.io/doc/book/using/using-credentials/
개인키 발급은 위 화면 처럼 Preferences로 접속하여 Access Tokens 탭에서 생성하실 수 있습니다. 권한을 체크하고 Create Personal Access Token 버튼을 클릭하면 아래와 같이 개인키가 생성됩니다.
눈 버튼을 클릭해서 발급된 토큰을 확인할 수 있습니다. 한번만 공개되니 안전한 곳에 저장해놓는 것을 권장합니다.
4. Gitlab 연동
먼저 젠킨스 페이지에서 Dashboard -> Jenkins 관리 -> System 으로 이동한 후 아래와 같이 Connection name과 Gitlab host URL을 작성합니다. 그런다음 Add 버튼을 눌러 Credentials를 생성해 줍니다.
Kind를 Secret text로 선택하신 후 Secret에 처음 발급 받았던 gitlab 개인키를 복붙해주시고 credentials를 구분할 ID를 작성해 줍니다.
마지막으로 credentials를 생성하신 후 Test Connection 버튼을 클릭하시면 아래와 같이 Success 문구가 뜬걸 확인할 수 있습니다.
5. Global Credentials 생성
나중에 Pipeline script 코드를 작성할 때 사용하기 위해 Dashboard -> Jenkins 관리 -> Manage Credentials -> global 순으로 이동하여 Global Credentials를 아래와 같이 생성해 줍니다.
Username 에 gitlab 아이디를 적어주고 Password는 gitlab에서 발급받은 개인키를 작성해 줍니다. 그 다음 Global Credentials를 식별하기 위해 ID 값을 지정해줍니다.
6. Pipeline 생성 및 webhook 연동
젠킨스와 Gitlab을 연동했으니 이제 CI-CD 파이프라인을 생성하겠습니다.
먼저 젠킨스 Dashboard 에서 새로운 아이템을 클릭하신 후 프로젝트 이름을 작성하고 Pipeline을 생성합니다.
Pipeline을 생성하셨으면 바로 다음 보이는 화면에서 Gitlab Connection을 지정해줍니다. 만약 Gitlab과 잘 연동 됐다면 아래와 같이 Connection Name이 매핑되어 있을 것입니다.
그런다음 Build Triggers에서 아래 항목을 체크해 줍니다.
체크한 항목은 연결된 GitLab 리포지토리에 Push되거나 Merge 될 때마다 Jenkins가 자동으로 빌드를 트리거해야 함을 의미합니다.
"http://j8c209.p.ssafy.io:8080/project/{itemname}"
webhook URL은 GitLab이 Jenkins에게 새 이벤트를 알리는 데 사용할 URL입니다.
항목에 체크하신 후 아래로 내리면 보이는 고급 버튼을 클릭합니다. 그런다음 아래와 같이 Generate 버튼을 클릭하여 Secret token을 발급 받습니다. 여기서 발급 받은 Secret token과 webhook URL 은 Gitlab에서 배포 자동화를 위해 webhook을 걸때 사용됩니다.
Secret token과 webhook URL을 모두 발급 받았으면 Gitlab 프로젝트에서 Settings -> webhook 페이지로 이동합니다.
그런 다음 이동한 페이지에서 아래와 같이 URL과 Secret token을 입력하고 Push events에 trigger를 걸 branch의 이름을 작성합니다. 비워두면 모든 branch에 적용됩니다.
모든 정보를 기입하고 Add webhook을 클릭합니다. 그리고 Test 버튼을 클릭하여 Push events를 발생해보면 Hook executed successfully: HTTP 200 메시지를 받을 수 있습니다.
7. Pipeline 스크립트 코드 작성하기
webhook 연동까지 하셨으면 처음에 Pipeline을 생성하고 webhook URL와 Secret token을 발급 받았던 화면에서 Pipeline script 코드를 작성해 줍니다. 아래 파이프라인 코드를 보면서 주석을 통해 의미를 파악해 보겠습니다.
pipeline {
agent any
tools {
gradle 'Gradle7.6'
}
// 빌드 커맨드와 마이크로 서비스 선언
environment {
BUILD_COMMAND = ' ./gradlew clean build -x test'
CHALLENGE_PROJECT='challenge-service'
USER_PROJECT='user-service'
PAY_PROJECT='pay-service'
EUREKA_PROJECT='eureka-service'
GATEWAY_PROJECT='api-gateway-service'
}
stages {
// 깃랩 프로젝트 코드를 클론해오는 코드
stage('github clone') {
steps {
git branch: 'develop',
// Global Credentials ID
credentialsId: 'deukToken',
url: 'https://lab.ssafy.com/s08-blockchain-contract-sub2/S08P22C209'
}
}
// 프로젝트 빌드
stage('Build') {
// parallel - 프로젝트 병렬 처리
parallel {
stage('build-challenge-service'){
// 해당 프로젝트에 changeset이 발생했을때 동작
when {
changeset "backend/challenge-service/**"
}
steps{
dir('backend/challenge-service') {
// environment 에서 선언한 빌드 커맨드
sh "$BUILD_COMMAND"
}
}
}
stage('build-user-service'){
when {
changeset "backend/user-service/**"
}
steps{
dir('backend/user-service') {
sh "$BUILD_COMMAND"
}
}
}
stage('build-pay-service'){
when {
changeset "backend/pay-service/**"
}
steps{
dir('backend/pay-service') {
sh "$BUILD_COMMAND"
}
}
}
stage('build-eureka-service'){
when {
changeset "backend/eureka-service/**"
}
steps{
dir('backend/eureka-service') {
sh "$BUILD_COMMAND"
}
}
}
stage('build-api-gateway-service'){
when {
changeset "backend/apigateway-service/**"
}
steps{
dir('backend/apigateway-service') {
sh "$BUILD_COMMAND"
}
}
}
}
}
// 빌드된 도커 이미지를 도커 허브에 push 하기 위한 작업
stage('Backup & Copy'){
// 병렬 처리
parallel{
stage('backup-copy-challenge-service'){
// 해당 프로젝트에 changeset이 발생했을 떄 동작
when{
changeset "backend/challenge-service/**"
}
steps{
dir('backend/challenge-service') {
sh 'docker build -t ssafyc209/${CHALLENGE_PROJECT} .'
sh 'docker push ssafyc209/${CHALLENGE_PROJECT}'
}
}
}
stage('backup-copy-user-service'){
when{
changeset "backend/user-service/**"
}
steps{
dir('backend/user-service') {
sh 'docker build -t ssafyc209/${USER_PROJECT} .'
sh 'docker push ssafyc209/${USER_PROJECT}'
}
}
}
stage('backup-copy-pay-service'){
when{
changeset "backend/pay-service/**"
}
steps{
dir('backend/pay-service') {
sh 'docker build -t ssafyc209/${PAY_PROJECT} .'
sh 'docker push ssafyc209/${PAY_PROJECT}'
}
}
}
stage('backup-copy-eureka-service'){
when{
changeset "backend/eureka-service/**"
}
steps{
dir('backend/eureka-service') {
sh 'docker build -t ssafyc209/${EUREKA_PROJECT} .'
sh 'docker push ssafyc209/${EUREKA_PROJECT}'
}
}
}
stage('backup-copy-api-gateway-service'){
when{
changeset "backend/apigateway-service/**"
}
steps{
dir('backend/apigateway-service') {
sh 'docker build -t ssafyc209/${GATEWAY_PROJECT} .'
sh 'docker push ssafyc209/${GATEWAY_PROJECT}'
}
}
}
}
}
// 배포 명령어
stage('Deploy'){
// 병렬 처리
parallel{
stage('deploy-challenge-service'){
// 해당 프로젝트에서 changeset이 발생했을 떄 동작
when{
changeset "backend/challenge-service/**"
}
steps{
// 기존에 해당 프로젝트 이름으로 만들어진 컨테이너가 실행중이라면 정지 후 삭제
sh 'docker stop ${CHALLENGE_PROJECT} || true && docker rm ${CHALLENGE_PROJECT} || true'
// docker hub에 재 업로드된 이미지를 받아서 컨테이너 재실행
sh 'docker run -d --network devday-network --name ${CHALLENGE_PROJECT} -e "eureka.client.serviceUrl.defaultZone=http://eureka-service:8761/eureka/" ssafyc209/${CHALLENGE_PROJECT}'
}
}
stage('deploy-user-service'){
when{
changeset "backend/user-service/**"
}
steps{
sh 'docker stop ${USER_PROJECT} || true && docker rm ${USER_PROJECT} || true'
sh 'docker run -d --network devday-network --name ${USER_PROJECT} -e "eureka.client.serviceUrl.defaultZone=http://eureka-service:8761/eureka/" ssafyc209/${USER_PROJECT}'
}
}
stage('deploy-pay-service'){
when{
changeset "backend/pay-service/**"
}
steps{
sh 'docker stop ${PAY_PROJECT} || true && docker rm ${PAY_PROJECT} || true'
sh 'docker run -d --network devday-network --name ${PAY_PROJECT} -e "eureka.client.serviceUrl.defaultZone=http://eureka-service:8761/eureka/" ssafyc209/${PAY_PROJECT}'
}
}
stage('deploy-eureka-service'){
when{
changeset "backend/eureka-service/**"
}
steps{
sh 'docker stop ${EUREKA_PROJECT} || true && docker rm ${EUREKA_PROJECT} || true'
sh 'docker run -d -p 8761:8761 --network devday-network --name ${EUREKA_PROJECT} ssafyc209/${EUREKA_PROJECT}'
}
}
stage('deploy-api-gateway-service'){
when{
changeset "backend/apigateway-service/**"
}
steps{
sh 'docker stop ${GATEWAY_PROJECT} || true && docker rm ${GATEWAY_PROJECT} || true'
sh 'docker run -d -p 8000:8000 --network devday-network --name ${GATEWAY_PROJECT} -e "eureka.client.serviceUrl.defaultZone=http://eureka-service:8761/eureka/" ssafyc209/${GATEWAY_PROJECT}'
}
}
}
}
}
}
여기 까지 진행하신 다음 푸쉬나 머지를 하면 아래와 같이 스크립트 코드에서 선언한 step에 맞게 CI-CD 과정을 확인하실 수 있고 성공화면까지 확인하실 수 있습니다.
'Spring' 카테고리의 다른 글
[Spring] CORS 이란? CORS 에러 해결방법 (1) | 2023.04.13 |
---|---|
[Spring] 비동기 통신과 데이터 동기화를 위한 카프카 활용 (1) (0) | 2023.04.11 |
[Spring] MSA 프로젝트 만들기 (3) (0) | 2023.03.24 |
[Spring] MSA 프로젝트 만들기 (2) (0) | 2023.03.22 |
[Spring] MSA 프로젝트 만들기 (1) (0) | 2023.03.19 |