먼저, Jenkins를 알기 전 CI/CD가 무엇인지 알아야 한다.
CI/CD란?
CI는 지속적 통합(Continuous Integration), CD는 지속적 배포(Continuous Deployment)의 약어로, 개발자들이 동시에 작업한 코드 변경 사항을 통합하고, 이를 실제 환경에 지속적으로 배포하는 프로세스를 의미한다.
Jenkins란?
Jenkins는 CI/CD를 위한 오픈 소스 도구로, 소프트웨어 개발 생명주기를 자동화한다.
Jenkins를 사용하면 코드 변경 사항이 발생할 때마다 자동으로 빌드, 테스트, 배포 과정을 수행할 수 있기 때문에 개발자는 코드 변경에 따른 빌드 및 테스트 과정을 수동으로 수행할 필요가 없어지며, 신속하게 개발에 대한 코드 검증 및 배포할 수 있고, 이 과정에서 휴먼 에러를 방지할 수 있다.
Jenkins는 다양한 플러그인을 지원하기 때문에 사용자가 필요에 맞게 플러그인을 통합하여 CI/CD 환경을 확장할 수 있다. 하지만 다양한 플러그인을 사용하여 파이프라인을 구성하면 구성이 복잡해질 수 있고, 이에 따른 버전 호환성, 보안 사항 등을 고려해야 하기 때문에 유지보수에 어려움을 겪을 수 있다. 그렇기 때문에 환경에 필요한 플러그인을 잘 선택하여 사용하는 것이 중요하다.
Jenkins는 Master/Slave라는 구조로 동작한다.
- Master는 controller라고 불리며, Jenkins slave를 관리하고 작업 스케줄링과 slave 모니터링을 포함하여 다양한 작업을 관리한다.
- Slave는 agent라고 불리며, 실제 파이프라인 작업을 수행한다. 다양한 환경에서 작동하며 작업을 분산시켜 부하를 감소시키는 역할을 담당한다. agent는 로컬 또는 클라우드 컴퓨터를 통해 Jenkins controller에 연결될 수 있어 다양한 유연성을 제공한다.
아래 그림을 통해 Jenkins의 동작을 이해할 수 있다.
- SCM (Source Code Management)
Jenkins는 코드가 보관되어 있는 Git 저장소에 코드 변경사항을 감지하여 자동으로 파이프라인을 수행할 수 있다. Git 저장소에는 소스 코드뿐 아니라 Jenkins 스크립트 실행에 필요한 'Jenkinsfile'과 Kubernetes 배포에 필요한 'Helm Chart'와 같은 파일들도 보관될 수 있다. - CI (Continuous Integration)
파이프라인이 실행되면 Agent를 통해 파이프라인 스크립트에 정의된 작업(job)이 실행된다. 이때 소스 코드 빌드, 코드 검증 테스트, 도커 이미지 빌드, 도커 이미지 검증 등 여러 작업이 순차적으로 진행된다. - CD (Continuous Deployment)
파이프라인에서 정의한 배포 작업에 따라 애플리케이션은 쿠버네티스 환경에 배포된다. 이 배포는 Jenkins를 통해 수행될 수도 있고, Kubernetes 환경에서 널리 사용되는 ArgoCD와 같은 도구를 활용하여 수행될 수도 있다.
이번 글에서는 Kubernetes 환경에서 Jenkins를 구성하고, 간단한 파이프라인 설정을 통해 Jenkins의 사용법과 동작을 이해하고자 한다.
전제 조건
- AWS EKS 클러스터
- Helm CLI 도구
설치 환경
- AWS EKS : v1.27.7
- Helm : v3.8.2
설치 버전
- Jenkins Helm Chart 버전 : 4.11.1
- Jenkins Helm APP 버전 : 2.426.2
1. Jenkins 구성
Jenkins의 Helm Chart를 등록한다.
$ helm repo add jenkinsci https://charts.jenkins.io
$ helm repo update
Jenkins의 Helm Chart values 파일을 작성한다.(jenkins-values.yaml)
## jenkins-values.yaml
persistence:
enabled: true
accessMode: "ReadWriteOnce"
size: "10Gi"
controller:
adminSecret: false
adminSecret을 true로 설정하면 Kubernetes secrets으로 관리자의 ID/PASSWD를 관리하게 되는데, Jenkins Ui에 접속하여 변경해도 Jenkins가 재생성되면 초기 Secrets에 설정되어 있는 비밀번호로 되돌아가 다시 변경해야 하는 번거로움이 있기 때문에 비활성화 후 Jenkins Ui에서 생성하여 진행한다. Secrets을 변경하여 비밀번호를 변경하는 방법이 있지만 가장 심플하게 테스트하기 위해 위와 같이 설정한다.
위에서 생성한 values파일(jenkins-values.yaml)을 사용하여 설치한다.
$ helm install jenkins -n jenkins jenkinsci/jenkins -f jenkins-values.yaml
[에러 상황 추가]
Case 1.
만약 아래와 같은 에러 메세지가 발생하면서 Pod가 Error 상태로 남아있다면 "configuration-as-code" 플러그인 버전을 올려야 한다.
- 1670.v564dc8b_982d0 → 1775.v810dc950b_514
에러 메세지
Multiple plugin prerequisites not met: Plugin workflow-aggregator:596.v8c21c963d92d (via pipeline-model-definition:2.2183.vb_36481468374->git-client:4.7.0) depends on configuration-as-code:1775.v810dc950b_514, but there is an older version defined on the top level - configuration-as-code:1670.v564dc8b_982d0, Plugin git:5.1.0 (via git-client:4.7.0) depends on configuration-as-code:1775.v810dc950b_514, but there is an older version defined on the top level - configuration-as-code:1670.v564dc8b_982d0
Case 2.
Pipeline 동작에서 Kubernetes agent가 동작하다 안 하다 반복한다면 "kubernetes" 플러그인 버전을 올려서 해결한다.
- kubernetes:4029.v5712230ccb → 4186.v1d804571d5d4
다음과 같이 Helm Chart value 파일 수정한다.
# jenkins-values.yaml
#...생략
controller:
#...생략
# 추가
installPlugins:
- kubernetes:4029.v5712230ccb_f8
- workflow-aggregator:596.v8c21c963d92d
- git:5.1.0
- configuration-as-code:1775.v810dc950b_514
Helm Chart 업그레이드 진행 후 Pod를 restart 한다.
# Helm Chart 업그레이드
$ helm upgrade -i -n jenkins jenkins -f jenkins-values.yaml jenkinsci/jenkins
# Jenkins Pod restart
$ kubectl rollout restart -n jenkins sts jenkins
2. Jenkins Ui 접속
포트 포워딩을 통해 Jenkins Ui에 접근한다.
- $ kubectl port-forward --address=0.0.0.0 -n jenkins jenkins-0 8080:8080
Jenkins Ui가 나오면 초기 관리자 정보를 기입하고 생성한다.
이후 생성한 관리자 ID/PASSWD로 로그인하여 Jenkins에 로그인한다.
3. Jenkins agent 설정
위와 같이 Helm Chart로 설치하면 Kubernetes 플러그인 설치와 함께 agent를 위한 "Cloud kubernetes Configuration" 설정이 기본적으로 되어 있다. 해당 설정은 쿠버네티스 클러스터에 Pod로 agent를 생성하여 Pod의 컨테이너에서 Job을 수행할 수 있게 해 준다.
해당 설정은 Jenkins의 Helm Chart values 파일에서 확인할 수 있다.
## Jenkins values info
...
kubernetesURL: "https://kubernetes.default"
# 플러그인 설치 설정
controller:
...
installPlugins:
- kubernetes:4029.v5712230ccb_f8
- workflow-aggregator:596.v8c21c963d92d
- git:5.1.0
- configuration-as-code:1670.v564dc8b_982d0
...
# agent 설정
agent:
enabled: true
defaultsProviderTemplate: ""
jenkinsUrl:
jenkinsTunnel:
kubernetesConnectTimeout: 5
kubernetesReadTimeout: 15
maxRequestsPerHostStr: "32"
retentionTimeout: 5
waitForPodSec: 600
namespace:
jnlpregistry:
image: "jenkins/inbound-agent"
tag: "3192.v713e3b_039fb_e-5"
...
실제로 Jenkins Ui에서 위 설정이 잘 들어가 있는지 확인하고 없으면 설정을 추가해줘야 한다.
먼저, Kubernetes 플러그인 설치를 확인한다.
- Dashboard > Jenkins 관리 > Plugins > Installed plugins > "kubernetes" 입력
agent 설정을 확인한다. 아래와 같이 kubernetes 이름의 Clouds가 있어야 한다.
- Dashboard > Jenkins 관리 > Clouds
위 사진에서 kubernetes를 누르고 들어가 Configure를 누른다.
아래와 같이 Kubernetes Cloud details, Pod Templates 정보가 보인다.
- Kubernetes Cloud details는 Jenkins가 Kubernetes API와 통신하기 위한 설정이다.
- Pod Templates은 Job을 수행할 agent인 Pod와 컨테이너에 대한 설정이다.
Kubernetes Cloud details을 누르면 아래와 같이 나온다.
Kubernetes API URL 정보, Jenkins agent가 실행될 namespace가 설정되어 있다.
좀 더 아래로 내려보면 Jenkins URL, tunnel 정보 등 설정되어 있다.
아래 정보는 Jenkins와 agent 간 통신에 필요하기 때문에 정확한 정보가 입력되어 있어야 한다. 특히, Jenkins tunnel에는 "http://"를 제외하고 입력해야 하는 것에 주의하자.
아래는 Kubernetes 플러그인에 의해 생성된 Pod에 들어가는 Label 정보이다. 해당 정보는 동시 실행 제한 동작에 관여한다.
다음은 Pod Templates 내용이다.
agent가 실행되면 생성될 Pod 이름, Namespace, Labels 정보이다. 해당 Labels 정보로 Jenkins Pipeline을 실행할 때 지정하여 사용할 수 있다.
아래는 agent Pod에서 생성될 컨테이너 정보이다. 만약 두 개 이상의 컨테이너를 설정하면 Pod에 멀티 컨테이너 형식으로 동작하여 각 컨테이너마다 실행할 Stage를 설정할 수 있다.
4. 파이프라인 실행
Kubernetes 관련 agent 설정이 완료되었으면 간단한 파이프라인을 실행해 보자.
- Dashboard > 새로운 Item
test-prj라는 이름으로 프로젝트를 생성한다.
- "Project 이름 입력" > Pipeline 선택 > OK 클릭
맨 아래에 보면 Pipeline 부분에 실행할 파이프라인 스크립트를 작성 후 저장한다.
- 파이프라인 스크립트에서 'node'에는 위 Pod Template에서 설정한 Pod Label인 'jenkins-jenkins-agent'를 입력한다.
podTemplate {
node('jenkins-jenkins-agent') {
stage('Run shell') {
sh 'hostname'
}
}
}
지금 빌드를 클릭하여 실행하면 아래와 같이 파이프라인 job이 실행되는 걸 확인할 수 있다.
위 실행 중인 Job(#15)을 클릭하면 Job에 대한 상세 내용을 확인할 수 있다.
'Console Output'을 클릭하면 '콘솔 출력' 부분에 파이프라인 동작에 대한 상세 정보를 확인할 수 있다.
파이프라인 스크립트 'Run shell' stage에서 실행한 "sh hostname"부분에 대한 출력을 확인할 수 있다.
default-xxxxx는 파이프라인을 위해 agent pod의 이름이다.
실제로 위 hostname을 Kubernetes의 kubectl 명령으로 확인해 보면 실행된 agent pod의 이름과 일치하는 걸 확인할 수 있다. agent pod의 이름을 보면 위 "Cloud Kubernetes Configuration"에서 Pod Template에 설정한 이름이 Prefix로 붙어 생성되었다.
$ kubectl get po -n jenkins -w
NAME READY STATUS RESTARTS AGE
jenkins-0 2/2 Running 0 135m
default-895zl 0/1 Pending 0 0s
default-895zl 0/1 ContainerCreating 0 0s
default-895zl 1/1 Running 0 2s
default-895zl 1/1 Terminating 0 10s
해당 Pod를 yaml로 출력하여 더 자세히 확인해 보자.
"Cloud Kubernetes Configuration"의 Pod Template에 설정한 spec 정보로 생성된 걸 확인할 수 있다.
$ kubectl get po -n jenkins default-895zl -oyaml
apiVersion: v1
kind: Pod
metadata:
labels:
jenkins/jenkins-jenkins-agent: "true"
jenkins/label: jenkins-jenkins-agent
jenkins/label-digest: 500b4f18aee87616849e4f4c2435020898e34aa0
name: default-895zl
namespace: jenkins
spec:
containers:
- args:
- 6217ff6621617f8a5d7c55194966e5d5ca509a7fe4f476776fd5a9aa8f077392
- default-895zl
env:
- name: JENKINS_SECRET
value: 6217ff6621617f8a5d7c55194966e5d5ca509a7fe4f476776fd5a9aa8f077392
- name: JENKINS_TUNNEL
value: jenkins-agent.jenkins.svc.cluster.local:50000
- name: JENKINS_AGENT_NAME
value: default-895zl
- name: JENKINS_NAME
value: default-895zl
- name: JENKINS_AGENT_WORKDIR
value: /home/jenkins/agent
- name: JENKINS_URL
value: http://jenkins.jenkins.svc.cluster.local:8080/
image: jenkins/inbound-agent:3192.v713e3b_039fb_e-5
imagePullPolicy: IfNotPresent
name: jnlp
resources:
limits:
cpu: 512m
memory: 512Mi
requests:
cpu: 512m
memory: 512Mi
...
이제 위 "Cloud Kubernetes Configuration"의 Pod Template에 컨테이너 설정을 하나 추가하여 멀티 컨테이너로 실행해 보자.
- Dashboard > Jenkins 관리 > Clouds > kubernetes > Configure
Add Container를 눌러 컨테이너 한 개를 더 추가한다.
아래와 같이 nginx 컨테이너를 추가하였다.
위에서 생성한 'test-prj' 프로젝트에서 아래 파이프라인 스크립트를 설정하고 빌드를 실행한다.
pipeline {
agent {
kubernetes {
label 'jenkins-jenkins-agent'
}
}
stages {
stage('nginx') {
steps {
container('nginx') {
sh 'hostname'
}
}
}
}
}
Cloud Kubernetes Configure에서 설정한 "label = jenkins-jenkins-agent"인 pod에 설정되어 있는 2개의 컨테이너가 실행되는데, 그중 nginx 컨테이너에서 hostname이라는 쉘 명령어를 실행한다는 파이프라인 스크립트 내용이다.
실행 후 Console Output을 확인해 보면 아래와 같이 두 개의 컨테이너가 실행된 걸 확인할 수 있다.
또는 파이프라인에 명시적으로 Pod를 yaml 형태로 선언하여 사용할 수 있다.
pipeline {
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: nginx
command:
- sleep
args:
- 99d
image: nginx:latest
'''
}
}
stages {
stage('nginx') {
steps {
container('nginx') {
sh 'echo "nginx container"'
}
}
}
}
}
위 파이프라인 스크립트를 실행한 후 Console Output을 확인해 보면 아래와 같다.
해당 설정은 Project 정보로 Pod 이름이 생성되고, 기본적으로 agent 실행 시 내장되어 있는 'jnlp' 컨테이너에 yaml로 선언한 nginx 컨테이너가 추가로 생성되어 멀티 컨테이너로 배포된다. "Cloud Kubernetes Configuration"의 Pod Template의 Pod 정보로 구동되는 것이 아니기 때문에 기본 'jnlp' 컨테이너의 이미지 버전 등 일부 값이 다른 걸 확인할 수 있다.
마무리
Jenkins는 강력한 CI/CD 도구로 다양한 기능과 유연성을 제공하기 때문에 Jenkins를 활용한 CI/CD 파이프라인을 구축하는 것은 효율적인 개발 및 배포를 위해 매우 중요하다. 다양한 도구들과 통합하여 CI/CD 파이프라인을 구축하면서 개발 프로세스 표준화하고 자동화하여 빠르게 안정적인 소프트웨어를 배포할 수 있는 환경을 만들어 보자.
'CICD > Jenkins' 카테고리의 다른 글
Jenkins로 가상 머신(VM) 환경에 배포하기 (with Pipeline) (0) | 2024.06.20 |
---|
댓글