앞에서는 build step을 이용해서 추가했다. 그러나 jenkins에서 이걸 언제 하나하나 다 만지고 있나. 프로그래머처럼 code를 이용해서 각각의 step을 design할 수 있으며 - 이것이 jenkins pipeline이다.
MSA는 빠른 배포가 목적이기 때문에 JUnit, SonarQube 등의 품질검사 pipeline을 구성한다. pipeline은 gradle build - JUnit test - SonarQube test - docker build, push - kubernetes deploy 이렇게 될 것임. pipeline으로 이것을 구성해 볼 것이다.
만약 2개 이상의 프로젝트가 있으면, 각각의 프로젝트를 jenkins project에 연결시켜서 각각 프로젝트의 image만 build하고 docker hub에 날리고, main server에서는 이 image들만 kubernetes로 처리하든, 받아와서 정리하면 될 것이다.
Jenkinsfile 내부에 있는 pipeline이 실행되며, 일반적으로 stages - stage - step 이런 단계로 구성된다.
1. jenkins pipeline project 생성
그리고 앞 포스팅과 마찬가지로 gitlab build trigger의 webhook url을 받고, secret token도 gitlab webhook에 등록한다.
다음으로 pipeline 설정으로 가자.
gitlab repo url을 등록하고 credential도 등록, branches to build는 어떤 branch를 build할 것인지이다.
Script Path는 나는 제일 위에 올려뒀으니 Jenkinsfile이라고만 적는다.
그럼 Jenkinsfile을 적어보자.
2. Jenkinsfile 작성
일단 큰 순서는 다음과 같다.
- gradlew clean & build
- JUnit test
- SonarQube test
- docker image build & push
- kubernetes deploy
일단 gradlew clean & build, docker image build & push & container run 이정도만 해보자.
먼저, 몇 가지를 알아두어야 한다.
* 결국 jenkinsfile은 jenkins 내의 script를 실행하는 것이다.
* jenkinsfile 내의 stage 이름이 겹치면 안된다.
* 구성은 stages - stage - steps이다.
* stages는 이름이 없다. 내부에 여러 개의 stage를 가질 수 있다.
* stage는 이름을 붙인다. 내부에 1개의 steps를 가진다.
* steps는 실제로 할 shell을 적는다.
* 여러 개의 step을 넣고 싶으면, stages - stage - [stages - stage - steps] 형태를 가져야 한다.
* shell 내의 $ 기호는 injection 위험이 있기 때문에 shell 전체를 '''{내용}'''으로 주석처리하자.
그럼 내가 작성한 Jenkinsfile은 다음과 같다.
pipeline {
agent any
tools{
gradle '6.9'
}
stages{
stage('Ready'){
steps{
sh "echo 'Ready'"
}
}
stage('Spring Boot Clean & Build'){
steps{
sh "chmod +x gradlew;"
sh "./gradlew clean;"
sh "./gradlew build;"
}
}
stage('Docker build & push & container run'){
steps{
sh '''ssh -t -t ec2-user@$(/sbin/ip route | awk '/default/ { print $3 }') <<EOF
cd /home/ec2-user/compose/jenkins/workspace/test1
docker build -t hyelie/composetest .
exit
EOF'''
sh '''ssh -t -t ec2-user@$(/sbin/ip route | awk '/default/ { print $3 }') <<EOF
docker push hyelie/composetest
docker run -p 8080:8080 -d hyelie/composetest
exit
EOF'''
}
}
}
}
제일 위의 tools는 global tool configuration 내의 내가 설정한 gradle 버전이다.
Ready는 준비되었다는 것을 출력해준다.
Spring Boot Clean & Build는 gradlew clean build를 실행한다. gradlew의 권한이 없기 때문에 chmod로 권한을 준다.
Docker build & push & container run은 jenkins에서 docker로 접속해서 image build, push, image run을 하는 것이다. 이후에 EC2 url로 들어가면 아주 잘 보인다.
Docker도 돌아아고 있고, docker hub에도 image가 들어가 있다.
3. JUnit test
다음으로 JUnit test를 pipeline에 올려보자.
기본적으로 Spring에서는 JUnit test를 지원한다. 이걸 이용해서 JUnit Test를 바로 진행할 수 있다. 수정한 Jenkinsfile은 아래와 같다.
Jenkinfile
pipeline { agent any tools{ gradle '6.9' } stages{ stage('Ready'){ steps{ sh "echo 'Ready'" } } stage('Spring Boot Clean & Build'){ steps{ sh "chmod +x gradlew;" sh "./gradlew clean;" sh "./gradlew build -x test;" } } // build 시 test 없이 진행하고, test는 추후에 거르기 위해 이렇게 이용함. stage('Gradle Junit Test') { steps { sh "chmod +x gradlew; ./gradlew test" } } // test를 이용해서 확인함. stage('JUnit Test Publish'){ steps{ junit '**/build/test-results/test/*.xml' } } // Junit 테스트 결과를 젠킨스 프로젝트 첫 화면에서 볼 수 있게 결과물을 출력한다. 이렇게 해주면 알아서 결과물을 보여준다. stage('Docker build & push & container run'){ steps{ sh '''ssh -t -t ec2-user@$(/sbin/ip route | awk '/default/ { print $3 }') <ome/ec2-user/compose/jenkins/workspace/test1 docker build -t hyelie/composetest . exit EOF''' sh '''ssh -t -t ec2-user@$(/sbin/ip route | awk '/default/ { print $3 }') <omposetest exit EOF''' } } } }
build.gradle
plugins {
id 'org.springframework.boot' version '2.4.8'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '8'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
test {
useJUnitPlatform()
}
build.gradle을 보면 test{useJUnitPlatform()}이 적혀 있다. 이는 spring에서 JUnit을 사용하겠다는 것이다. 그런데 내가 하고 싶은 것은, build는 build대로 하고 build 이후에 test를 거치고 싶다. 그래서 ./gradlew build -x test를 이용해 build 할 때 test 없이 진행하고, 이후에 ./gradlew test를 이용해 test를 진행하고 junit test publish를 통해 pipeline에 띄워줄 것이다. gradle 6.9버전 기준 위의 경로를 사용하면 된다.
/src/test/java/com/example/demo/DemoApplicationTests.java
package com.example.demo;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.assertj.core.api.Assertions; // Assertion method를 이용하기 위함
import org.junit.jupiter.api.AfterEach; // AfterEach method를 이용하기 위함
import org.junit.jupiter.api.BeforeEach; // BeforeEach method를 이용하기 위함
@SpringBootTest
class DemoApplicationTests {
@Test
void contextLoads1() {
System.out.println("result = " + (1 == 0));
Assertions.assertThat(1).isEqualTo(1);
}
@Test
void contextLoads2() {
Assertions.assertThat(2).isEqualTo(0);
}
}
이렇게 실패할 assertioni을 넣고 build하면 다음과 같이 error가 난다.
실패해야하는 test인 contextLoad2()에서 오류가 났다고 한다.
그러면 contextLoad2()를 통과하게 바꿔보자.
build가 모두 성공적으로 끝났다! 테스트 결과 현황도 우측 상단에 잘 나오고, test를 성공한 build history만 여기에 나오게 된다.
4. SonarQube test
한 가지 문제가 생겼다. EC2를 처음 만들 때 별 설정 없이 만들었더니 SSD 용량이 너무 작다... 이걸 먼저 늘리겠다.
파란색 볼륨 ID를 클릭해서 넘어가자.
작업 - 볼륨 수정을 눌러서 크기를 바꾸자.
그러면 하나는 끝났다.
SonarQube는 jenkins를 설치했던 것처럼 docker-compose를 이용해 설치하겠다. 아래 블로그를 참고했다.
https://gblee1987.tistory.com/105
/home/ec2-user/compose/docker-compose.yml
version: "3"
services:
jenkins:
container_name: jenkins-compose
build:
context: jenkins-dockerfile
dockerfile: Dockerfile
user: root
ports:
- "8081:8080"
volumes:
- /home/ec2-user/compose/jenkins:/var/jenkins_home
- /home/ec2-user/compose/.ssh:/root/.ssh
sonarqube:
image: sonarqube:lts
container_name: sonarqube
ports:
- "9000:9000"
volumes:
- sonarqube_conf:/opt/sonarqube/conf
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
volumes:
sonarqube_conf:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
sonarqube의 데이터를 volume에 저장하기 위해 volume을 선언하고, 포트는 9000으로 하겠다. 기존에 있는 모든 container, image를
docker ps -a
docker rm {container ID}
docker images
docker rmi {image ID}
를 이용하 싹 지우고, docker-compose up -d를 이용해 실행시키자. 잠시 기다리면 sonarqube가 실행된다.
https://waspro.tistory.com/596
그럼 이 블로그를 참고해서, sonarqube를 실행시켜 보겠다. 제일 처음 로그인 화면의 id/pw는 admin, admin이다. 로그인하면 비밀번호 바꾸라 그런다. 적당히 바꾸자.
sonarqube 메인이 보인다.
이제 sonarqube project를 생성하고 token을 생성할 것이다.
생성 방식은 manually, 이후 token을 만들자.
적당히 아무렇게나 두자.
create 했으면 continue를 누르자.
나는 gradle이니까 gradle에 있는 내용을 복사하자. 총 2가지 단계를 진행해야 한다고 한다.
1) build.gradle에 pulgins 넣기
2) 실행은 ./gradlew sonarqube \ ~
이렇게 한다고 한다.
다 했으면 administration을 눌러서 webhook을 jenkins와 연동하자. webhook을 사용하는 이유는, jenkins에서 sonarqube에 접속해서 분석 요청 -> sonarqube에서 분석 -> 분석 완료 시 jenkins에 webhook으로 알려줌 이 단계를 거치기 위함이다.
name은 jenkinswebhook으로, url은 http://{jenkins url}:{jenkins port}/sonarqube-webhook/으로 등록한다.
다음으로 jenkins plugin을 설치하자.
총 2가지, SonarQube Scanner, Sonar Quality Gates 2개 플러그인을 설치한다.
다음으로 sonar qube token을 jenkins configuration에 등록한다.
아까 만들었던 sonarqube token을 등록한다. kind는 secret text, 이름은 sonar-token으로 하겠다.
다음으로 jenkins 관리 - 시스템 설정 - sonarqube servers에서 sonarqube server 정보 등록을 한다. 서버 이름은 sonar-server, url은 sonar qube url을, token은 방금 생성한 것으로 한다.
마지막으로 jenkins 관리 - global tool configuration - sonarqube scanner를 등록한다. install automatically를 체크해서 자동으로 만들게 하자.
거의 마지막이다. sonarqube analysis를 추가하자. 내가 참조한 블로그에서는 무한로딩을 방지하기 위한 코드를 넣었는데, EC2램이 너무 적어서 어제 gradle build에 16분이 걸렸다(..) 그래서 일단 이거는 패스하기로 하자. 그리고 sonar qube가 잘 되는지 보기 위해 잡다한 것 다 치우고 build 이후에 sonarqube로 연동만 해 볼 것이다.
/local/jenkinsfile
pipeline {
agent any
tools{
gradle '6.9'
}
stages{
stage('Ready'){
steps{
sh "echo 'Ready'"
}
}
stage('Spring Boot Clean & Build'){
steps{
sh "chmod +x gradlew;"
sh "./gradlew clean;"
sh "./gradlew build -x test;"
}
} // build 시 test 없이 진행하고, test는 추후에 거르기 위해 이렇게 이용함.
// stage('Gradle Junit Test') {
// steps {
// sh "chmod +x gradlew; ./gradlew test"
// }
// } // test를 이용해서 확인함.
// stage('JUnit Test Publish') {
// steps {
// junit '**/build/test-results/test/*.xml'
// }
// } // Junit 테스트 결과를 젠킨스 프로젝트 첫 화면에서 볼 수 있게 결과물을 출력한다. 이렇게 해주면 알아서 결과물을 보여준다.
stage('SonarQube Analysis'){
steps{
withSonarQubeEnv('sonar-server'){
sh './gradlew sonarqube -Dsonar.projectKey=demo -Dsonar.host.url=http://3.17.157.130:9000 -Dsonar.login=c94ed346d6400551c9a29132cbe88674465f4924'
}
}
}
// stage('Docker build & push & container run'){
// steps{
// sh '''ssh -t -t ec2-user@$(/sbin/ip route | awk '/default/ { print $3 }') <<EOF
// cd /home/ec2-user/compose/jenkins/workspace/test1
// docker rmi -f hyelie/composetest
// docker build -t hyelie/composetest .
// exit
// EOF'''
// sh '''ssh -t -t ec2-user@$(/sbin/ip route | awk '/default/ { print $3 }') <<EOF
// docker push hyelie/composetest
// exit
// EOF'''
// }
// }
// stage('Clean'){
// steps{
// sh "chmod +x gradlew;"
// sh "./gradlew clean;"
// }
// }
}
}
일단 이렇게 해서 build하고 sonarqube 연동만 되는지 보자.
sonarqube stage에 있는 withSonarQubeEnv는 jenkins 관리 - 시스템 설정 - sonarqube servers에서 등록한 sonarqube server 이름이다.
shell은 sonarqube 프로젝트를 생성했을 때 token을 만들었었는데, 거기 나온 run analysis on your project에 있는 내용들이다.
시키는대로 build.gradle에 이 내용을 넣고, run the following command를 복사해서 jenkinsfile에 넣자.
- 실행 확인
굿!!!
***) EC2 램 작은거 킹받아서 GCP로 넘어갔다.
1) GCP 연결
2) 포트 열기
https://4whomtbts.tistory.com/90