본문 바로가기

Technical/Cloud, Virtualization, Containers

[Kubernetes - CI/CD] Customized Jenkins 제작과 활용 - 1/2


Kubernetes 를 활용하여 CI/CD를 구현하는 방법은 여러 가지가 있다. 이번 시리즈는 커스텀 Jenkins 이미지를 사용한 컨테이너 Application 빌드 배포 자동화를 구현해 보고자 한다. 본 글은 그 첫 번 째로, 커스텀 Jenkins 이미지를 제작, 테스트 빌드를 통해 CI/CD가 정상적으로 작동하는지를 확인하는 내용이다.


Prerequisites

  • Kubernetes 1.8~1.9.x 가 설치되고 정상 작동 할 것
  • Application 소스 저장소로 사용할 github 계정 준비
  • Persistent Volume으로 사용할 Storage는 Heketi-API로 연동된 Glusterfs(http://bryan.wiki/286 참조)
  • Jenkins 빌드 작업을 위한 네임스페이스틑 ns-jenkins 로 설정하여 사용(별도 지정하지 않으면 default 네임스페이스 사용)
  • 작업 디렉터리는 $HOME/jenkins-custom-k8s-cicd 로 하고, 하위에 각 상황과 용도에 맞는 서브 디렉터리를 만들어 사용


Jenkins 기본 이미지로 Jenkins Pod/Service 기동


[root@kubemaster ~]# kubectl create namespace ns-jenkins

[root@kubemaster ~]# mkdir 00-jenkins-custom-image && cd 00-jenkins-custom-image

[root@kubemaster 00-jenkins-custom-image]# vi 00-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

[root@kubemaster 00-jenkins-custom-image]# vi 01-jenkins-leader-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: jenkins-leader-pvc
  annotations:
    volume.beta.kubernetes.io/storage-class: glusterfs-storage
  labels:
    app: jenkins-leader
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 500Mi


[root@kubemaster 00-jenkins-custom-image]# vi 02-jenkins-dep-svc.yaml

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: jenkins-leader
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: jenkins-leader
    spec:
      serviceAccountName: jenkins
      securityContext:
        # Jenkins uid:gid=1000:1000
        fsGroup: 1000
      containers:
        - name: jenkins-leader
          image: jenkins
          volumeMounts:
          - name: jenkins-home
            mountPath: /var/jenkins_home
          ports:
          - containerPort: 8080
          - containerPort: 50000
      volumes:
      - name: jenkins-home
        persistentVolumeClaim:
          claimName: jenkins-leader-pvc
---
apiVersion: v1
kind: Service
metadata:
  name: jenkins-leader-svc
  labels:
    app: jenkins-leader
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
    nodePort: 30500
  - port: 50000
    protocol: TCP
    name: slave
    nodePort: 30501
  selector:
    app: jenkins-leader


[root@kubemaster 00-jenkins-custom-image]# kubectl create -f 00-jenkins-sa-clusteradmin-rbac.yaml 

serviceaccount "jenkins" created

clusterrolebinding "cluster-admin-clusterrolebinding" created


[root@kubemaster 00-jenkins-custom-image]# kubectl create -f 00-jenkins-leader-pvc.yaml -n ns-jenkins 

persistentvolumeclaim "jenkins-leader-pvc" created


[root@kubemaster 00-jenkins-custom-image]# kubectl create -f 01-jenkins-dep-svc.yaml -n ns-jenkins 

deployment "jenkins-leader" created

service "jenkins-leader-svc" created


[root@kubemaster 00-jenkins-custom-image]# kubectl get pods -n ns-jenkins 

NAME                              READY     STATUS    RESTARTS   AGE

jenkins-leader-75869666cc-9kq6q   1/1       Running   0          7m


[root@kubemaster 00-jenkins-custom-image]# kubectl logs -f -n ns-jenkins jenkins-leader-75869666cc-9kq6q

Running from: /usr/share/jenkins/jenkins.war

webroot: EnvVars.masterEnvVars.get("JENKINS_HOME")

...

*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.

Please use the following password to proceed to installation:

1541910096194795a11f9b2342b24b8d

This may also be found at: /var/jenkins_home/secrets/initialAdminPassword

*************************************************************

...

Mar 20, 2018 8:35:10 AM hudson.model.AsyncPeriodicWork$1 run

INFO: Finished Download metadata. 60,791 ms

...

* RBAC 설정에서는 각종 권한 처리를 단순하게 하기 위해 ns-jenkins 네임스페이스의 ServiceAccount인 jenkins와 default cluster-admin 권한을 부여한다. 실제 서비스 환경에서는 좀더 세밀한 권한 관리에 신경을 써둘 필요가 생길 수도 있다. 

* 네임스페이스를 따로 지정하지 않으면 모든 jenkins 요소들이 default 네임스페이스에 생성된다

* 설치용 임시암호를 복사해 둔다

* [Tip] 해당 Jenkins-leader container 의 초기 암호 값을 바로 알아 내려면 아래 명령을 실행해도 된다

# kubectl exec -it -n ns-jenkins `kubectl get pods -n ns-jenkins --selector=app=jenkins-leader --output=jsonpath={.items..metadata.name}` -- cat /root/.jenkins/secrets/initialAdminPassword


Jenkins 플러그인 및 커스텀 설정


  • 앞 단계에서 Kubernetes 서비스를 NodePort 로 expose 한 결과를 확인


[root@kubemaster 00-jenkins-custom-image]# kubectl get svc -n ns-jenkins

NAME                                   TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                        AGE

glusterfs-dynamic-jenkins-leader-pvc   ClusterIP   10.139.137.127   <none>        1/TCP                          3d

jenkins-leader-svc                     NodePort    10.141.255.17    <none>        80:30500/TCP,50000:30501/TCP   3d



  • Jenkins-leader 서비스를 실행한 yaml 파일에서 정의한 대로, 웹브라우저로 Minion(Worker Node) IP의 30500 포트로 접속하여, 앞 단계에서 확인한 설치용 임시 암호를 입력한 후 최초 설정 진행

http://10.255.10.171:30050 으로 접속, 설치용 임시 암호 입력(편의상 다음에 나타나는 관리자용 계정/암호는 admin/admin 으로 설정)


Install suggested plugins 를 선택하여 기본 설치 플러그인을 설치


자동으로 기본 Plugin 들이 설치되며, 이후에 Jenkins 관리자 계정과 암호를 등록하고 완료(Start using Jenkins)하면 Jenkins 관리 Console 로 접속


Manage Jenkins > Configure Global Security 선택


TCP port for JNLP - Fixed 50000, Prevent Cross Site ... - 체크 해제, Apply & Save



  • Kubernetes Plugin 설치


Manage Jenkins > Manage Plugins 선택


Available 탭 선택


Cloud Providers 항목으로 스크롤(검색), kubernetes 체크 - Install without restart 클릭 - 설치 후 Go back to top page 선택


  • Kubernetes 빌드를 위한 plugin 설정

Manage Jenkins > Configure System 선택


#  of executors 항목의 값을  0  으로 설정


페이지 맨 끝, Cloud 항목으로 이동, Add a new cloud 선택 > kubernetes 선택



이번 단계에서는 아래 각 항목에 대해 시스템 구성에 맞게 정확히 입력/설정한다

  • Name: kubernetes

  • Kubernetes URL: https://kubernetes.default.svc.cluster.local

  • Disable https certificate check: Yes

  • Kubernetes Namespace: ns-jenkins(Jenkins 서비스를 기동시키는 네임스페이스명)

  • 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 클릭


Credentials 팝업창, Kind: kubernetes service account 선택 > Add 클릭


Credentials: 좌측 None 클릭 > 새로 만들어진 Secret text 선택 > Test Connection > Save 클릭



  • Kubernetes 빌드 테스트


좌측 메인 메뉴의 New Item 클릭 > Item name 입력 > Pipeline 클릭 > 하단 OK 클릭


아래로 스크롤, Script 입력 창에 아래 스크립트 내용 Copy & Paste > Save


podTemplate(label: 'pod-golang', 
    containers: [
        containerTemplate(
            name: 'golang',
            image: 'golang',
            ttyEnabled: true,
            command: 'cat'
        )
    ]
) {
    node ('pod-golang') {

        stage 'Switch to Utility Container'
        container('golang') {

          sh ("go version")

        }
    }
}  


메인 메뉴 Build Now 클릭 > Build History 의 Build 아이템 우측 역삼각형(▼) 클릭 > Console Output 클릭


빌드 과정 및 결과 확인(실제로 빌드 작업이 수행되는 jenkins-slave pod는 ns-jenkins 네임스페이스에서 실행)


빌드가 수행되는 동안 ns-jenkins 네임스페이스의 pod 목록 변화를 확인


  • 최종적으로 Jenkins를 통한 Build test를 위한 golang pod의 lifecycle은 다음 명령어로 확인 가능

# kubectl get pods -n ns-jenkins -w
NAME                              READY     STATUS    RESTARTS   AGE
jenkins-leader-6dc657fb9f-ngsj9   1/1       Running   0          6h
jenkins-slave-jqgwz-5kql9         2/2       Running   0          4m
jenkins-slave-jqgwz-5kql9   2/2       Terminating   0         4m
jenkins-slave-jqgwz-5kql9   0/2       Terminating   0         5m
jenkins-slave-jqgwz-5kql9   0/2       Terminating   0         6m
jenkins-slave-jqgwz-5kql9   0/2       Terminating   0         6m



커스텀 설정된 Jenkins 빌더 이미지 저장


  • 기본 및 kubernetes 플러그인의 설치/설정이 완료된 Jenkins 빌더(leader) 컨테이너의 설정 정보를 추출하고 불필요한 파일을 삭제
  • 최종 완성된 컨테이너를 Customized Jenkins Builder Docker 이미지로 저장, docker.io 레지스트리에 Push
[root@kubemaster 00-jenkins-custom-image]# export NAMESPACE=ns-jenkins
[root@kubemaster 00-jenkins-custom-image]# export JENKINS_POD=$(kubectl get pods -n $NAMESPACE | grep jenkins-leader | awk '{print $1}')
[root@kubemaster 00-jenkins-custom-image]# export DOCKER_ACCOUNT=drlee001

[root@kubemaster 00-jenkins-custom-image]# mkdir -p docker/jenkins-kubernetes-leader

[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/config.xml \
  docker/jenkins-kubernetes-leader/config.xml
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/users/ \
  docker/jenkins-kubernetes-leader/users/
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/jobs/ \
  docker/jenkins-kubernetes-leader/jobs/
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/secrets/master.key \
  docker/jenkins-kubernetes-leader/secrets/master.key
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/secrets/hudson.util.Secret \
  docker/jenkins-kubernetes-leader/secrets/hudson.util.Secret
[root@kubemaster 00-jenkins-custom-image]# kubectl cp ${NAMESPACE}/${JENKINS_POD}:var/jenkins_home/secrets/slave-to-master-security-kill-switch \
  docker/jenkins-kubernetes-leader/secrets/slave-to-master-security-kill-switch
[root@kubemaster 00-jenkins-custom-image]# curl -sSL "http://admin:admin@10.255.10.171:30500/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | \
  perl -pe 's/.*?<shortName>([\w-]+).*?<version>([^<]+)()(<\/\w+>)+/\1 \2\n/g'|sed 's/ /:/' > \
  docker/jenkins-kubernetes-leader/plugins.txt 

[root@kubemaster 00-jenkins-custom-image]# vi docker/jenkins-kubernetes-leader/executors.groovy
import jenkins.model.*
Jenkins.instance.setNumExecutors(0)

[root@kubemaster 00-jenkins-custom-image]# docker login -u ${DOCKER_ACCOUNT}

[root@kubemaster 00-jenkins-custom-image]# vi docker/jenkins-kubernetes-leader/Dockerfile
USER root
RUN apt-get update -y
USER ${user}

COPY config.xml /usr/share/jenkins/ref/config.xml
COPY executors.groovy /usr/share/jenkins/ref/init.groovy.d/executors.groovy
COPY jobs /usr/share/jenkins/ref/jobs
COPY secrets /usr/share/jenkins/ref/secrets
COPY users /usr/share/jenkins/ref/users
COPY plugins.txt /usr/share/jenkins/plugins.txt

# Workaround for 'Lockfile creation - File not found' error
RUN xargs /usr/local/bin/install-plugins.sh < /usr/share/jenkins/plugins.txt

RUN echo 2.0 > /usr/share/jenkins/ref/jenkins.install.UpgradeWizard.state

ENTRYPOINT ["/bin/tini", "--", "/usr/local/bin/jenkins.sh"]
[root@kubemaster 00-jenkins-custom-image]# docker build -t ${DOCKER_ACCOUNT}/jenkins-leader:2.60.3-ns-version --rm --no-cache docker/jenkins-kubernetes-leader/
[root@kubemaster 00-jenkins-custom-image]# docker push ${DOCKER_ACCOUNT}/jenkins-leader:2.60.3-ns-version
[root@kubemaster 00-jenkins-custom-image]# docker build -t ${DOCKER_ACCOUNT}/jenkins-leader:latest --rm --no-cache docker/jenkins-kubernetes-leader/
[root@kubemaster 00-jenkins-custom-image]# docker push ${DOCKER_ACCOUNT}/jenkins-leader:latest


- Barracuda -