쿠버네티스 시작하기(7) - CI/CD 파이프라인 만들기(1/3) - 쿠버네티스 클러스터에 jenkins 설치 및 설정
이번 장에서는 설치한 쿠버네티스 위에 CI/CD 파이프라인을 만들 것이다. CI/CD 파이프라인을 만드는 이유는 간단하다. 로컬에서 개발한 소스를 수작업으로 서버에 올린 후 쿠버네티스 클러스터에 컨테이너로 배포한다는 것은 상상할 수 없기 때문이다. 때문에 배포 파이프라인을 만들어서 로컬에서 어플리케이션 개발 후 Git Repository로 Push/Merge를 하게 되면 소스가 자동으로 서버 내 쿠버네티스 클러스터로 배포가 되게끔 할 것이다. 우선 CI/CD 파이프라인을 구축 하기 전에 CI/CD에 대한 정확한 이해가 필요하다. CI/CD에 대한 정확한 이해는 이 포스트(https://twofootdog.tistory.com/16)를 참고해보도록 하자.
1. CI/CD 파이프라인 구성도
앞으로 생성할 CI/CD 파이프라인 구성도는 아래와 같다(배포 파이프라인 구성 시, sonarqube & JUnit test는 제외할 예정이다. 해당 내용은 별도의 포스트에서 다룰 예정이다).
2. 사전준비
쿠버네티스를 활용한 배포 파이프라인을 만들기 위해서는 아래와 같은 준비가 필요하다.
- CentOS로 구성된 쿠버네티스 클러스터(쿠버네티스 시작하기1~6까지 수행했으면 클러스터는 완성이 되어있을 것이다)
- Git 설치 & Git Repository 가입. 필자는 Gitlab을 Repository로 활용하면서 진행할 예정이다.
- Slack 가입. 배포 수행 및 성공/실패 시 알람 기능을 구현할 예정이다.
- Docker hub가입
자 준비가 다 되었다면 시작해보도록 하자.
3. 기본 이미지로 Jenkins 구동
3-1. Namespace 생성 & ServiceAccount 생성 & ClusterRoleBinding
배포 파이프라인을 만들기 위해서는 우선 쿠버네티스 클러스터에 ns-jenkins라는 namespace를 만들고 jenkins-sa-clusteradmin-rbac.yaml파일로 jenkins 구동 시 사용할 ServiceAccount를 생성하고 cluster-admin role을 부여한다(필자는 ServiceAccount 이름을 jenkins라고 썼다). 또한 default 계정에도 cluster-admin role을 부여한다.
# kubectl create namespace ns-jenkins
# mkdir jenkins && cd jenkins
# vi jenkins-sa-clusteradmin-rbac.yaml
vi jenkins-sa-clusteradmin-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
namespace: ns-jenkins
name: jenkins
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: cluster-admin-clusterrolebinding
subjects:
- kind: ServiceAccount
name: jenkins
namespace: ns-jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: cluster-admin-clusterrolebinding-2
subjects:
- kind: ServiceAccount
name: default
namespace: ns-jenkins
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
3-2. Jenkins Deployment 및 Service 구동
jenkins를 사용하기 위해서는 jenkins 데이터가 저장될 저장소가 필요하다. 참고한 자료에서는 pvc(persistent volume claim)을 활용하여 저장소도 쿠버네티스로 올렸지만, 필자는 프로젝트 진행 시 glusterFS같은 파일시스템이 준비되지 않았었기 때문에 해당 방법으로 진행하지 않고, jenkins 저장소를 서버 디렉토리에 mount시키는 방법으로 진행해 보겠다. glusterFS와 pvc를 활용한 jenkins 구축은 추후 별도의 포스팅으로 작성해보도록 하겠다.
우선 아래와 같이 jenkins-svc.yaml파일을 작성하자.
vi jenkins-svc.yaml
#apiVersion: extensions/v1beta1
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins-leader
namespace: ns-jenkins
spec:
replicas: 1
selector:
matchLabels:
app: jenkins-leader
template:
metadata:
labels:
app: jenkins-leader
spec:
serviceAccountName: jenkins
securityContext: # Jenkins uid:gid=1000:1000
fsGroup: 1000
containers:
- name: jenkins-leader
image: jenkins/jenkins:alpine
volumeMounts:
- name: jenkins-home
mountPath: /var/jenkins_home
ports:
- containerPort: 8080
- containerPort: 50000
volumes:
- name: jenkins-home
#persistentVolumeClaim:
#claimName: jenkins-leader-pvc
hostPath:
path: /home/admin/k8s/jenkins/jenkins-home/
type: Directory
nodeSelector:
kubernetes.io/hostname: k8s-master
---
apiVersion: v1
kind: Service
metadata:
name: jenkins-leader-svc
namespace: ns-jenkins
labels:
app: jenkins-leader
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
nodePort: 30000
- port: 50000
protocol: TCP
name: slave
nodePort: 30001
selector:
app: jenkins-leader
해당 yaml파일의 Deployment와 Service 두개로 구현이 되어 있다. 특이사항은 Deployment의 apiVersion이 apps/v1으로 변경되었다는 점과(extension/v1beta1은 프로젝트 시작 전에는 유효했지만 이 글을 포스팅 할때는 deprecated 되었다) persistentVolumeClaim대신 hostPath를 설정하여 jenkins 저장소를 서버의 특정 디렉토리(/home/admin/k8s/jenkins/jenkins-home)로 지정했다는 점이다. 또한 nodeSelector를 사용하여 label이 kubernetes.io/hostname=k8s-master 인 노드, 즉 마스터노드에 jenkins를 올렸다는 점이다. 사실 jenkins를 굳이 마스터노드에 올려놓을 필요는 없지만, 해당 yaml파일을 적용하게 될 경우 jenkins저장소로 지정된 디렉토리가 미 존재하게 되면(이 포스트에서는 /home/admin/k8s/jenkins/jenkins-home) 쿠버네티스 클러스터에 jenkins가 올라가지 않는다. 그런데 만약 nodeSelector로 특정 노드를 지정하지 않았다면 어느 노드에 jenkins가 올라갈지 확인이 되지 않기 때문에 모든 노드에 jenkins 저장소를 만들어 놔야 하는 번거로움이 있어서, 그냥 마스터노드에 올리기로 정하고 마스터노드에 jenkins저장소 디렉토리를 만든 것이다.
또한 Service부분은 type은 NodePort로 구현했는데, Service의 기본은 ClusterIP이며, ClusterIP인 경우는 컨테이너는 내부 통신만 가능하다. 하지만 type이 NodePort면 외부 포트를 통해서 컨테이너 내부 포트로 접근이 가능해진다(yaml파일에서 지정한 nodePort 30000을 통해 jenkins 내부로 접근 가능하다). 그렇기 때문에 만약 jenkins 환경설정을 위해 webpage로 접근하려면 http://<호스트ip>:30000 으로 접속하면 된다. 또한 port 50000이 지정된 내용은 jenkins slave 컨테이너의 내부포트다. 평소에 jenkins leader 컨테이너가 클러스터상에 존재하다가 배포요청이 올 경우 leader가 slave 컨테이너를 만들어서 slave가 application 배포를 수행한다고 생각하면 된다.
위와 같이 작성하고 해당 yaml파일을 적용시킨다.
# kubectl apply -f jenkins-svc.yaml
하지만 적용을 해도 jenkins Deployment와 Service가 구동되지 않는다. 왜냐하면 컨테이너 생성 시 jenkins저장소 디렉토리(/home/admin/k8s/jenkins/jenkins-home)에 권한 문제로 접근할 수 없기 때문이다. 때문에 아래 명령어를 수행해서 권한 문제를 해결해줘야 한다.
# chown -R 1000:1000 [디렉토리명]
디렉토리 변경 후 기존 jenkins-svc.yaml 적용을 해제하고 재 적용한다.
# kubectl delete -f jenkins-svc.yaml
# kubectl apply -f jenkins-svc.yaml
그러면 jenkins가 정상 구동된 것을 확인할 수 있다.
4. Jenkins WebPage 접속 후 Jenkins 설치
jenkins 환경설정을 해보자. 우선 http://<호스트ip>:30000 주소를 웹페이지에 입력해보자. 입력하게 되면 아래와 같은 화면이 나오게 된다.
jenkins 컨테이너 내부에 있는 /var/jenkins_home/secrets/initialAdminPassword 파일을 확인해서 암호를 입력해야 한다. 아래 명령어를 이용하여 해당 암호를 확인하고 입력하고 Continue 버튼을 클릭하자.
# kubectl exec -it [jenkins pod 명] cat /var/jenkins_home/secrets/initialAdminPassword -n ns-jenkins
필자는 아래와 같이 수행하였다.
[root@k8s-master jenkins]# kubectl get pod -n ns-jenkins
NAME READY STATUS RESTARTS AGE
jenkins-leader-7d4bddf497-chl5t 1/1 Running 0 20m
[root@k8s-master jenkins]# kubectl exec -it jenkins-leader-7d4bddf497-chl5t cat /var/jenkins_home/secrets/initialAdminPassword -n ns-jenkins
6d22522a2bf6441681ea4a10f6244954
[root@k8s-master jenkins]#
다음은 아래와 같이 plugin을 어떻게 설치할지가 나오는데 무엇을 선택할지 잘 모르니 Install suggested plugins를 선택하자.
그럼 아래와 같이 정상적으로 설치가 진행된다.
다음으로 어드민 유저 정보를 입력한다.
다음으로 Jenkins URL을 입력하고 완료한다.
그러면 아래와 같이 Jenkins 화면으로 넘어가는 것을 확인할 수 있다.
5. Jenkins 환경설정 & 플러그인 설치
이제 jenkins 환경설정 및 플러그인 설치를 진행하도록 하겠다.
우선 Jenkins 관리 -> Configure Global Security에서 TCP port for inbound agents를 50000으로 맞춰주고, prevent Cross Site Request Forgery exploits 체크를 해제해 주고 Apply & Save 해준다.
다음으로 Jenkins관리 -> 플러그인 관리로 가서 kubernetes, Gitlab, Gitlab Hook을 선택하고 지금다운로드하고 재시작후 설치하기를 선택한다.
설치 후 재시작이 완료되면 Jenkins관리 -> 시스템 설정으로 간다.
#of executor는 2->0으로 변경한다(동시에 배포 가능한 스레드 수인데 0으로 설정하여 동시에 여러개 배포가 되지 않도록 변경한다)
다음으로 아래에 있는 Cloud 항목으로 가서 Add a new cloud -> Kubernetes를 클릭한다.
다음에 kubernetes에 대한 설정을 아래와 같이 입력한다.
- Name : kubernetes
- kubernetes URL : https://kubernetes.default.svc.cluster.local
- Disable https certificate check : Yes
- kubernetes Namespace : ns-jenkins(jenkins 서비스를 구동시킨 namespace명)
- Jenkins URL : http://jenkins-leader-svc.ns-jenkins.svc.cluster.local
- Jenkins tunnel : jenkins-leader-svc.ns-jenkins.svc.cluster.local:50000
각 항목을 입력 후 Credentials에 Add->Jenkins 선택한다.
Jenkins Credentials Provider에서 Kind로 Kubernetes Service Account를 선택한다. 그러면 맨 처음에 yaml로 추가한 ServiceAccount를 사용하게 될 것이다.
다시 설정 페이지로 돌아와서 Credentials -> Secret Text를 선택한 후 Test Connection을 클릭한다. Connection test Successful이 나오면 연결이 정상적으로 이루어진 것이다. 연결이 되었으면 Apply & Save한다.
쿠버네티스 설정이 완료되었으니, 정상적으로 jenkins pipeline이 수행되는지 테스트를 진행해보도록 하겠다.
새로운Item -> Pipeline을 선택하고 Item name을 작성하고 OK를 누른다.
다음으로 jenkins pipeline 수행 시 실행 될 script를 작성하고 Apply & 저장을 누른다.
작성된 script는 아래와 같다.
podTemplate(label: 'builder',
containers: [
containerTemplate(name: 'gradle', image: 'gradle:5.6-jdk8', command: 'cat', ttyEnabled: true),
]) {
node('builder') {
stage('Build') {
container('gradle') {
sh "echo pipeline test"
}
}
}
}
다음으로 생성한 pipeline(test-project)를 선택한 후 Build Now 버튼을 클릭한다.
Build History에 새로운 번호가 생기면서 Build가 진행될 것이다. 해당 번호를 클릭해서 로그를 확인해보자.
로그를 확인해보면 정상 처리가 된 것을 확인할 수 있다.
처리 도중 쿠버네티스 클러스터의 pod를 확인해보면, ns-jenkins 네임슾페이스에서 builder라는 pod가 Pending -> ContainerCreating -> Running 상태로 동작하다가 배포가 완료된 후 Terminating되는 것을 확인할 수 있다.
마치며
지금까지 쿠버네티스를 활용하여 Jenkins 설치 및 환경설정을 진행해 보았다.
다음 장에서는 설치한 Jenkins와 Git Repository를 연동시켜서 Git Repository에 존재하는 소스를 빌드/배포하고 Slack을 통해서 알람까지 보내는 파이프라인을 만들어 보도록 하겠다.
참고