Groovy를 이용해서 Jenkins 설정 자동화하기(사용자 생성, 고정 API Token 입력)
kindof
·2023. 1. 18. 18:47
0. Docker 환경에서 Jenkins 서버 띄우기
이전 글[Jenkins, Docker를 통한 CI 환경 구축과 Slack 알람 받기]에서 도커 환경에서 Jenkins 서버를 띄우는 방법에 대해 설명했습니다.
그런데 이 글에서는 DockerHub에서 제공하는 Jenkins lts 버전 이미지를 Pull 받아 사용했었는데요.
사실 위 글에서 사용한 이미지에는 어떤 커스텀화 된 설정들이 없기 때문에 최초 서버 접속 시 아래와 같이 사용자의 비밀번호를 입력하고 플러그인 설치를 묻는 과정, 그 후에 계정을 설정하는 등의 과정을 수작업으로 진행해야 했습니다.
1. Groovy 파일을 이용해서 사용자 자동 생성
Jenkins Docker의 공식 깃허브를 보면, 기본 Jenkins 이미지에 사용자가 원하는 기능을 추가하기 위해 /usr/share/jenkins/ref 위치에 custom groovy 파일을 둘 수 있다고 되어 있습니다.
그래서 이 내용을 따라 새로운 Dockerfile과 groovy 파일을 작성하고 이를 바탕으로 자동으로 사용자 계정을 만드는 실습을 먼저 진행해보겠습니다.
[1] 먼저 Docker는 설치되어 있어야 합니다.
$ brew install docker docker-credential-helper --formula
# 설치 확인
$ docker --version
[2] 아래와 같이 Dockerfile, groovy 파일을 생성합니다.
$ tree -l
.
├── Dockerfile
└── ref
└── init.groovy.d
└── 01_user_setting.groovy
3 directories, 2 files
[3] Dockerfile을 작성합니다.
FROM jenkins/jenkins:lts
USER root
COPY ref /usr/share/jenkins/ref
USER jenkins
ENV JENKINS_USER sunghyeon
ENV JENKINS_PASSWORD 1234
#
# Skip initial setup
#
ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false
새롭게 작성한 Dockerfile은 기존의 LTS 이미지에 로컬의 ref 경로를 /usr/share/jenkins/ref에 복사, 사용자의 정보를 넣는 ENV 추가, 초기 셋업을 스킵하는 옵션만 추가했습니다.
[4] groovy 파일을 작성합니다.
import jenkins.model.*
import hudson.security.*
def env = System.getenv()
def jenkins = Jenkins.getInstance()
jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(false))
jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy())
def user = jenkins.getSecurityRealm().createAccount(env.JENKINS_USER, env.JENKINS_PASSWORD)
user.save()
jenkins.save()
사실 구글링을 해보면 groovy 파일 작성에 관한 정보가 많이 없는 것 같은데요. 뒤에서 또 다른 groovy 파일 설정을 해볼텐데, 이렇게 Jenkins가 제공하는 기능들은 공식 깃허브에서 찾아볼 수밖에 없는 것 같습니다.
어쨌든, 위 파일은 사용자 계정을 Dockerfile의 ENV에서 읽어와서 생성하고, 로그인을 하면 모든 권한을 부여한다는 내용입니다.
[5] 도커파일 빌드, 실행
docker build --tag jenkins/test . && docker run -p 8085:8080 jenkins/test
Dockerfile이 위치한 디렉토리에서 위 명령어를 실행하면 아래와 같은 로그를 남기며 서버가 띄워집니다.
Sending build context to Docker daemon 4.096kB
Step 1/7 : FROM jenkins/jenkins:lts
---> 1055c8a99f66
Step 2/7 : USER root
---> Using cache
---> ab021d54eea0
Step 3/7 : COPY ref /usr/share/jenkins/ref
---> Using cache
---> cd012d711f09
Step 4/7 : USER jenkins
---> Using cache
---> 41eaf0afda6c
Step 5/7 : ENV JENKINS_USER sunghyeon
---> Using cache
---> 19c62f0bf460
Step 6/7 : ENV JENKINS_PASSWORD 1234
---> Using cache
---> a86ed8be2972
Step 7/7 : ENV JAVA_OPTS -Djenkins.install.runSetupWizard=false
---> Using cache
---> baf8acf7b64b
Successfully built baf8acf7b64b
Successfully tagged jenkins/test:latest
Running from: /usr/share/jenkins/jenkins.war
webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")
2023-01-18 06:38:37.792+0000 [id=1] INFO winstone.Logger#logInternal: Beginning extraction from war file
2023-01-18 06:38:38.286+0000 [id=1] WARNING o.e.j.s.handler.ContextHandler#setContextPath: Empty contextPath
2023-01-18 06:38:38.316+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: jetty-10.0.12; built: 2022-09-14T01:54:40.076Z; git: 408d0139887e27a57b54ed52e2d92a36731a7e88; jvm 11.0.17+8
2023-01-18 06:38:38.477+0000 [id=1] INFO o.e.j.w.StandardDescriptorProcessor#visitServlet: NO JSP Support for /, did not find org.eclipse.jetty.jsp.JettyJspServlet
2023-01-18 06:38:38.505+0000 [id=1] INFO o.e.j.s.s.DefaultSessionIdManager#doStart: Session workerName=node0
2023-01-18 06:38:38.717+0000 [id=1] INFO hudson.WebAppMain#contextInitialized: Jenkins home directory: /var/jenkins_home found at: EnvVars.masterEnvVars.get("JENKINS_HOME")
2023-01-18 06:38:38.832+0000 [id=1] INFO o.e.j.s.handler.ContextHandler#doStart: Started w.@6cd56321{Jenkins v2.375.2,/,file:///var/jenkins_home/war/,AVAILABLE}{/var/jenkins_home/war}
2023-01-18 06:38:38.847+0000 [id=1] INFO o.e.j.server.AbstractConnector#doStart: Started ServerConnector@485e36bc{HTTP/1.1, (http/1.1)}{0.0.0.0:8080}
2023-01-18 06:38:38.864+0000 [id=1] INFO org.eclipse.jetty.server.Server#doStart: Started Server@625abb97{STARTING}[10.0.12,sto=0] @1384ms
2023-01-18 06:38:38.869+0000 [id=23] INFO winstone.Logger#logInternal: Winstone Servlet Engine running: controlPort=disabled
2023-01-18 06:38:39.015+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Started initialization
2023-01-18 06:38:39.033+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: Listed all plugins
2023-01-18 06:38:39.448+0000 [id=30] INFO jenkins.InitReactorRunner$1#onAttained: Prepared all plugins
2023-01-18 06:38:39.450+0000 [id=29] INFO jenkins.InitReactorRunner$1#onAttained: Started all plugins
2023-01-18 06:38:39.458+0000 [id=31] INFO jenkins.InitReactorRunner$1#onAttained: Augmented all extensions
2023-01-18 06:38:39.604+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: System config loaded
2023-01-18 06:38:39.604+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: System config adapted
2023-01-18 06:38:39.604+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: Loaded all jobs
2023-01-18 06:38:39.607+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: Configuration for all jobs updated
2023-01-18 06:38:39.633+0000 [id=44] INFO hudson.util.Retrier#start: Attempt #1 to do the action check updates server
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.codehaus.groovy.vmplugin.v7.Java7$1 (file:/var/jenkins_home/war/WEB-INF/lib/groovy-all-2.4.21.jar) to constructor java.lang.invoke.MethodHandles$Lookup(java.lang.Class,int)
WARNING: Please consider reporting this to the maintainers of org.codehaus.groovy.vmplugin.v7.Java7$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
2023-01-18 06:38:39.781+0000 [id=28] INFO j.util.groovy.GroovyHookScript#execute: Executing /var/jenkins_home/init.groovy.d/01_user_setting.groovy
2023-01-18 06:38:40.159+0000 [id=28] INFO jenkins.InitReactorRunner$1#onAttained: Completed initialization
2023-01-18 06:38:40.195+0000 [id=22] INFO hudson.lifecycle.Lifecycle#onReady: Jenkins is fully up and running
2023-01-18 06:38:49.839+0000 [id=44] INFO h.m.DownloadService$Downloadable#load: Obtained the updated data file for hudson.tasks.Maven.MavenInstaller
2023-01-18 06:38:49.840+0000 [id=44] INFO hudson.util.Retrier#start: Performed the action check updates server successfully at the attempt #1
[6] 결과 확인
이제 localhost:8085 로 접속해보면 젠킨스 설정과 관련된 스텝들을 건너뛰고 바로 Dashboard가 보여집니다.
그리고 우리가 설정했던 사용자의 아이디와 비밀번호를 입력하면 아래와 같이 로그인이 되는 것을 확인할 수 있습니다.
2. Jenkins Remote Job 실행
아래와 같이 실험을 위해 테스트용 Job을 하나 만들겠습니다.
Job을 수동으로 돌리면 아래와 같이 정상 동작합니다.
그런데 만약 이러한 Job이 Jenkins 외부에서 호출되어야 한다면 어떨까요?
실제로 규모가 큰 서비스에서는 많은 Job들이 서로 Pipeline으로 연결되어있어 외부의 Job이 종료된 뒤, 우리 내부의 Job을 호출할 수 있습니다.
그리고 이런 경우를 상정하여 Jenkins에서는 API Token이라는 것을 제공하는데요.
아래와 같이 <Jenkins URL>/user/<username>/configure 에 들어가보면 API Token을 생성할 수 있고, 이 토큰을 바탕으로 외부에서 내부의 Job을 트리거할 수 있습니다.
실제로 테스트를 위해 curl 요청을 보내보면, 201 응답과 함께 Dashboard에서 Job이 트리거 된 것을 확인할 수 있습니다.
3. Groovy 파일을 이용해서 고정된 API Token 설정
그런데 위와 같이 사용자의 API Token을 바탕으로 한 외부의 트리거링은 한 가지 문제가 있습니다. 바로 '젠킨스 도커가 다시 띄워지면 초기화된다'는 것인데요.
따라서 위와 같은 루트를 통해 Job을 트리거링한다면, 젠킨스 도커가 시작될 때마다 API Token을 이전에 사용하던 값 그대로 넣어주는 작업이 필요하게 됩니다. Token을 새로 발급받으면 이를 다시 공유해야 하기 때문이죠.
그래서 처음에 작성한 Groovy 파일에 내용을 추가합니다.
import jenkins.model.*
import hudson.security.*
import jenkins.security.*
def env = System.getenv()
def jenkins = Jenkins.getInstance()
jenkins.setSecurityRealm(new HudsonPrivateSecurityRealm(false))
jenkins.setAuthorizationStrategy(new FullControlOnceLoggedInAuthorizationStrategy())
def user = jenkins.getSecurityRealm().createAccount(env.JENKINS_USER, env.JENKINS_PASSWORD)
###################################################
def apiTokenProperty = user.getProperty(ApiTokenProperty.class)
def result = apiTokenProperty.tokenStore.addFixedNewToken("test_token", "11e869c106fd9b99e0461864b07473629b")
###################################################
user.save()
jenkins.save()
이제 다시 Dockerfile을 빌드하고 실행해보면, 아래와 같이 "test_token"이 바로 저장되어 있고, Jenkins 컨테이너 안에 config 파일을 보면 위에서 추가한 토큰이 해시화되어 들어가 있는 것을 확인할 수 있습니다.
$ docker exec -it <Container ID> bash
$ cat /var/jenkins_home/users/<user>/config.xml
감사합니다.
'DevOps' 카테고리의 다른 글
이미 바인딩 되어있는 DNS VIP를 교체할 때 테스트하는 방법 (0) | 2023.06.25 |
---|---|
Jenkins, Docker를 통한 CI 환경 구축과 Slack 알람 받기 (0) | 2022.09.21 |
WSL2, Docker desktop을 사용할 때 네트워킹 실패 문제(--net=host Option 사용 시 주의할 점) (0) | 2022.09.14 |