在容器上構建持續部署及最佳實踐初探

  • 2019 年 11 月 8 日
  • 筆記

要想理解持續集成和持續部署,先要了解它的部分組成,以及各個組成部分之間的關係。下面這張圖是我見過的最簡潔、清晰的持續部署和集成的關係圖。

file

圖片來源

持續部署:

如圖所示,開發的流程是這樣的:
程序員從源碼庫(Source Control)中下載源代碼,編寫程序,完成後提交代碼到源碼庫,持續集成(Continuous Integration)工具從源碼庫中下載源代碼,編譯源代碼,然後提交到運行庫(Repository),然後持續交付(Continuous Delivery)工具從運行庫(Repository)中下載代碼,生成發佈版本,並發佈到不同的運行環境(例如DEV,QA,UAT, PROD)。

圖中,左邊的部分是持續集成,它主要跟開發和程序員有關;右邊的部分是持續部署,它主要跟測試和運維有關。持續交付(Continuous Delivery)又叫持續部署(Continuous Deployment),它們如果細分的話還是有一點區別的,但我們這裡不分得那麼細,統稱為持續部署。本文側重講解持續部署。

持續集成和部署有下面幾個主要參與者:

  • 源代碼庫:負責存儲源代碼,常用的有Git和SVN。
  • 持續集成與部署工具:負責自動編譯和打包以及把可運行程序存儲到可運行庫。比較流行的有Jenkins,GitLab,Travis CI,CircleCI 等
  • 庫管理器(Repository Manager):也就是圖中的Repository,我們又叫運行庫,負責管理程序組件。最常用的是Nexus。它是一個私有庫,它的作用是管理程序組件。

庫管理器有兩個職能:

  • 管理第三方庫:應用程序常常要用到很多第三方庫,並且不同的技術棧需要的庫不同,它們經常是存放在第三方公共庫里,管理起來不是很方便。一般公司會建立一個私有管理庫,來集中統一管理各種第三方軟件,例如它既可以做為Maven庫(Java),也可以做為鏡像庫(Docker),還可以做為NPM庫(JavaScript),來保證公司軟件的規範性。
  • 管理內部程序的交付:所有公司在各種環境(例如DEV,QA,UAT, PROD)發佈的程序都由它來管理,並賦予統一的版本號,這樣任何交付都有據可查,同時便利於程序回滾。

持續部署步驟:

各個公司對持續部署(Continuous Deployment)的要求不同,它的步驟也不相同,但主要包括下面幾個步驟:

  • 下載源碼:從源代碼庫(例如github)中下載源代碼。
  • 編譯代碼:編譯語言都需要有這一步
  • 測試:對程序進行測試。
  • 生成鏡像:這裡包含兩個步驟,一個是創建鏡像,另一個是存儲鏡像到鏡像庫。
  • 部署鏡像: 把生成的鏡像部署到容器上

上面的流程是廣義的持續部署流程,狹義的流程是從庫管理器中檢索可運行程序,這樣就省去了下載源碼和編譯代碼環節,改由直接從庫管理器中下載可執行程序。但由於並不是每個公司都有單獨的庫管理器,這裡就採用了廣義的持續部署流程,這樣對每個公司都適用。

持續部署實例:

下面我們通過一個具體的實例來展示如何完成持續部署。我們用Jenkins來做為持續部署工具,用它部署一個Go程序到k8s環境。

我們的流程基本是上面講的狹義流程,但由於沒有Nexus,我們稍微變通了一下,改由從源碼庫直接下載源程序,步驟如下:

  • 下載源碼:從github下載源代碼到Jenkins的運行環境
  • 測試:這一步暫時沒有實際內容
  • 生成鏡像:創建鏡像,並上傳到Docker hub。
  • 部署鏡像: 將生成的鏡像部署到k8s

在創建Jenkins項目之前,先要做些準備工作:

建立Docker Hub賬戶

需要在Docker Hub上創建賬戶和鏡像庫,這樣才能上傳鏡像。具體過程這裡就不詳細講解了,請查閱相關資料。

在Jenkins上創建憑證(Credentials)

需要設置訪問Docker hub的用戶和口令,以後在Jenkins腳本里可以通過變量的方式進行引用,這樣口令就不會以明碼的方式出現在程序里。

用管理員賬戶登錄 Jenkins主頁面後,找到 Manage Jenkins-》Credentials-》System -》Global Credentials -》Add Credentials,如下圖所示輸入你的Docker Hub的用戶名和口令。「ID」是後面你要在腳本里引用的。

file

創建預裝Docker和k8s的Jenkins鏡像

Jenkins的默認容器裏面沒有Docker和k8s,因此我們需要在Jenkins鏡像的基礎上重新創建新的鏡像,後面還會詳細講解。
下面是鏡像文件(Dockerfile-modified-jenkins)

FROM jenkins/jenkins:lts    USER root    ENV DOCKERVERSION=19.03.4    RUN curl -fsSLO https://download.docker.com/linux/static/stable/x86_64/docker-${DOCKERVERSION}.tgz     && tar xzvf docker-${DOCKERVERSION}.tgz --strip 1                    -C /usr/local/bin docker/docker     && rm docker-${DOCKERVERSION}.tgz    RUN curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl       && chmod +x ./kubectl       && mv ./kubectl /usr/local/bin/kubectl  

上面的鏡像在「jenkins/jenkins:lts」的基礎上又安裝了Docker和kubectl,這樣就支持這兩個軟件了。鏡像里使用的是docker的19.03.4版本。這裡裝的只是「Docker CLI」,沒有Docker引擎。用的時候還是要把虛擬機的卷掛載到容器上,使用虛機的Docker引擎。因此最好保證容器里的Docker版本和虛機的Docker版本一致。

使用如下命令查看Docker版本:

vagrant@ubuntu-xenial:/$ docker version

詳細情況請參見Configure a CI/CD pipeline with Jenkins on Kubernetes

準備工作已經完成,現在要正式創建Jenkins項目:

Jenkins腳本:

項目的創建是在Jenkins的主頁上來完成,它的名字是「jenkins-k8sdemo」,它的最主要部分是腳本代碼,它也跟Go程序存放在相同的源碼庫中,文件的名字也是「jenkins-k8sdemo」。項目的腳本頁面如下圖所示。

file

如果你不熟悉安裝和創建Jenkins項目,請參閱在k8s上安裝Jenkins及常見問題

下面就是jenkins-k8sdemo腳本文件:

def POD_LABEL = "k8sdemopod-${UUID.randomUUID().toString()}"  podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [      containerTemplate(name: 'modified-jenkins', image: 'jfeng45/modified-jenkins:1.0', ttyEnabled: true, command: 'cat')    ],    volumes: [       hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')    ]) {        node(POD_LABEL) {         def kubBackendDirectory = "/script/kubernetes/backend"         stage('Checkout') {              container('modified-jenkins') {                  sh 'echo get source from github'                  git 'https://github.com/jfeng45/k8sdemo'              }            }         stage('Build image') {              def imageName = "jfeng45/jenkins-k8sdemo:${env.BUILD_NUMBER}"              def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend"               container('modified-jenkins') {                 withCredentials([[$class: 'UsernamePasswordMultiBinding',                   credentialsId: 'dockerhub',                   usernameVariable: 'DOCKER_HUB_USER',                   passwordVariable: 'DOCKER_HUB_PASSWORD']]) {                   sh """                     docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}                     docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} .                     docker push ${imageName}                     """                 }               }             }         stage('Deploy') {             container('modified-jenkins') {                 sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml"                 sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-service.yaml"               }         }      }  }      

我們逐段看一下代碼:

設定容器鏡像:

podTemplate(label: POD_LABEL, cloud: 'kubernetes', containers: [      containerTemplate(name: 'modified-jenkins', image: 'jfeng45/modified-jenkins:1.0', ttyEnabled: true, command: 'cat')    ],    volumes: [       hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')    ])  

這裡設定Jenkins子節點Pod的容器鏡像,用的是「jfeng45/modified-jenkins:1.0」,也就是我們在上個步驟創建的。所有的腳本里的步驟(stage)都用的是這個鏡像。「volumes:」用來掛載卷到Jenkins容器中,這樣Jenkins子節點就可以使用虛機的Docker引擎。

關於Jenkins腳本命令和設置掛載卷請參閱jenkinsci/kubernetes-plugin

創建鏡像:

下面的代碼生成Go程序的Docker鏡像文件,這裡我們沒有用Docker插件,而是直接調用Docker命令,它的好處後面會講到。它引用了我們前面設置的「Docker hub」的憑證去訪問Docker庫。在腳本里,我們先登錄到「Docker hub」,然後使用上一步從GitHub下載的源代碼來創建鏡像,最後上傳鏡像到「Docker hub」。其中「${WORKSPACE}」是Jenkins預定義變量,從GitHub下載的源代碼就存放在「${WORKSPACE}」里。

stage('Build image') {                def imageName = "jfeng45/jenkins-k8sdemo:${env.BUILD_NUMBER}"              def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend"               container('modified-jenkins') {                 withCredentials([[$class: 'UsernamePasswordMultiBinding',                   credentialsId: 'dockerhub',                   usernameVariable: 'DOCKER_HUB_USER',                   passwordVariable: 'DOCKER_HUB_PASSWORD']]) {                   sh """                     docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}                     docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} .                     docker push ${imageName}                     """                 }               }             }

如果你想了解Jenkins命令詳情,請參閱Set Up a Jenkins CI/CD Pipeline with Kubernetes

我們這裡並沒有重新生成Go程序的鏡像文件,而是復用了以前就有的k8s創建Go程序的鏡像文件,Go程序的鏡像文件路徑是「scriptkubernetesbackenddockerDockerfile-k8sdemo-backend」。
它的代碼如下。後面還會講到這樣做的好處。

# vagrant@ubuntu-xenial:~/app/k8sdemo/script/kubernetes/backend$  # docker build -t k8sdemo-backend .    FROM golang:latest as builder    # Set the Current Working Directory inside the container  WORKDIR /app    COPY go.mod go.sum ./    RUN go mod download    COPY . .    WORKDIR /app/cmd    # Build the Go app  #RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main.exe    RUN go build -o main.exe    ######## Start a new stage from scratch #######  FROM alpine:latest    RUN apk --no-cache add ca-certificates    WORKDIR /root/    RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2    # Copy the Pre-built binary file from the previous stage  COPY --from=builder /app/cmd/main.exe .    # Command to run the executable  # CMD exec /bin/bash -c "trap : TERM INT; sleep infinity & wait"  CMD  

關於Go鏡像文件詳情,請參閱創建優化的Go鏡像文件以及踩過的坑

部署鏡像:

下面部署Go程序到k8s上,這裡也沒有用kubectl插件,而是直接用kubectl命令調用已經存在的k8s的部署和服務配置文件(文件里會引用生成的Go鏡像),它的好處後面也會講到。

 stage('Deploy') {             container('modified-jenkins') {                 sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml"                 sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-service.yaml"               }         }

關於k8s的部署和服務配置文件詳情,請參閱把應用程序遷移到k8s需要修改什麼?

為什麼沒用Declarative?

用腳本來寫Pipeline有兩種方法,「Scripted Pipleline」和「Declarative Pipleline」,這裡用的是第一種方法。 「Declarative Pipleline」是新的方法,之所以沒用它,是因為開始用的是Declarative模式但沒調出來,然後就改用「Scripted Pipleline」,結果成功了。後來才發現設置Declarative的方法,特別是如何掛載卷,但看了一下,比起「Scripted Pipleline」要複雜不少,就偷了一下懶,沒有再改。

如果你想知道怎樣在Declarative模式下設置掛載卷,請參閱Jenkins Pipeline Kubernetes Agent shared Volumes

自動執行項目:

現在的Jenkins中的項目需要手動啟動,如果你需要自動啟動項目的話就要創建webhook,GitHub和dockerhub都支持webhook,在它們的頁面上都有設置選項。「webhook」是一個反向調用的URL,每當有新的代碼或鏡像提交時,GitHub和dockerhub都會調用這個URL,URL被設置成Jenkins的項目地址,這樣相關的項目就會自動啟動。

檢驗結果:

現在Jenkins的項目就完全配置好了,需要運行項目,檢驗結果。啟動項目後,
查看「Console Output」,下面是部分輸出(全部輸出太長,請看附錄),說明部署成功。

。。。  + kubectl apply -f /home/jenkins/workspace/test1/script/kubernetes/backend/backend-deployment.yaml  deployment.apps/k8sdemo-backend-deployment created  [Pipeline] sh+ kubectl apply -f /home/jenkins/workspace/test1/script/kubernetes/backend/backend-service.yaml  service/k8sdemo-backend-service created  [Pipeline] }  [Pipeline] // container  [Pipeline] }  [Pipeline] // stage  [Pipeline] }  [Pipeline] // node  [Pipeline] }  [Pipeline] // podTemplate  [Pipeline] End of Pipeline  Finished: SUCCESS  

查看運行結果:
獲得Pod名字:

vagrant@ubuntu-xenial:/home$ kubectl get pod  NAME                                           READY   STATUS    RESTARTS   AGE  envar-demo                                     1/1     Running   15         32d  k8sdemo-backend-deployment-6b99dc6b8c-8kxt9    1/1     Running   0          50s  k8sdemo-database-deployment-578fc88c88-mm6x8   1/1     Running   9          20d  k8sdemo-jenkins-deployment-675dd574cb-r57sb    1/1     Running   0          2d23h

登錄Pod並運行程序:

vagrant@ubuntu-xenial:/home$ kubectl exec -ti k8sdemo-backend-deployment-6b99dc6b8c-8kxt9 -- /bin/sh  ~ # ./main.exe  DEBU[0000] connect to database  DEBU[0000] dataSourceName:dbuser:dbuser@tcp(k8sdemo-database-service:3306)/service_config?charset=utf8  DEBU[0000] FindAll()  DEBU[0000] created=2019-10-21  DEBU[0000] find user:{1 Tony IT 2019-10-21}  DEBU[0000] find user list:[{1 Tony IT 2019-10-21}]  DEBU[0000] user lst:[{1 Tony IT 2019-10-21}]

結果正確。

Jenkins原理

實例部分已經結束,下面來探討最佳實踐。在這之前,先要搞清楚Jenkins的原理。

可執行命令

我一直有一個問題就是那些命令是Jenkins可以通過shell執行的?Jenkins和Docker、k8s不同,後者有自己的一套命令,只要把它們學會了就行了。而Jenkins是通過與別的系統集成來工作的,因此它的可執行命令與其他系統有關,這導致了你很難知道那些命令是可以執行的,那些不行。你需要弄懂它的原理,才能得到答案。當Jenkins執行腳本時,主節點會自動生成一個子節點(Docker容器),所有的Jenkins命令都是在這個容器里執行的。所以能執行的命令與容器密切相關。一般來講,你可以通過shell來運行Linux命令。那下面的問題就來了:

  1. 為什麼我不能用Bash?

    因為你使用的子節點的容器可能使用的是精簡版的Linux,例如Alpine,它是沒有Bash的。

  2. 為什麼我不能運行Docker命令或Kubectl?

    因為它的默認容器是jenkinsci/jnlp-slave,而它裏面沒有預裝Docker或kubectl。你可以不使用默認容器,而是指定你自己的容器,並在其中預裝上述軟件,那麼就可以執行這些命令了。

如何共享文件

一個Jenkins項目通常要分成幾個步驟(stage)來完成,例如你下載的源碼要在幾個步驟之間共享,那怎麼共享呢?Jenkins為每個項目分配了一個WORKSPACE(磁盤空間), 裏面存儲了所有從源碼庫和其他地方下載的文件,不同stage之間可以通過WORKSPACE來共享文件。

關於WORKSPACE詳情,請參閱Jenkins Project Artifacts and Workspace

最佳實踐

要總結最佳實踐就要理解持續部署在整個開發流程中的作用和位置,它主要起一個串接各個環節的作用。而程序的部署是由k8s和Docker來完成的,因此程序部署的腳本也都在k8s中,並由k8s來維護。我們不想在Jenkins里再維護一套類似的腳本,因此最好的辦法是把Jenkins的腳本壓縮到最小,儘可能多地直接調用k8s的腳本。

另外能寫代碼就不要在頁面上配置,只有代碼是可以重複執行並保證穩定結果的,頁面配置不能移植,而且不能保證每次配置都產生一樣的結果。

盡量少使用插件

Jenkins有許多插件,基本上你想要完成什麼功能都有相應的插件。例如你需要使用Docker功能,就有「Docker Pipeline」插件,你要使用k8s功能就有「kubectl」插件。但它會帶來很多的問題。

  • 第一,每個插件都有他自己的設置方式(一般要在Jenkins插件頁面進行設置),但這種設置是與其他持續部署工具不兼容的。如果以後你要遷移到其他持續部署工具,這些設置都需要廢棄。
  • 第二,每個插件都有自己的命令格式,因此你需要另外學習一套新的命令。
  • 第三,這些插件往往只支持部分功能,使你能做的事情受到了限制。

例如,你需要創建一個Docker鏡像文件,命令如下,它將創建一個名為"jfeng45/jenkins-k8sdemo"的鏡像,鏡像的默認文件是在項目的根目錄下的Dockerfile。

app = docker.build("jfeng45/jenkins-k8sdemo")

但創建Docker鏡像文件命令有許多參數選項,例如,你的鏡像文件名不是Dockerfile,並且目錄不是在項目根目錄下,應如何寫呢?這在以前的版本是不支持的,後來的版本支持了,但畢竟不太方便,還要學新的命令。最好的辦法是能直接使用Docker命令,這樣就完美的解決了上面說的三個問題。答案就在前面講的Jenkins原理里,其實絕大多數插件都是不需要的,你只要自己創建一個Jenkins子節點容器,並安裝相應的軟件就能圓滿解決。

下面是使用插件的腳本和不使用的對比,不使用的看起來更長,那時因為使用插件的腳本和Jenkins里的憑證設置有更好的集成,而不使用的腳本沒有。但除了這個小缺點,其他方面不使用的腳本都要遠遠優於使用插件的。

使用插件的腳本(用插件命令):

stage('Create Docker images') {    container('docker') {        app = docker.build("jfeng45/codedemo", "-f ${WORKSPACE}/script/kubernetes/backend/docker/Dockerfile-k8sdemo-test .")        docker.withRegistry('', 'dockerhub') {            // Push image and tag it with our build number for versioning purposes.            app.push("${env.BUILD_NUMBER}")        }      }    }

不使用插件的腳本(直接用Docker命令):

stage('Create a d ocker image') {       def imageName = "jfeng45/codedemo:${env.BUILD_NUMBER}"       def dockerDirectory = "${kubBackendDirectory}/docker/Dockerfile-k8sdemo-backend"        container('modified-jenkins') {          withCredentials([[$class: 'UsernamePasswordMultiBinding',            credentialsId: 'dockerhub',            usernameVariable: 'DOCKER_HUB_USER',            passwordVariable: 'DOCKER_HUB_PASSWORD']]) {            sh """              docker login -u ${DOCKER_HUB_USER} -p ${DOCKER_HUB_PASSWORD}              docker build -f ${WORKSPACE}${dockerDirectory} -t ${imageName} .              docker push ${imageName}              """          }        }      }

盡量多使用k8s和Dcoker

例如我們要創建一個應用程序的鏡像,我們可以寫一個Docker文件,並在Jenkins腳本里調用這個Docker文件來創建,也可以寫一個Jenkins腳本,在腳本里來創建鏡像。比較好的方法是前者。因為Docker和k8s都是事實上的標準,移植起來很方便。

Jenkins腳本的代碼越少越好

如果你認同前面兩個原則,那麼這一條就是順理成章的,原因也和上面是一樣的。

常見問題:

1.變量要放在雙引號里
Jenkins的腳本即可以使用單引號也可以使用雙引號,但如果你在引號里引用了變量,那麼就要使用雙引號。

正確的命令:

sh "kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml"

錯誤的命令:

sh 'kubectl apply -f ${WORKSPACE}${kubBackendDirectory}/backend-deployment.yaml'

2.docker not found

如果Jenkins的容器里沒有Docker,但你又調用了Docker命令,那麼「Console Output」里就會有如下錯誤:

+ docker inspect -f . k8sdemo-backend:latest  /var/jenkins_home/workspace/k8sdec@2@tmp/durable-01e26997/script.sh: 1:     /var/jenkins_home/workspace/k8sdec@2@tmp/durable-01e26997/script.sh: docker:     not found

3.Jenkins宕機了

在調試Jenkins時,我新創建了一個鏡像文件並上傳到「Docker hub」之後就發現Jenkins宕機了。檢查了Pod,發現了問題,k8s找不到Jenkins的鏡像文件了(鏡像文件從磁盤上消失了)。因為Jenkins的部署文件的設置是「imagePullPolicy: Never」,所以一旦鏡像沒有了,它不會自動重新下載。後來找到了原因,Vagrant的默認磁盤大小是10G,如果空間不夠,它會自動從磁盤上刪除其他鏡像文件,騰出空間,結果就把Jenkins的鏡像文件給刪了,解決方案是擴充Vagrant的磁盤大小。

下面是修改之後的Vagrantfile,把磁盤空間改成了16G。

Vagrant.configure(2) do |config|       。。。       config.vm.box = "ubuntu/xenial64"       config.disksize.size = '16GB'       。。。  end

詳情請見How can I increase disk size on a Vagrant VM?

源碼:

完整源碼的github鏈接

下面是項目中與本文有關的部分:

file

索引

  1. Nexus Platform Overview
  2. Configure a CI/CD pipeline with Jenkins on Kubernetes
  3. 在k8s上安裝Jenkins及常見問題
  4. jenkinsci/kubernetes-plugin
  5. Jenkins Pipeline Kubernetes Agent shared Volumes
  6. Set Up a Jenkins CI/CD Pipeline with Kubernetes
  7. 創建優化的Go鏡像文件以及踩過的坑
  8. 把應用程序遷移到k8s需要修改什麼?
  9. Jenkins Pipeline Kubernetes Agent shared Volumes
  10. Jenkins Project Artifacts and Workspace
  11. How can I increase disk size on a Vagrant VM?

附錄:

下面是Jenkins項目運行後的完整的「Console Output」:

Running in Durability level: MAX_SURVIVABILITY  [Pipeline] Start of Pipeline  [Pipeline] podTemplate  [Pipeline] {  [Pipeline] node  Still waiting to schedule task  『k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3』 is offline  Agent k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3 is provisioned from template Kubernetes Pod Template  Agent specification [Kubernetes Pod Template] (k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa):  * [modified-jenkins] jfeng45/modified-jenkins:1.0    Running on k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3 in /home/jenkins/workspace/jenkins-k8sdemo  [Pipeline] {  [Pipeline] stage  [Pipeline] { (Checkout)  [Pipeline] container  [Pipeline] {  [Pipeline] sh  + echo get source from github  get source from github  [Pipeline] git  No credentials specified  Cloning the remote Git repository  Cloning repository https://github.com/jfeng45/k8sdemo   > git init /home/jenkins/workspace/jenkins-k8sdemo # timeout=10  Fetching upstream changes from https://github.com/jfeng45/k8sdemo   > git --version # timeout=10   > git fetch --tags --force --progress -- https://github.com/jfeng45/k8sdemo +refs/heads/*:refs/remotes/origin/*   > git config remote.origin.url https://github.com/jfeng45/k8sdemo # timeout=10   > git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10   > git config remote.origin.url https://github.com/jfeng45/k8sdemo # timeout=10  Fetching upstream changes from https://github.com/jfeng45/k8sdemo   > git fetch --tags --force --progress -- https://github.com/jfeng45/k8sdemo +refs/heads/*:refs/remotes/origin/*  Checking out Revision 90c57dcd8ff362d01631a54125129090b503364b (refs/remotes/origin/master)   > git rev-parse refs/remotes/origin/master^{commit} # timeout=10   > git rev-parse refs/remotes/origin/origin/master^{commit} # timeout=10   > git config core.sparsecheckout # timeout=10   > git checkout -f 90c57dcd8ff362d01631a54125129090b503364b   > git branch -a -v --no-abbrev # timeout=10   > git checkout -b master 90c57dcd8ff362d01631a54125129090b503364b  Commit message: "added jenkins continous deployment files"  [Pipeline] }   > git rev-list --no-walk 90c57dcd8ff362d01631a54125129090b503364b # timeout=10  [Pipeline] // container  [Pipeline] }  [Pipeline] // stage  [Pipeline] stage  [Pipeline] { (Build image)  [Pipeline] container  [Pipeline] {  [Pipeline] withCredentials  Masking supported pattern matches of $DOCKER_HUB_USER or $DOCKER_HUB_PASSWORD  [Pipeline] {  [Pipeline] sh  + docker login -u **** -p ****  WARNING! Using --password via the CLI is insecure. Use --password-stdin.  WARNING! Your password will be stored unencrypted in /home/jenkins/.docker/config.json.  Configure a credential helper to remove this warning. See  https://docs.docker.com/engine/reference/commandline/login/#credentials-store    Login Succeeded  + docker build -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/docker/Dockerfile-k8sdemo-backend -t ****/jenkins-k8sdemo:7 .  Sending build context to Docker daemon  218.6kB    Step 1/13 : FROM golang:latest as builder   ---> dc7582e06f8e  Step 2/13 : WORKDIR /app   ---> Running in c5770704333e  Removing intermediate container c5770704333e   ---> 73445078c82d  Step 3/13 : COPY go.mod go.sum ./   ---> 6762344c7bc8  Step 4/13 : RUN go mod download   ---> Running in 56a1f253c3f5  go: finding github.com/davecgh/go-spew v1.1.1  go: finding github.com/go-sql-driver/mysql v1.4.1  go: finding github.com/konsorten/go-windows-terminal-sequences v1.0.1  go: finding github.com/pkg/errors v0.8.1  go: finding github.com/pmezard/go-difflib v1.0.0  go: finding github.com/sirupsen/logrus v1.4.2  go: finding github.com/stretchr/objx v0.1.1  go: finding github.com/stretchr/testify v1.2.2  go: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894  Removing intermediate container 56a1f253c3f5   ---> 455ef98244eb  Step 5/13 : COPY . .   ---> 092444c8a5ef  Step 6/13 : WORKDIR /app/cmd   ---> Running in 558240a3dcb1  Removing intermediate container 558240a3dcb1   ---> 044e01b8184b  Step 7/13 : RUN go build -o main.exe   ---> Running in 648899ba522c  Removing intermediate container 648899ba522c   ---> 69f6652bc706  Step 8/13 : FROM alpine:latest   ---> 965ea09ff2eb  Step 9/13 : RUN apk --no-cache add ca-certificates   ---> Using cache   ---> a27265887a1e  Step 10/13 : WORKDIR /root/   ---> Using cache   ---> b9c048c97f07  Step 11/13 : RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2   ---> Using cache   ---> 95a2b77e3e0a  Step 12/13 : COPY --from=builder /app/cmd/main.exe .   ---> Using cache   ---> c5dc6dfdf037  Step 13/13 : CMD exec /bin/sh -c "trap : TERM INT; (while true; do sleep 1000; done) & wait"   ---> Using cache   ---> b141558cb0f3  Successfully built b141558cb0f3  Successfully tagged ****/jenkins-k8sdemo:7  + docker push ****/jenkins-k8sdemo:7  The push refers to repository [docker.io/****/jenkins-k8sdemo]  0e5809dd35f7: Preparing  8861feb71103: Preparing  5b63d4bd63b4: Preparing  77cae8ab23bf: Preparing  77cae8ab23bf: Mounted from ****/codedemo  8861feb71103: Mounted from ****/codedemo  5b63d4bd63b4: Mounted from ****/codedemo  0e5809dd35f7: Mounted from ****/codedemo  7: digest: sha256:95c780bb08793712cd2af668c9d4529e17c99e58dfb05ffe8df6a762f245ce10 size: 1156  [Pipeline] }  [Pipeline] // withCredentials  [Pipeline] }  [Pipeline] // container  [Pipeline] }  [Pipeline] // stage  [Pipeline] stage  [Pipeline] { (Deploy)  [Pipeline] container  [Pipeline] {  [Pipeline] sh  + kubectl apply -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/backend-deployment.yaml  deployment.apps/k8sdemo-backend-deployment created  [Pipeline] sh  + kubectl apply -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/backend-service.yaml  service/k8sdemo-backend-service created  [Pipeline] }  [Pipeline] // container  [Pipeline] }  [Pipeline] // stage  [Pipeline] }  [Pipeline] // node  [Pipeline] }  [Pipeline] // podTemplate  [Pipeline] End of Pipeline  Finished: SUCCESS