※ 1, 2, 3편을 안 보고 이 포스팅을 볼 경우에 이해가 안 될 수가 있습니다. 1, 2, 3편을 연달아 보시는 걸 추천드립니다.
1. 파이프 라인이란?
단일 작업이 아니라 연쇄적인 작업들을 이어주게 합니다. Jenkins에는 파이프 라인을 구축하는 2가지 방식이 있습니다.
1) Delivery Pipeline
Item 타입의 프로트타입의 연장선입니다. Jenkins 프로젝트 마지막의 빌드 후 조치에서 Build other projects를 선택. Projects to build에서 첫 번째 단계 이후에 실행할 Jenkins 프로젝트명을 적으면 됩니다. (반드시 두 번째 단계에서 실행할 Jenkins 프로젝트가 구성되야 한다.)
Trigger only if build is stable을 선택하여 안정적으로 첫 번째 단계가 끝났을 경우에 다음 jenkins 프로젝트가 실행됩니다. 이렇게 연쇄적으로 실행이 되게끔 파이프 라인을 구축할 수 있습니다.
2) Jenkins Pipeline
Jenkins에서 프로젝트를 생성할 때, 위의 사진처럼 Pipeline을 선택하면 생성할 수 있습니다. Item 대신 script를 활용하여 좀 더 다이나믹하게 자신이 원하는 형태로 구성할 수 있습니다.
크게 두 가지 방식이 있는데 하나는 Declarative, 다른 하나는 Scripted(Groovy+DSL) 방식입니다.
이 두 방식의 차이점은 Declarative의 경우 실패할 때 그 다음 경우로 진행하지 않는 반면, Scripted는 그 다음 경우로 계속 진행합니다. 이외에도 시작 시 유효성 검사 유무, 제어문, option의 차이점이 있습니다.
이 포스팅에서는 Declarative 문법을 활용하여 파이프 라인을 구축하겠습니다.
2. Declarative 문법
Declarative 문법은 위와 같습니다.
문법을 기반으로 이렇게 단계별로 파이프 라인을 나눌 수 있습니다.
Declarative 문법을 사용하고 싶은 경우에는
Pipeline Syntax를 클릭하여
Item에서처럼 설정값을 넣어주고, Generate Pipeline Script를 클릭하면
파이프 라인 문법에 맞게끔 생성된 걸 확인할 수 있습니다. 이걸 복사하고 붙여넣기하면 됩니다.
이제 이를 기반으로 파이프 라인을 구축해보겠습니다.
3. Pipeline Flow
제가 구축할 파이프 라인은 아래와 같습니다.
- Git Clone
- SonarQube로 코드 분석
- Spring Boot 파일을 Gradle을 이용하여 Jar 파일로 빌드하기
- Jar 파일을 Docker 파일을 이용하여 이미지 빌드하기
- 생성된 도커 이미지를 docker hub에 push하기
- 기존 도커 이미지 삭제
- publish over ssh를 사용하여 배포 서버에 3편에 제작한 쉘 스크립트 실행하여 배포하기
요약을 하면
( Git Clone => SonarQube => Compile => Image Build => Image Push => Image Clean => Deployment )
단계로 진행할 겁니다.
3-1. Git Clone
Build Triggers에는 poll scm을 체크하여 2편처럼 Github에 master 브랜치에서 Push가 일어나면, 알아서 코드를 Pull하게끔 설정했습니다.
파이프 라인 스크립트 문법은 위와 같습니다.
cp 명령어를 왜 한 것인지 모르겠다는 분들은 이 글을 참조하시면 됩니다.
3-2. SonarQube
Jenkins의 SonarQube 설정은 이 글을 참조하시길 바랍니다.
이 포스팅에서는 SonarQube의 Pipeline Script를 어떻게 작성하는지만 나타내겠습니다.
이전의 SonarQube 글처럼 설정했다면, 위의 이미지처럼 시스템 설정에서 SonarQube가 설정된 걸 확인할 수 있습니다.
다시 파이프라인으로 돌아가서
withSonarQubeEnv에는 시스템 설정에서 등록한 sonarqube의 Name을 등록해줍니다. 여기서 './gradlew sonarqube' 명령어로 gradle에서 sonarqube 검사를 실시하게끔 만듭니다.
파이프 라인을 실행하고, Sonarqube가 설치된 ec2의 URL을 확인해보면
검사가 된 걸 확인할 수 있습니다.
3-3. Compile
간단히 gradle로 build하면 끝입니다.
3-4. Image Build
Jenkins가 설치된 EC2에 반드시 Docker가 설치되야 합니다. Docker 설치는 https://dongle94.github.io/docker/docker-ubuntu-install/ 을 참고하시길 바랍니다.
Docker Image를 만들기 위해서는 Dockerfile이 필요합니다. Dockerfile의 내용은 3편을 참고하시면 됩니다.
저는 /home/ubuntu/spring 위치에 Dockerfile을 위치시켰습니다.
/home/ubuntu/spring에 위치한 Dockerfile을 파이프 라인 worksapce의 jar 파일이 있는 위치로 copy 시켰습니다.
이후 Docker 파일을 이용하여 image를 생성했습니다.
3-5. Image Push
DockerHub token을 등록해 줘야 jenkins를 통해 docker push 시 Docker Hub 로그인 과정에서 권한 관련 오류가 나지 않습니다. 이를 위해 Docker Hub token을 발급받습니다.
Docker Hub에 접속해서 Account Settings 클릭.
Security > New Access Tokens 클릭. Access Token Description 입력.(원하는 이름으로 입력하면 된다.) 그 다음 Generate 클릭해 줍니다.
Token 복사. 한 번밖에 노출이 안 되므로 꼭 어딘가에 저장을 해야 합니다.
이제 Jenkins로 돌아와서 자신의 도커 허브 토큰을 등록합시다.
Jenkins 관리
Manage Credentials
Add Credentials
Username: Docker hub id 입력, Password: Docker hub token 입력, ID: 원하는 credetial 이름 지정
위의 형식대로 입력하고 Create를 누르면 계정 설정은 끝났습니다.
이제 파이프 라인 구축을 해보겠습니다.
환경 변수에 repository는 docker hub 아이디/repository명 형식으로 지정하고, DOCKERHUB_CREDENTIALS은 jenkins에 위의 과정에서 등록한 docker hub credentials 이름을 넣어줍니다.
이전에 등록한 docker hub의 token을 기반으로 도커 허브에 접속하여 Docker hub에 이미지를 push 해주면 끝입니다.
3-6. Image Clean
Jenkins 서버에는 이제 이미지가 필요없기에 기존 도커 이미지를 삭제해줍니다.
3-7. Deployment
3편에 만든 쉘 스크립트를 실행만 시켜도 돼서 publish over ssh 대신 ssh를 사용하면 되지만, 저는 jar 파일도 같이 옮기고 싶기에 publish over ssh 방식을 선택했습니다.
stage('Ssh Publisher - Deployment'){
steps{
sshPublisher(publishers: [sshPublisherDesc(configName: 'EC2-hospital-portfolio', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''sudo docker rmi -f mac20010/hospital
~/backend/deploy.sh
docker rmi -f $(docker images -f "dangling=true" -q)
''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/backend/', remoteDirectorySDF: false, removePrefix: 'build/libs/', sourceFiles: 'build/libs/hospital-0.0.1-SNAPSHOT.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
}
배포 파이프라인 스크립트는 위와 같습니다.
배포 스크립트(deploy.sh)를 실행하기 전에 sudo docker rmi -f mac20010/hospital 명령어를 한 이유는 이전에 docker hub에서 pull한 이미지를 갱신시키기 위함입니다.
쉽게 설명하면 배포 스크립트에서 docker-compose를 실행하게 되는데, 이때 사용하는 이미지는 mac20010/hospital라는 기존 ec2에 도커 허브에 pull한 이미지를 사용합니다.
저는 이미지를 계속 갱신시키고 싶은데 기존의 이미지가 계속 남아있어서 도커 허브에 pull을 못하는 겁니다. 이에 sudo docker rmi -f mac20010/hospital 명령어로 기존 이미지를 삭제.
deploy.sh를 실행하면, docker-compose이 실행되어 mac20010/hospital라는 이미지 이름이 배포 ec2 서버에 없기에 도커 허브에서 pull을 하게 됩니다. 즉, docker hub에 갱신된 이미지를 계속 pull하기 위해 이미지를 삭제했다고 생각하시면 됩니다.
deploy.sh 쉘 스크립트가 끝났다면, docker rmi -f $(docker images -f "dangling=true" -q 명령어로 이름이 없는 이미지를 삭제하게 합니다.
이러면 배포 ec2 서버에 갱신된 이미지만 남게됩니다.
흐름을 간단히 설명하면 아래와 같습니다.
기존의 도커 이미지를 삭제 (docker rmi -f mac20010/hospital 명령어)
docker compose에서 새롭게 빌드한 docker image를 pull한다. (deploy.sh 실행)
스크립트가 끝나면 이전의 이미지가 삭제된다. (docker rmi -f $(docker images -f "dangling=true" -q 명령어)
4. 마무리
전체적인 파이프 라인이 구축이 완성되었습니다.
전체적인 Pipeline 스크립트 내용은 아래와 같습니다.
pipeline{
environment {
repository = "mac20010/hospital"
DOCKERHUB_CREDENTIALS = credentials('dockerhub-login')
}
triggers {
pollSCM('* * * * *')
}
agent any
stages{
stage('Git Clone'){
steps{
git 'https://github.com/kimjungwon2/hospital'
sh '''sudo cp /home/ubuntu/spring/application-jwt.yml /var/lib/jenkins/workspace/Backend-Pipeline/src/main/resources/application-jwt.yml
sudo cp /home/ubuntu/spring/application-db.yml /var/lib/jenkins/workspace/Backend-Pipeline/src/main/resources/application-db.yml
sudo cp /home/ubuntu/spring/application-aws.yml /var/lib/jenkins/workspace/Backend-Pipeline/src/main/resources/application-aws.yml'''
}
}
stage('SonarQube'){
steps{
withSonarQubeEnv('SonarQube_Server') {
sh '''chmod +x gradlew
./gradlew sonarqube'''
}
}
}
stage('Compile'){
steps{
sh './gradlew clean build'
}
}
stage('Build Image') {
steps {
script {
sh '''sudo cp /home/ubuntu/spring/Dockerfile /var/lib/jenkins/workspace/Backend-Pipeline/build/libs/
cd /var/lib/jenkins/workspace/Backend-Pipeline/build/libs
docker build -t $repository:latest .
'''
}
}
}
stage('Image Push') {
steps {
script {
sh '''echo $DOCKERHUB_CREDENTIALS_PSW | docker login -u $DOCKERHUB_CREDENTIALS_USR --password-stdin
docker push $repository:latest
'''
}
}
}
stage('Image Clean') {
steps {
sh "docker rmi $repository:latest"
}
}
stage('Ssh Publisher - Deployment'){
steps{
sshPublisher(publishers: [sshPublisherDesc(configName: 'EC2-hospital-portfolio', transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: '''sudo docker rmi -f mac20010/hospital
~/backend/deploy.sh
docker rmi -f $(docker images -f "dangling=true" -q)
''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: '/backend/', remoteDirectorySDF: false, removePrefix: 'build/libs/', sourceFiles: 'build/libs/hospital-0.0.1-SNAPSHOT.jar')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: true)])
}
}
}
}
파이프 라인을 실행시켜보면
모든 단계가 정상적으로 작동하는 걸 확인할 수 있습니다.
긴 과정 따라오시느라 고생 많으셨습니다.
블로그 글처럼 완전 똑같이 하는 게 아니라. 전체적인 흐름을 이해하여 자신의 환경에 맞게끔 수정하신다면 각자의 환경에 맞는 CI/CD를 구축하실 수 있을 겁니다. 감사합니다.
'DevOps > CI&CD' 카테고리의 다른 글
EC2에 SonarQube 설치 & Jenkins로 코드 분석하기 (2) | 2023.04.27 |
---|---|
Jenkins를 활용한 CI/CD 구축(3/4) - Docker를 활용한 무중단 배포 (0) | 2023.04.25 |
Jenkins를 활용한 CI/CD 구축(2/4) - publish over ssh (0) | 2023.02.15 |
Jenkins를 활용한 CI/CD 구축(1/4) - EC2 서버 Spring boot 빌드 (0) | 2023.02.15 |