(CI/CD) 젠킨스 Jenkins
젠킨스 설치
- 공식 홈페이지에서 권장하는 방법은
.war
파일 하나 받아서 java 커맨드로 띄우는 방식. 이게 제일 간단 - yum 등을 이용해 아예 패키지로 설치해도 되긴 함
1
2
#!/bin/sh
nohup java -DJENKINS\_HOME=/home1/user/jenkins/jenkins\_home -jar /home1/user/apps/jenkins/jenkins.war --httpPort=8080 > /home1/user/logs/kenins/nohub.out &
- JENKINS_HOME은 옵션으로 줄 수도 있고, 환경변수로 줄 수도 있음. 기본 경로는
~/.jenkins
- jenkins-x.x.x 폴더에 symlink 걸어서 jenkins로 참조하도록 하는게 관리 편함
8080
은 기본 포트
자바가 없다면?
- [OS/LINUX & UNIX] - Java 관련 (JDK, 설치, …)
- JAVA_HOME 꼭 추가해줄 것. 없으면 JDK 추가할 때 젠킨스가 JDK path처럼 안보이는데? 라고 한다.
Jenkins 설정
jenkins가 webhook을 수신했을 때, 어떤 작업을 수행하도록 명시하는 방법 중 자주쓰는 방식은Freestyle project 와Pipeline 방식 (젠킨스에서 New Item 눌러보면 알겠지만 다른 방식도 있다.)
Pipeline 방식 = Jenkinsfile 사용하는 방식
Freestyle project가 빌드와 관련된 설정을 젠킨스 상에서 이것 저것 눌러서 세팅하는 방식인 반면, Pipeline 방식은 Jenkinsfile이라는 소스 안에 빌드 설정이 들어가는 방식
- 형상관리 편함
- 빌드 방법을 소스와 함께 관리할 수 있음
- https://www.jenkins.io/solutions/pipeline/
- https://www.jenkins.io/doc/book/pipeline/getting-started/
- Blue Ocean 과 연동하여 CD pipeline 확인 가능
- Multibranch Pipeline 방식을 사용하면 [Push는 Push 대로, PR은 플러그인 써서 따로] 이렇게 별도로 아이템 설정할 필요 없이 하나의 잡으로 관리할 수 있다.
Jenkinsfile 예제
Pipeline에서 Jenkinsfile을 빈 파일로 두면, 빌드가 트리거 될 때 마다 빌드옵션이 없어서 빈 빌드를 수행하게 되어 항상 success가 뜬다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
node {
stage ('clone') {
checkout scm
}
stage ('gradle wrap') {
sh '/opt/gradle/gradle-5.2.1/bin/gradle wrap'
}
stage('gradlew build') {
if (isUnix()) {
sh './gradlew build'
} else {
bat 'gradlew.bat build'
}
}
}
checkout scm이 credential을 적절히 설정하고, branch도 알아서 설정한 다음, git에서 해당 브랜치를 fetch해오는 작업을 해주는 명령어다.
PR webhook 수신 시 빌드 트리거 하는 플러그인
- Github Pull Request Builder Plugin
- 기존에 많이 사용하던건데, 보안 이슈 때문에 deprecated 되었음. 그래도 여전히 사용 가능.
- Github Branch Source Plugin
- ghprb가 보안 때문에 말이 많아서 이걸 쓰는 것을 권장하고 있음
- 젠킨스 설치 시 인기 플러그인 자동설치를 누르면 알아서 설치됨
- docs + howto
- github
Jenkinsfile
을 프로젝트 루트 디렉토리에 추가한다. ( 이걸 안하면 Repo가 인식이 안됨. )- New Item - Github Organization 선택 *** Github Org 아이템은 모든 레포 중 조건에 맞는 것들을 선택할 때 사용하는 Item. 이걸 이용해도 되긴 된다.
- Owner에 github repository가 위치한 계정 또는 조직 이름 적고 (접근 권한 가진 계정은 Credentials에 명시)
- 해당 계정 또는 조직에서 어떤 레포지토리에 접근할 것인지는 Behaviors - Add - Filter by name 선택. 하고 레포지토리 명시.
- Discover branches - Exclude …(기본 옵션으로 놔둔다)
- 이건 해당 레포지토리에서 branch를 가져와 목록으로 띄워줄거냐,를 결정한다.
Exclude ...
옵션은 일반 브랜치는 Branch 쪽에 띄워주고 pull request를 요청한 브랜치는 Pull Requests쪽에 띄워준다.- PR을 날렸을 때는 PR한 브랜치에 대해서 빌드하고, Branch 목록에 있는 브랜치에서 push가 발생하면 push 할 때 마다 빌드함.
Only ...
옵션은 현재 pull request를 요청한 브랜치만 Branch 목록 및 Pull Requests 목록에 중복해서 띄워준다.- 즉 이 옵션을 선택하면 push PR 대기중인 브랜치와 PR 대상 브랜치만 트래킹한다.
All branches
옵션은 그냥 모든 브랜치 보여준다.
- Add - within repository - Filter by name (with wildcards)에 푸시마다 빌드를 하지 않을 브랜치(feature/*)를 적어준다.
- Scan Organization Triggers를 원하는 시간으로 설정한다.
- PR을 날렸을 때 바로 빌드하는게 아니라, 주기적으로 레포지토리를 Scan해서 새로운 PR이 있으면 빌드하는 방식이다.
- 한 10분 정도로 해놓으면 괜찮은 듯. 기본은 1day로 되어 있어서 너무 텀이 길다.
이렇게 설정하면, master, develop같은 브랜치는 푸시할 때 마다 빌드하게 되고, Filter로 Exclude된 feature/* 브랜치에 대해서는 푸시해도 따로 빌드를 하지 않는다(애초에 브랜치 목록에 안보인다.)
새로운 PR이 있는지 주기적으로 체크해서 별도로 빌드, Merge 시 develop, master에 대해 빌드. 하게 된다.
젠킨스에서 빌드 테스트가 돌아가는 시점을 언제로 할 것인가?
- master, dev 브랜치 Push(Merge)가 발생할 때만 빌드 테스트.
- 별로임. 코드리뷰 다 끝나고 Merge 눌렀을 때 테스트가 돌아가는데, 이 때 Failure이 떨어지면 난감하다.
- 물론 PR 올리기 전에 dev -> feature로 땡겨와서 동기화해주면 문제가 발생할 여지가 줄어들기는 하는데, 완벽한 해결책은 아님
- PR 시점
- Code review 전에 테스트가 돌아갈 수 있다는 장점이 있음
- https://ohgyun.com/551- PR 시점의 빌드 테스트의 장점과 플러그인
- feature 브랜치에 Push가 발생할 때도 빌드 테스트.
- 제일 안전한 방식
- 근데 스타일에 따라 Push를 자주 하는 사람도 있을거고, 일단 임시로 Fail 뜨는 Push를 하는 경우도 있을거라.. 좀 지저분하긴 함 보편 적으로는 master, dev push 시, PR 시 둘 다 쓴다.
기타
모든 설정을 잘 한 것 같은데 안된다? 로그를 확인해볼 것. 권한 없어서 안될 가능성도 있음.
1
2
3
4
5
6
7
8
9
10
11
5월 19, 2019 2:06:02 오후 jenkins.branch.OrganizationFolder$OrganizationScan run
정보: hackday #20190519.140600 organization scan action completed: SUCCESS in 2.4 sec
5월 19, 2019 2:07:02 오후 org.jenkinsci.plugins.github.webhook.WebhookManager$1 run
정보: GitHub webhooks activated for job hackday/secure\_keyboard with [GitHubRepositoryName[host=github.com,username=NAVER-CAMPUS-HACKDAY,repository=secure\_keyboard]] (events: [PULL\_REQUEST, PUSH])
5월 19, 2019 2:07:02 오후 jenkins.branch.OrganizationFolder$OrganizationScan run
정보: hackday #20190519.140700 organization scan action completed: SUCCESS in 2 sec
5월 19, 2019 2:07:02 오후 com.squareup.okhttp.internal.Platform$JdkWithJettyBootPlatform getSelectedProtocol
정보: ALPN callback dropped: SPDY and HTTP/2 are disabled. Is alpn-boot on the boot class path?
5월 19, 2019 2:07:03 오후 org.jenkinsci.plugins.github.webhook.WebhookManager$2 applyNullSafe
경고: Failed to add GitHub webhook for GitHubRepositoryName[host=github.com,username=NAVER-CAMPUS-HACKDAY,repository=secure\_keyboard]
java.lang.NullPointerException: There are no credentials with admin access to manage hooks on GitHubRepositoryName[host=github.com,username=NAVER-CAMPUS-HACKDAY,repository=secure\_keyboard]
유용한 Jenkins 플러그인 목록
- CheckStyle
- SLOCCount : 언어 별, 파일 별 loc 계산. UI 깔끔
- Cobertura , JaCoCo : TC 커버리지
- Cobertura는 관리도 잘 안되고 있는 것 같고, javaNCSS를 사용하고 있는데 이게 java8 대응이 안돼서 lambda 파싱에러가 무지남. JaCoCo 쓰는게 나아보인다.
- SonarQube : 정적 분석 도구 (플러그인으로 위 모듈 모두 달 수 있어서, SonarQube를 달거라면 이렇게 하는게 낫지 싶다.)
- plugins.jenkins.io/pipeline-stage-view/
Jenkins start, stop shell script
start-stop-daemon 이용한 방법
직접 작성
- shell script에서 할당 시에는
=
앞뒤 공백 있으면 안됨 [ $PID ]
말고bash ["X$PID" = "X"]
로 체크하는게 무슨 장점이 있었던 것 같은데 까먹었다.- VAR=
cmd
형식의 문법은 이 라인이 실행되는 순간 VAR가 결정되는 것이고, 매번 evaluation 되는 것이 아니다. 매번 evaluation 되도록 하려면 참고 - 함수 evaluation은
$(fun)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/bin/sh
USER=`/usr/bin/whoami`
get_jenkins_pid() {
ps -Af | grep "jenkins.war" | grep -v grep | awk '{print$2}'
}
start() {
echo "Starting jenkins..."
if [ $(get_jenkins_pid) ]; then
echo "Jenkins($(get_jenkins_pid)) is already running."
exit 1
fi
nohup java -DJENKINS_HOME={jenkins_home_path} -jar {jenkins.war path} --httpPort=18080 > {log_path}/nohup.out 2>&1 &
for i in {1..10}; do
if [ $(get_jenkins_pid) ]; then
break
fi
echo "."
sleep 1
done
if [ $(get_jenkins_pid) ]; then
echo "Started jenkins($(get_jenkins_pid))"
else
echo "Failed to start jenkins"
fi
}
stop() {
echo "Stopping jenkins..."
cnt_jenkins_pid=$(get_jenkins_pid)
if [ ! $cnt_jenkins_pid ]; then
echo "Jenkins was not running."
exit 1
fi
kill -9 $cnt_jenkins_pid
for i in {1..10}; do
if [ ! $(get_jenkins_pid) ]; then
break
fi
echo "."
sleep 1
done
if [ ! $(get_jenkins_pid) ]; then
echo "Stoped jenkins($cnt_jenkins_pid)"
else
echo "Failed to stop jenkins($(get_jenkins_pid))"
fi
}
if [ $USER != "irteam" ]; then
echo "irteam 계정으로 실행해 주세요";
exit;
fi
case $1 in
"stop")
stop
;;
"start")
start
;;
*)
echo "Usage: $0 { start | stop }"
exit 1
;;
esac
This post is licensed under CC BY 4.0 by the author.