欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

在容器上构建持续部署及最佳实践初探

程序员文章站 2022-05-09 16:21:39
要想理解持续集成和持续部署,先要了解它的部分组成,以及各个组成部分之间的关系。下面这张图是我见过的最简洁、清晰的持续部署和集成的关系图。 "图片来源" 持续部署: 如图所示,开发的流程是这样的: 程序员从源码库(Source Control)中下载源代码,编写程序,完成后提交代码到源码库,持续集成( ......

要想理解持续集成和持续部署,先要了解它的部分组成,以及各个组成部分之间的关系。下面这张图是我见过的最简洁、清晰的持续部署和集成的关系图。

在容器上构建持续部署及最佳实践初探

持续部署:

如图所示,开发的流程是这样的:
程序员从源码库(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”是后面你要在脚本里引用的。

在容器上构建持续部署及最佳实践初探

创建预装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”。项目的脚本页面如下图所示。

在容器上构建持续部署及最佳实践初探

如果你不熟悉安装和创建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脚本命令和设置挂载卷请参阅

创建镜像:

下面的代码生成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程序的镜像文件路径是“\script\kubernetes\backend\docker\dockerfile-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的部署和服务配置文件详情,请参阅

为什么没用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?

源码:

下面是项目中与本文有关的部分:

在容器上构建持续部署及最佳实践初探

索引

  1. nexus platform overview
  2. configure a ci/cd pipeline with jenkins on kubernetes
  3. 在k8s上安装jenkins及常见问题
  4. jenkins pipeline kubernetes agent shared volumes
  5. set up a jenkins ci/cd pipeline with kubernetes
  6. 创建优化的go镜像文件以及踩过的坑
  7. jenkins pipeline kubernetes agent shared volumes
  8. jenkins project artifacts and workspace
  9. 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
[91mgo: finding github.com/davecgh/go-spew v1.1.1
[0m[91mgo: finding github.com/go-sql-driver/mysql v1.4.1
[0m[91mgo: finding github.com/konsorten/go-windows-terminal-sequences v1.0.1
[0m[91mgo: finding github.com/pkg/errors v0.8.1
[0m[91mgo: finding github.com/pmezard/go-difflib v1.0.0
[0m[91mgo: finding github.com/sirupsen/logrus v1.4.2
[0m[91mgo: finding github.com/stretchr/objx v0.1.1
[0m[91mgo: finding github.com/stretchr/testify v1.2.2
[0m[91mgo: finding golang.org/x/sys v0.0.0-20190422165155-953cdadca894
[0mremoving 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