(CI/CD) 젠킨스 Jenkins
젠킨스 설치
- 공식 홈페이지에서 권장하는 방법은
.war
파일 하나 받아서 java 커맨드로 띄우는 방식. 이게 제일 간단 - yum 등을 이용해 아예 패키지로 설치해도 되긴 함
1
2
3
#!/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
16
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
12
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 되도록 하려면https://unix.stackexchange.com/questions/444946/how-can-we-run-a-command-stored-in-a-variable
*** 함수 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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
#!/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