서버 소프트웨어를 개발 후 배포하는 방법은 여러가지가 있습니다. 단순히 로컬에서 빌드 후 결과파일을 직접 서버에 copy 하는 방법에서 시작해서 CI/CD 기반의 툴(Jenkins, CircleCI 등)을 활용해 그 과정을 자동화하는 방법까지 다양합니다. 이번 글에서는 많이 쓰이는 CI/CD 툴인 Jenkins 를 Kubernetes Cluster 상에 설치하는 과정을 진행해보겠습니다.

Helm 차트를 활용해 좀 더 손쉽게 Jenkins 를 Kubernetes Cluster 상에 설치하는 방법도 있으나, Kubernetes 에 익숙해지는 과정에 있는 만큼, kubectl 과 yaml 파일 기반으로 직접 설치해 보도록 하겠습니다.

  • Jenkins Docker Image 생성

Raspberry pi 는 arm 기반 아키텍쳐인데 공식적인 jenkins docker image 는 아직 arm 을 지원하지 않는 듯 합니다. 따라서 이미지를 직접 만들도록 하겠습니다. 먼저 Dockerfile 을 작성합니다.

FROM ubuntu:20.04

RUN apt-get update && apt-get install -y \
gnupg \
wget \
&& rm -rf /var/lib/apt/lists/*

RUN wget -q -O - https://pkg.jenkins.io/debian-stable/jenkins.io.key | apt-key add - \
&& sh -c 'echo deb https://pkg.jenkins.io/debian-stable binary/ > /etc/apt/sources.list.d/jenkins.list'

ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y \
jenkins \
openjdk-8-jdk \
&& rm -rf /var/lib/apt/lists/*

ENTRYPOINT service jenkins start && tail -f /var/log/jenkins/jenkins.log

ubuntu 20.04 LTS 를 기반으로 jenkins 를 설치하는 간단한 내용입니다.

$ sudo docker build -t truelifer-registry.com:8090/jenkins_for_raspberry_pi_4:$(git log -1 --format=%h) .
$ sudo docker build -t truelifer-registry.com:8090/jenkins_for_raspberry_pi_4:latest .

위에서 작성한 Dockerfile 을 가지고 Image 를 빌드합니다. 태그는 최신 git commit 의 축약된 hash 값 및 latest 로 지정해줍니다.

$ sudo docker push truelifer-registry.com:8090/jenkins_for_raspberry_pi_4:$(git log -1 — format=%h)
sudo docker push truelifer-registry.com:8090/jenkins_for_raspberry_pi_4:latest

위에서 build 한 최신버전의 image 를 Registry 에 push해줍니다.

$ sudo docker build -t truelifer-registry.com:8090/daum_news_crawler:$(git log -1 --format=%h) .$ sudo docker build -t truelifer-registry.com:8090/daum_news_crawler:latest .
  • Namespace 생성
$ kubectl create namespace jenkins

먼저 jenkins 를 위한 별도의 namespace 를 생성해줍니다.

namespace/jenkins created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • StorageClass 생성

이전 글에서 이미 만들어 놓은 StorageClass 를 사용합니다.

  • Persistent Volume 생성
$ sudo mkdir -p /workspace/data/kubernetes/pv/jenkins
$ sudo chmod 777 /workspace/data/kubernetes/pv/jenkins

우선 worker1, worker2 각각 위의 명령을 통해 jenkins의 데이터가 저장될 폴더를 생성하고 적절한 권한을 부여합니다. 우선은 777으로 권한을 설정했으나 향후 RBAC 기반으로 전체적인 권한체계를 구성할 예정입니다.

$ vi pv-jenkins.yaml

vi 편집기로 pv 생성을 위한 yaml 파일을 생성합니다.

apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-jenkins
spec:
capacity:
storage: 4Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /workspace/data/kubernetes/pv/jenkins
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- worker2

4GB 의 용량으로 설정하고 하나의 pod 만 접근하기 때문에 ReadWriteOnce, pod 가 삭제되더라도 데이터 보존을 위해 Retain 전략으로 설정합니다. NodeAffinity 설정을 통해 worker2 에 pv 를 생성하도록 합니다. 즉, Jenkins pv 는 worker2 노드에 만들어지고 Jenkins pod 역시 worker2 노드에 생성되게 됩니다.

$ kubectl apply -f ./pv-jenkins.yaml -n jenkins

kubectl 명령을 통해 pv 를 생성해 줍니다.

persistentvolume/pv-jenkins created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

  • Persistnet Volume Claim 생성
$ vi pvc-jenkins.yaml

vi 편집기로 pvc 생성을 위한 yaml 파일을 생성합니다.

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-jenkins
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 4Gi

위에서 생성한 local-storage 타입으로 4GB 의 용량의 pvc 를 생성합니다.

$ kubectl apply -f ./pvc-jenkins.yaml -n jenkins

kubectl 명령을 통해 pv 를 생성해 줍니다.

persistentvolumeclaim/pvc-jenkins created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get pvc pvc-jenkins -n jenkins

생성된 pvc 의 상태를 조회해봅니다.

NAME          STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS    AGE
pvc-jenkins Pending local-storage 4m10s$ kubectl get pv pv-jenkins -n jenkins

생성된 pv 의 상태도 조회해봅니다.

NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS    REASON   AGE
pv-jenkins 4Gi RWO Retain Available local-storage 6m59s

VolumeBindingMode 를 WaitForFirstConsumer로 설정했기 때문에 Status 가 아직 Available 인 점을 확인할 수 있습니다.

  • Deployment 생성
$ vi deployment-jenkins.yaml

vi 편집기로 deployment 생성을 위한 yaml 파일을 생성합니다.

apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins
template:
metadata:
labels:
app: jenkins
spec:
containers:
- name: jenkins
image: truelifer-registry.com:8090/jenkins_for_raspberry_pi_4:latest
ports:
- containerPort: 8080
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
- name: docker-socket
mountPath: /var/run/docker.sock
- name: docker-binary
mountPath: /usr/bin/docker
volumes:
- name: jenkins-home
persistentVolumeClaim:
claimName: pvc-jenkins
- name: docker-socket
hostPath:
path: /var/run/docker.sock
- name: docker-binary
hostPath:
path: /usr/bin/docker

위에서 생성한 pv, pvc 을 기반으로 deployment yaml 파일을 작성합니다.

$ kubectl apply -f ./deployment-jenkins.yaml -n jenkins

kubectl 명령을 통해 deployment 를 생성해 줍니다.

deployment.apps/jenkins created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get pods -n jenkins

생성된 pod 를 조회해봅니다.

NAME                       READY   STATUS    RESTARTS   AGE
jenkins-64c46866bb-5xwsk 1/1 Running 0 1m10

Status 가 Running 상태로 정상적으로 배포되었음을 확인할 수 있습니다.

  • Service 생성
$ vi service-jenkins.yaml

vi 편집기로 service 생성을 위한 yaml 파일을 생성합니다.

apiVersion: v1
kind: Service
metadata:
name: jenkins
spec:
type: NodePort
ports:
- port: 8080
nodePort: 30001
targetPort: 8080
selector:
app: jenkins

Jenkins 를 외부에서 웹브라우저를 통해 접속하기 위해 NodePort 타입으로 설정하도록 합니다. Node 의 Port 기본 범위는 30000 ~ 32767로 정해져있습니다. 저는 30001 포트를 사용하도록 하겠습니다.

$ kubectl apply -f ./service-jenkins.yaml

kubectl 명령을 통해 service 를 생성해 줍니다.

service/service-jenkins created

정상적으로 생성이 되었다면 위와 같은 메세지가 출력됩니다.

$ kubectl get services -n jenkins

생성된 service를 조회해봅니다.

NAME      TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
jenkins NodePort 10.108.24.158 <none> 8080:30001/TCP 12s

NodePort 타입으로 service 가 정상적으로 생성되었음을 확인할 수 있습니다.

  • IPTIME Port Fowarding 설정

위 과정을 통해 Kubernetes Cluster 상에 Jenkins 를 배포하는 과정은 완료가 되었습니다. 외부에서 접속하기 위해서는 추가적으로 한 가지 작업이 더 남아있습니다. 공유기의 Port Fowarding 설정입니다. Chrome 브라우저를 통해 IPTIME 관리자페이지(192.168.0.1) 로 접속한 후 로그인을 합니다.

jenkins-rasp-worker2 라는 이름으로 port fowarding rule 을 추가합니다. 외부에서 {도메인이름}:8080 으로 오는 접속을 내부의 Node(worker2 의 30001번 포트)로 연결해 주는 작업입니다.

  • 외부접속 테스트 및 로그인

이제 모든 설정이 완료되었습니다. Chrome 웹 브라우저를 통해 정상적으로 접속이 되는지 확인해보도록 하겠습니다.

Jenkins 화면이 정상적으로 출력되었습니다. 최초에 Jenkins 에 접속하기 위해서는 Admin Password 를 입력해야 합니다. kubectl logs 명령어를 통해 Jenkins 컨테이너의 로그를 확인하거나 Jenkins 컨테이너 내부에 들어가서 cat /var/jenkins_home/secrets/initialAdminPassword 명령어를 통해 비밀번호를 확인할 수 있습니다. 확인한 비밀번호를 입력 후 하단의 Continue 버튼을 클릭합니다.

Jenkins 의 플러그인을 설치하는 화면입니다. 필요한 플러그인만 골라서 설치하려면 우측의 “Select Plugins to install” 을 클릭합니다.

적당히 필요한 플러그인을 선택 후 “Install” 버튼을 클릭합니다.

선택한 플러그인의 설치가 진행됩니다.

플러그인 설치가 완료되면 계정생성 폼이 뜹니다. 적당한 id/pw 를 입력 후 우측 하단의 “Save and Continue” 를 클릭합니다.

Jenkins URL 을 설정 후 우측 하단의 “Save and Finish”를 클릭합니다.

모든 구성이 완료되었습니다. “Start using Jenkins” 버튼을 클릭합니다.

구성이 완료된 Jenkins 의 기본 화면입니다. 이제 이 페이지에서 Build & Deploy Pipeline 을 만들어 CI/CD 를 구현하면 됩니다.

  • 마무리

이번 글에서는 Jenkins 를 Kubernetes Cluster 상에 배포/설치해 보았습니다. 이전 글에서 구현했던 daum news crawler의 소스코드에 변경사항이 생기면 그것을 반영하기 위해 수동으로 Docker 이미지를 빌드하고 Registry 에 push 한 후에 Kubernetes Cronjob 을 재생성해주어야 합니다. 이전 글에서 CI/CD 툴은 Jenkins를 설치한 이유는 이런 일련의 과정(pipeline)을 자동화하기 위함이었습니다. 다음 시간에는 수동으로 해야 했던 이 작업들을 Jenkins를 통해 자동화해보도록 하겠습니다.

--

--

Ethan Park
Ethan Park

Written by Ethan Park

Software engineer on paternity leave.

No responses yet