2024.03.11(월)
🎬CI/CD 시나리오
- CI (Continuous Integration; 지속적 통합) 단계
- 일반적으로 개발자가 소스 코드를 커밋하고 푸시하는 것으로 시작
- 응용 소프트웨어를 자동으로 빌드, 통합
- (자동) 테스트를 통하여 배포할 수 있는 상태임을 확인
- CD (Continuous Delivery/Deployment; 지속적 인도) 단계
- CI 단계에서 소프트웨어가 배포 가능한 상태임을 확인하는 것으로 시작
- 응용 소프트웨어를 컨테이너 이미지로 만들어 냄
- 포드, 티플로이먼트, 서비스 등 다양한 오브젝트 조건에 맞추어 (미리 설정한 파일을 통해) 배포
- 자바(Java)로 작성된 오픈 소스 자동화 서버
- 지속적 인도 프로세스를 구축하는 데 널리 이용됨: 유연성과 확장성이 뛰어남
- 허드슨(Hudson)이라는 이름으로 공개된 바 있으나 오라클이 허드슨을 인수하고 젠킨스(Jenkins)로 이름을 바꿈
💡특징
- 다양한 프로그래밍 언어 지원
- 플러그인을 통한 확장
- 사용자가 직접 플러그인을 작성해 젠킨스의 기능을 확장하는 것도 가능
- 이식성
- 여러 종류의 컴퓨터에서뿐만 아니라 컨테이너 및 클러스터 환경에도 부드럽게 적용
- 대부분의 소스 관리 시스템 지원
- 분산 처리 지원
- 마스터/슬레이브 구조를 채택하여 여러 노드에서 작업 수행
- 코드로 파이프라인 구성
🏗️아키텍처

- 마스터-슬레이브 구조
- 슬레이브(slave)는 에이전트(agent)라고 부르기도 함
- 마스터
- 빌드 시작 트리거 포착 (ex: 코드 커밋)
- 알림 (ex: 빌드 실패를 사용자에게 slack으로 전달)
- 클라이언트와 통신하며 HTTP 요청 처리 (웹에서 상호작용 가능)
- 에이전트에서 실행 중인 작업의 우선순위 조정 등 빌드 환경 관리
- 에이전트
↔️수평적 확장
- 조직(개발팀, 테스트팀, 데브옵스팀)이 늘어날 때마다 마스터 인스턴스의 수를 늘려가는 방식

- 통합 자동화가 복잡해진다는 단점이 있으나
- 마스터 역할을 하는 컴퓨터의 하드웨어 사양에 대한 부담 감소
- 팀마다 각기 다른 설정이 가능
- 팀 전용 마스터 인스턴스가 있으므로 팀워크와 업무 효율이 높아짐
- 마스터 인스턴스 하나에 문제가 생겨도 다른 팀에 끼치는 영향이 최소화됨
📥k8s 클러스터에 소프트웨어 설치
대표적인 k8s용 패키지 매니저
- 오브젝트 배포에 필요한 사양이 이미 정의된 차트(chart)를 이용하여 패키지를 검색하고 내려받아 설치
- 공개되어 있는 소프트웨어 패키지를 k8s에 배포하는 것 외에도 배포 효율화를 위해 많이 이용되는 방법
- RedHat 계열의 rpm, yum 또는 Debian 계열의 apt와 비슷한 방식으로 동작
- 설치
- chocolatey 설치 (Windows PowerShell 관리자 권한으로)
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1'))
- chocolatey로 helm 설치
choco install kubernetes-helm
- 설치 확인
helm
✅helm으로 젠킨스 설치
⚙️젠킨스 기초 설정
🗝️관리자 비밀번호 알아내기
🔑관리자 비밀번호 변경
⚠️ Jenkins 메뉴 (Users > Jenkins Admin > Configure)에서 변경한 사항은 포드가 새로 실행할 때 적용이 되지 않아 원래의 비밀번호로 되돌려짐
- k8s 오브젝트(secret)를 변경해서 admin(Jenkins Admin) 계정의 비밀번호를 적용해야 함
jenkins-0
라는 이름의 pod를 삭제 → k8s에 의해 pod 재시작(STATUS = Ready까지 시간이 좀 걸림) → 변경된 admin 비밀번호로 로그인 가능!
🔤언어 설정
- 영어 사용
- Manage Jenkins > Plugins > Available plugins에서 플러그인 “Locale” 검색 & 설치
- Manage Jenkins > System > Locale에
en_US
입력 & 박스 체크하고 저장
⌚시간대 설정
- 자신이 살고 있는 지역의 시간대로 서버 시간대를 설정
- Manage Jenkins > People > [계정 선택] > Configure > User Defined Time Zone에서
Asia/Seoul
설정 후 저장
🌱젠킨스 기본 사용법 맛보기
💬Hello World
사실은 빌드 및 배포 등에 대해서는 아직 아무런 하는 일이 없는 예제이지만 Jenkins “Item”을 생성하고 이것이 어떤 방식으로 동작하는지를 엿보는 목적으로 테스트
Jenkins는 “Item” 단위로 프로젝트를 관리 (웹 사용자 인터페이스를 이용해 설정할 수 있고 상태를 열람할 수 있도록 되어 있음)
🧐클러스터 내 에이전트 동작 관찰
- Jenkins 마스터는 k8s 클러스터 내에 동적으로 에이전트를 생성, 즉 프로비저닝(provisioning)
- 이를 위해서 k8s 플러그인이 설치되어 있어야 하며, 적절한 설정도 되어 있어야 하지만 helm을 이용해 Jenkins를 설치할 때 이러한 설정들도 기본값으로 구성되어 있음
- 동적 에이전트 프로비저닝 테스트
- (인위적으로) 빌드 과정이 오래 걸리는 파이프라인 구성
- 순차적으로 여러 차례의 빌드 스케줄 (▶️Build Now 버튼 여러 번 클릭)
- 지금은 수동으로 빌드를 개시했지만 보통은 SCM(ex. git) repository에 발생하는 이벤트(ex. push)에 의해 트리거됨
- Jenkins의 동작과 k8s 클러스터의 상태 관찰

- Dashboard > Manage Jenkins
- Plugins에서 Kubernetes plugin이 이미 설치되어 있는 것을 확인할 수 있다. (thanks to helm)
- Clouds > kubernetes > Pod Templates에는 default라는 pod template이 존재한다.
- 기본적으로 에이전트가 작업을 수행할 때 이 pod template을 가지고 pod가 만들어진다.
- 생성되는 pod 안에는 container가 한 개 들어가고 명시된 도커 이미지를 기반으로 빌드된다.
⌨️젠킨스 프로젝트 실습
간단한 웹 응용을 젠킨스에 의해 빌드 및 배포되도록 설정해보기
🕺Freestyle project 이용
- 젠킨스의 빌드 환경 설정
- ⛔Build Now 했을 때 실패
Started by user Jenkins Admin
Running as SYSTEM
Agent default-7p4m7 is provisioned from template **default**
---
⁝
+ docker build -t do0ori/simple-echo .
/tmp/jenkins7904157774705426922.sh: 2: **docker: not found**
Build step 'Execute shell' marked build as failure
Finished: FAILURE
- default pod template의 도커 이미지에는 Docker CLI가 설치되어 있지 않기 때문 ⇒ Custom Agent를 등록하고 사용하도록 해야 함!
- Dashboard > Manage Jenkins > Clouds > kubernetes > Pod Templates에서 Add a pod template 버튼 클릭
- ⭐신규 추가하는 pod template에는 두 개의 컨테이너 스펙이 포함되도록 할 예정
- 빌드 작업을 실행할 컨테이너 (이름:
jnlp
)
- JNLP(Java Network Launch Protocol)을 따라 Jenkins 마스터와 작업 조율하면서 빌드 작업 실행
- docker CLI와 kubectl CLI를 갖추고 있음
- 지금은 미리 만들어진 이미지 이용: sheayun/jnlp-agent-sample
- 도커 데몬(daemon)을 실행하는 컨테이너 (이름:
dind
)
- Docker in Docker
- 어느 클러스터에 있더라도 통일된 docker build 환경을 제공하기 위해 독립된 컨테이너로 제공 (ex. 현재 docker desktop으로 클러스터 제공하고 있는 호스트의 daemon을 이용하지 않음)
- 이미지는 docker:latest 사용
- New pod template settings 다음과 같이 작성
Name | sample |
---|
Namespace | default |
Labels | jenkins-sample-agent |
Usage | Only build jobs with label expressions matching this node |
Pod template to inherit from | default |
-
Container에서 Add Container > Container Template
Name | jnlp |
---|
Docker image | sheayun/jnlp-agent-sample |
- Command to run과 Arguments to pass to the command 항목은 비우기
- Environment Variables > Add Environment Variable > Environment Variable (같은 pod에서 실행되는 다른 container에 docker host가 있음을 알려주기 위한 설정)
Key | DOCKER_HOST |
---|
Value | tcp://localhost:2375 |
-
Container에서 Add Container > Container Template
Name | dind |
---|
Docker image | docker:latest |
Command to run | /usr/local/bin/dockerd-entrypoint.sh |
- Arguments to pass to the command 항목은 비우기
- Environment Variables > Add Environment Variable > Environment Variable (Docker deamon이 해당 환경 변수를 가지게 해 TLS 인증을 하지 않도록 하는 설정)
Key | DOCKER_TLS_CERTDIR |
---|
Value | 비우기 |
- Advanced에서 Run in privileged mode 체크
- Create를 눌러서 설정 저장
- Dashboard > simple-echo-1 > Configuration
- General 항목의 Restrict where this project can be run 체크
- Label Expression에
jenkins-sample-agent
입력 (아까 만든 pod template의 label 이름)
- Save로 저장
- ⛔Build Now 했을 때 실패
Started by user Jenkins Admin
Running as SYSTEM
Agent sample-trtm6 is provisioned from template **sample**
---
⁝
**denied: requested access to the resource is denied**
Build step 'Execute shell' marked build as failure
Finished: FAILURE
- template은 우리가 만든 sample을 잘 사용하고 있으나, docker push를 할 때 docker hub registry에 대한 권한/인증 정보가 없기 때문 ⇒ 추가 설정 필요
- Dashboard > Manage Jenkins > Credentials
- Stores scoped to Jenkins에서 Domains 항목의 (global) 클릭 → Add Credentials 클릭
Kind | Username with password |
---|
Username | Docker Hub ID |
Password | Docker Hub Password |
ID | dockerhub-credentials |
Description | DockerHub credentials for account |
- Create로 생성
- Dashboard > simple-echo-1 > Configuration
- ✅Build Now 했을 때 성공! docker hub에 잘 push된 것을 확인할 수 있다.

- kubectl을 이용해 레지스트리로부터 이미지를 가져다가 같은 클러스터에 배포 (및 노출)
- ⛔Build Now 했을 때 실패
+ kubectl apply -f deployment.yaml
**Error from server (Forbidden): error when retrieving current configuration of:**
Resource: "apps/v1, Resource=deployments", GroupVersionKind: "apps/v1, Kind=Deployment"
Name: "dpy-hname", Namespace: "default"
from server for: "deployment.yaml": deployments.apps "dpy-hname" is forbidden: User "system:serviceaccount:default:default" cannot get resource "deployments" in API group "apps" in the namespace "default"
Build step 'Execute shell' marked build as failure
Finished: FAILURE
- kuternetes 클러스터 접근 권한이 없기 때문 ⇒ 추가 설정 필요
~/.kube/config
파일을 복사해 kubeconfig라고 이름 짓고 server: https://kubernetes.default
로 수정
- Dashboard > Manage Jenkins > Credentials
- Stores scoped to Jenkins에서 Domains 항목의 (global) 클릭 → Add Credentials 클릭
Kind | Secret file |
---|
Scope | Global |
File | kubeconfig 파일 추가 |
ID | KUBECONFIG |
Description | kubeconfig file for docker-desktop |
- Create로 생성
- Dashboard > simple-echo-1 > Configuration
- Build Environment 항목의 Configure Kubernetes CLI (kubectl) with multiple credentials 체크
Credentials | 아까 생성한 kubeconfig 선택 |
---|
- Save로 저장
- ✅Build Now 했을 때 성공!! 드디어 성공..
+ kubectl apply -f deployment.yaml
deployment.apps/**dpy-hname** created
+ kubectl apply -f service.yaml
service/**svc-hname** created
[kubernetes-cli] kubectl configuration cleaned up
Finished: SUCCESS

🚀Pipeline 이용
- 앞선 프로젝트와 비슷한 것을 파이프라인으로 실습
- 간단한 파이프라인을 구성하고 이에 의해서 다음 과정이 수행되도록 실험
- docker build && push
- kubectl에 의해 deployment 및 service 생성
- 위 파이프라인 정의를 Jenkinsfile로 만들고 이것을 리포지토리에 등록하여 빌드 과정 자동화
- 젠킨스 접속(http://localhost:8080/) > Dashboard 왼쪽 위 ➕New Item > Item name 입력 (simple-echo-2), Pipeline 선택 후 OK
- Pipeline script를 다음과 같이 작성 (Freestyle project에서 했던 과정을 모두 script로 만들었다고 보면 됨)
pipeline {
environment {
dockerImageName = "do0ori/simple-echo"
}
agent {
kubernetes {
yaml '''
apiVersion: v1
kind: Pod
spec:
containers:
- name: jnlp
image: sheayun/jnlp-agent-sample
env:
- name: DOCKER_HOST
value: "tcp://localhost:3275"
- name: dind
image: docker:latest
command:
- /usr/local/bin/dockerd-entrypoint.sh
env:
- name: DOCKER_TLS_CERTDIR
value: ""
securityContext:
privileged: true
'''
}
}
stages {
stage("git scm update") {
steps {
sh "git clone https://github.com/do0ori/jenkins-web-practice.git ."
}
}
stage("docker build && push") {
steps {
script {
dockerImage = docker.build dockerImageName
docker.withRegistry(
"https://registry.hub.docker.com",
"dockerhub-credentials"
) {
dockerImage.push("latest")
}
}
}
}
stage("deploy application on kubernetes cluster") {
steps {
withKubeConfig([
credentialsId: "KUBECONFIG",
serverUrl: "https://kubernetes.default",
namespace: "default"
]) {
sh '''
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
'''
}
}
}
}
post {
always {
sh "docker logout"
}
}
}
- Save로 저장
- ✅Build Now 했을 때 성공
- script에서
sh "git clone https://github.com/do0ori/jenkins-web-practice.git ."
를 checkout scm
으로 수정해서 Jenkinsfile
이라는 이름의 파일에 저장하고 github에 올리기
- Pipeline의 Definition을 Pipeline script에서 Pipeline script from SCM으로 변경
- ✅Build Now 했을 때 성공
🔧Jenkins 파이프라인의 구성
- Jenkins 파이프라인 정의에는 두 가지 방식이 있음
- 선언적(declarative) 방식
- 우리가 지금까지 실습한 방법
- 빌드 단계(stage)를 선언적으로 정의하는 방식
- 스크립트(scripted) 방식
- 아직까지는 마주친 적 없지만 앞으로 만나보게 될 것
- 빌드 단계(stage)를 절차적으로 기술하는 (스크립트를 실행하는) 방식
- 두 가지 방식으로 파이프라인 실습을 해봄
- Jenkins UI에서 선언적 스크립트를 입력하는 방식
- GitHub repo에 Jenkinsfile로 파이프라인 스크립트를 저장하는 방식