본문 바로가기

Technical/Cloud, Virtualization, Containers

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


전편에 이어지는 내용으로, 이번에는 예제 프로젝트인 hugo-app 을 대상으로 개발~배포~실행까지 Pipeline을 구성하여 CI/CD 과정을 구현해 보자


Jenkins-leader 서비스 기동


  • 새로운 프로젝트를 시작할 때, 전용의 Jenkins 빌더 셋을 만드는 과정에 해당
  • 기존에 제작/테스트 했던 jenkins 서비스 환경을 tear-down 하고 Custom Jenkins 빌더로 새로 시작

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

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

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


  • 새로운 시작

[root@kubemaster ~]# mkdir jenkins-custom-k8s-cicd/01-jenkins-custom-deploy

[root@kubemaster ~]# cd jenkins-custom-k8s-cicd/01-jenkins-custom-deploy

[root@kubemaster 01-jenkins-custom-deploy]# cp ../00-jenkins-custom-image/0*.yaml .


[root@kubemaster 01-jenkins-custom-deploy]# export JENKINS_LEADER_IMAGE="drlee001/jenkins-leader:2.60.3-ns-version"

[root@kubemaster 01-jenkins-custom-deploy]# sed -i -e "s|image: .*|image: ${JENKINS_LEADER_IMAGE}|g" 02-jenkins-dep-svc.yaml

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

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

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

* Docker Hub 에 Push된 Custom Jenkins 빌더 이미지 Deploy


GitHub 프로젝트 연동을 위한 Jenkins 설정


앞선 1/2 의 내용대로 jenkins-leader 이미지를 생성하였다면 admin / admin 으로 Jenkins console 에서 로그인할 수 있을 것이다. Manage Jenkins > Configure System 선택


GitHub : GitHub Server > Add GitHub Server - GitHub Server 클릭


아래로 펼쳐진 항목들 중 두 번 째 Advanced 버튼 클릭


Additional Actions: Manage additional GitHub actions - Convert login and password to token 클릭 


아래로 펼쳐지는 항목들 중 From login and password 선택 > 본인의 GitHub 로그인 계정 입력 > Create token credentials 클릭 > Created ... 성공 메시지가 나오면 하단 Save 클릭


* 주의: Create token credential 단계에서 "GH token 생성 오류" 발생시, 이미 plugin 을 위한 토큰이 생성된 경우이므로, github account > Settings > Developer settings > Personal access tokens 의 "Jenkins Github Plugin token" 을 삭제해 주어야 한다


메인 메뉴 Manage Jenkins > Configure System > GitHub: GitHub Server - Credentials > None 클릭 > GitHub (https://api...) 항목 클릭 > 아래 두 번 째 Advanced 버튼 클릭


아래 펼쳐지는 항목들 중 Shared secret: none 클릭 > GitHub (https://api...) 항목 클릭 > 하단 Save 버튼 클릭 




대상 프로젝트 Pull(Clone), Build & Deploy


  • 아래 예제로 제공되는 간단한 Static Web 프로젝트를 대상으로 CI/CD 과정을 수행해 보자(이 과정을 응용하면 일반적인 다른 프로젝트들도 역시 연동이 가능하게 될 것이다). 실제 테스트를 위해 GitHub의 아래 프로젝트로 이동하여 본인의 GitHub 계정으로 fork 하고 GITHUB_ACCOUNT 변수에 본인의 Account 를 할당해서 사용할 것을 추천한다. 
[root@kubemaster jenkins-custom-k8s-cicd]# export GITHUB_ACCOUNT='YourGithubAccount'
[root@kubemaster jenkins-custom-k8s-cicd]# git clone https://github.com/${GITHUB_ACCOUNT}/test-webapp.git


[참고] 여기까지 수행하였다면, 본 CI/CD 시리즈의 전체 디렉토리 구조는 다음과 같이 나타난다 

[root@kubemaster jenkins-custom-k8s-cicd]# ls -l

total 4

drwxr-xr-x. 2 root root 115  4월  9 00:32 00-jenkins-custom-image

drwxr-xr-x. 2 root root 115  4월  9 00:33 01-jenkins-custom-deploy

drwxr-xr-x. 3 root root  39  4월  9 12:17 docker

-rw-r--r--. 1 root root 376 10월 24 17:35 README.md

drwxr-xr-x. 4 root root 148  4월  9 13:52 test-webapp


  • Clone 받은 프로젝트(또는 본인의 연동 대상 프로젝트)로 이동하고 파일 목록을 확인해 보자. 실제 상황에서 빌드 대상이 되는 파일들은 applications 내의 하위 폴더로 적절히 위치시키면 된다.
[root@kubemaster jenkins-custom-k8s-cicd]# cd test-webapp/
[root@kubemaster test-webapp]# ls -l
total 20
drwxr-xr-x. 3 root root   27  4월  9 12:23 applications
-rwxr-xr-x. 1 root root  192  4월  9 13:18 build-and-push.sh
-rwxr-xr-x. 1 root root  626  4월  9 13:52 github-repo-set.sh
-rw-r--r--. 1 root root 1946  4월  9 13:44 Jenkinsfile
-rw-r--r--. 1 root root   14  4월  9 13:52 README.md
-rwxr-xr-x. 1 root root  104  4월  9 13:24 run-and-show.sh

  • 실제 Build 대상이 되는 프로그램의 리소스들은 다음과 같다. 실제로도 유사한 형태로 구성하면 된다.
[root@kubemaster test-webapp]# ls -l applications/test-webapp-1/
total 48
-rw-r--r--. 1 root root   139  4월  9 12:14 Dockerfile
-rw-r--r--. 1 root root 38651  4월  9 12:34 DockerFileEx.jpg
-rw-r--r--. 1 root root   396  4월  9 16:51 index.html
drwxr-xr-x. 2 root root    29  4월  9 13:22 k8s

[root@kubemaster test-webapp]# ls -l applications/test-webapp-1/k8s/
total 4
-rw-r--r--. 1 root root 615  4월  9 13:22 deployment.yaml
* k8s 디렉토리 내에는 test-webapp-1 프로그램의 Kubernetes 내 Deploy 되는 형태를 정의하는 Yaml 파일을 배치한다. 내용은 아래와 같다.

[root@kubemaster test-webapp]#  cat  applications/test-webapp-1/k8s/deployment.yaml 
apiVersion: v1
kind: Service
metadata:
  name: test-webapp-1
  labels:
    app: test-webapp
spec:
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: test-webapp
    tier: test
  type: NodePort

---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: test-webapp-1
  labels:
    app: test-webapp
spec:
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: test-webapp
        tier: test
    spec:
      containers:
      - image: YourDockerHubAccount/test-webapp-1:latest
        name: test-webapp-1
        ports:
        - containerPort: 80
          name: test-webapp-1


대상 프로그램의 Containerizing

  • 다음 스크립트 내용을 수행하여 대상 프로그램을 Docker Image 로 만들고 Docker Hub 에 Push
[root@kubemaster test-webapp]# cat build-and-push.sh 
#!/bin/sh
DOCKERHUB_ACCOUNT="YourDockerHubAccount"
docker login -u ${DOCKERHUB_ACCOUNT}
docker build -t ${DOCKERHUB_ACCOUNT}/test-webapp-1:latest -f applications/test-webapp-1/Dockerfile applications/test-webapp-1/
docker push ${DOCKERHUB_ACCOUNT}/test-webapp-1:latest
[root@kubemaster test-webapp]# ./build-and-push.sh
Password: ********
Login Succeeded
Sending build context to Docker daemon 44.54 kB
Step 1/4 : FROM nginx:latest
 ---> c5c4e8fa2cf7
Step 2/4 : COPY index.html /usr/share/nginx/html/index.html
 ---> Using cache
 ---> 8e420f1a99bc
Step 3/4 : COPY DockerFileEx.jpg /usr/share/nginx/html/DockerFileEx.jpg
 ---> Using cache
 ---> b39fed81c0e4
Step 4/4 : EXPOSE 80
 ---> Using cache
 ---> 818b28961124
Successfully built 818b28961124
The push refers to a repository [docker.io/YourDockerHubAccount/test-webapp-1]
300a550bf23d: Pushed 
a3cb71492d49: Pushed 
... 
latest: digest: sha256:a44e5bb0fe8af44630e24c16d49f6bb34cf5f7a0b66b5e30de20fc590273396b size: 1364
* DOCKERHUB_ACCOUNT 변수 값에는 본인 자신의 Ducker Hub 계정을 사용

[root@kubemaster test-webapp]# docker rmi YourDockerHubAccount/test-webapp-1:latest
* 실제 Docker Image 가 Pull 되어 실행되는 머신은 현재 작업중인 Master 노드가 아니므로 불필요한 이미지 삭제

  • 다음 스크립트를 수행하여 Kubernetes 내에 대상 프로그램의 Service/Pod 를 기동
[root@kubemaster test-webapp]# cat run-and-show.sh 
#!/bin/sh
kubectl apply -f applications/test-webapp-1/k8s/deployment.yaml
kubectl get svc test-webapp-1

[root@kubemaster test-webapp]# ./run-and-show.sh 
service "test-webapp-1" created
deployment "test-webapp-1" created
NAME            TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
test-webapp-1   NodePort   10.128.227.169   <none>        80:32562/TCP   21h

  • 웹브라우저를 사용하여 대상 프로그램(Webapp)에 접속, 확인해 보자


'Docker Hub로의 Push' 와 'Kubectl 실행'을 위한 Token 생성 

  • Jenkins-leader 에서 등록한 작업(item)은 jenkins-slave Pod가 동적으로 생성되면서 그 내부에서 처리되고, 완료 후 해당 Pod는 자동으로 종료된다.
  • Jenkinsfile의 container 영역에서 정의한 작업이 수행되는 과정에서, Docker Hub 쪽으로 컨테이너 이미지 Push, Kubernetes Cluster의 deploy된 Pod/container 이미지를 교체할 때 각각, 인증을 위한 token 이 Kubernetes 내에 secret 형태로 존재해야 한다.
  • DOCKER_ACCOUNT 변수에 본인의 Docker Hub 계정을 사용한다.

# export DOCKER_ACCOUNT="drlee001"

# docker login -u $DOCKER_ACCOUNT

password:

Login Succeeded

#  kubectl create secret -n ns-jenkins generic docker-config --from-file=$HOME/.docker/config.json

secret "docker-config" created

# kubectl create secret -n ns-jenkins generic kube-config --from-file=$HOME/.kube/config

secret "kube-config" created

# kubectl get secret -n ns-jenkins 

NAME                  TYPE                                  DATA      AGE

default-token-chxlp   kubernetes.io/service-account-token   3         3d

docker-config         Opaque                                1         2m

jenkins-token-dhx8s   kubernetes.io/service-account-token   3         3d

kube-config           Opaque                                1         2m



  • GitHub으로 연동되는 빌드 대상 프로젝트 repository 의 Jenkinsfile 내용은 다음과 같다. 여기서 정의한 Pod/container 구성에 맞춰진 jenkins-slave Pod가 동적으로 수행된다.
  • 아래 구성대로 기동되는 Pod는 내부에 총 3개의컨테이너를 포함한다(docker, kubectl 그리고 jnlp 컨테이너).
#!groovy
podTemplate(label: 'test-webapp-1', containers: [
    containerTemplate(name: 'kubectl', image: 'smesch/kubectl', ttyEnabled: true, command: 'cat',
        volumes: [secretVolume(secretName: 'kube-config', namespace: 'ns-jenkins', mountPath: '/root/.kube')]),
    containerTemplate(name: 'docker', image: 'docker', ttyEnabled: true, command: 'cat',
        envVars: [containerEnvVar(key: 'DOCKER_CONFIG', value: '/tmp/'),])],
        volumes: [secretVolume(secretName: 'docker-config', namespace: 'ns-jenkins', mountPath: '/tmp'),
                  hostPathVolume(hostPath: '/var/run/docker.sock', mountPath: '/var/run/docker.sock')
  ]) {

    node('test-webapp-1') {

        def DOCKER_HUB_ACCOUNT = 'YourDockerHubAccount'
        def DOCKER_IMAGE_NAME = 'test-webapp-1'
        def K8S_DEPLOYMENT_NAME = 'test-webapp-1'
        def POD_NAMESPACE = 'default'

        stage('Clone test-webapp-1 App Repository') {
            checkout scm
            
            container('docker') {
                stage('Docker Build & Push Current & Latest Versions') {
                    sh ("docker build -t ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER} -f applications/test-webapp-1/Dockerfile applications/test-webapp-1/")
                    sh ("docker push ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER}")
                    sh ("docker tag ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER} ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:latest")
                    sh ("docker push ${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:latest")
                }
            }

            container('kubectl') {
                stage('Deploy New Build To Kubernetes') {
                    sh ("kubectl set image -n ${POD_NAMESPACE} deployment/${K8S_DEPLOYMENT_NAME} ${K8S_DEPLOYMENT_NAME}=${DOCKER_HUB_ACCOUNT}/${DOCKER_IMAGE_NAME}:${env.BUILD_NUMBER}")
                }
            }

        }        
    }
}
* jenkins-slave의 namespace가 jenkins-leader Pod와 동일하게 ns-jenkins 임에 유의



Jenkins pipeline 작업을 통한 자동 빌드 & 배포 실행


  • 대상 프로그램의 리소스 중 일부를 수정해서 Jenkins pipeline 작업을 수행해 보자

[root@kubemaster test-webapp]# vi applications/test-webapp-1/index.html 

<html>
<head>
</head>
<body>
<p>
  <h2 style="font-family:sans-serif">Hello from K8s-Jenkins pipeline! You've successfully built and run the Test-Webapp-1 app.</h2>
  <br>Version: v04
</p>
<p style="font-family:sans-serif">
  This app is a simple static web page running on <a href="https://hub.docker.com/_/nginx/">nginx base image</a>.
</p>
<p>
  <img src="DockerFileEx.jpg">
</p>
</body>
</html>

[root@kubemaster test-webapp]# git add .

[root@kubemaster test-webapp]# git commit -a -m "Update - index.html"

[master fce13d5] Update - index.html

 2 files changed, 5 insertions(+), 4 deletions(-)

[root@kubemaster test-webapp]# git push origin master

Username for 'https://github.com': YourGitHubAccount

Password for 'https://YourGitHubAccount@github.com': YourGitHubPassword

Counting objects: 11, done.

Delta compression using up to 2 threads.

Compressing objects: 100% (5/5), done.

Writing objects: 100% (6/6), 609 bytes | 0 bytes/s, done.

Total 6 (delta 3), reused 0 (delta 0)

remote: Resolving deltas: 100% (3/3), completed with 3 local objects.

To https://github.com/YourGitHubAccount/test-webapp.git

   24c9645..fce13d5  master -> master

* 프로그램 소스 일부를 수정하고 Commit & Push 한다


Jenkins-leader UI 화면에서 새로운 Item 을 등록한다(이후의 과정은 포스팅 시리즈 1/2(http://bryan.wiki/295 - 빌드테스트 섹션)과 동일하므로 별도 설명 생략)






Console Output 의 마지막에 Finished: SUCCESS 메시지까지 나타나면 성공적으로 빌드 & 배포가 완료된 것이다. 이제 위의 Webapp 접속 화면을 확인하면 수정/변경된 내용을 확인할 수 있을 것이다.


- Barracuda -


[관련 글]

2018/03/20 - [Technical/Cloud, 가상화, PaaS] - [Kubernetes - CI/CD] Customized Jenkins 제작과 활용 - 1/2