万字干货:一步步教你如何在容器上构建持续部署!

作者| 倚天码农

 责编| 徐威龙

封图| CSDN下载于视觉中国

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

图源:sonatype

持续部署

如上图所示,开发的流程是这样的:

程序员从源码库(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:ltsUSER rootENV DOCKERVERSION=19.03.4RUN 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}.tgzRUN 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:

https://developer.ibm.com/tutorials/configure-a-cicd-pipeline-with-jenkins-on-kubernetes/

准备工作已经完成,现在要正式创建Jenkins项目:

Jenkins脚本:

项目的创建是在Jenkins的主页上来完成,它的名字是“jenkins-k8sdemo”,它的最主要部分是脚本代码,它也跟Go程序存放在相同的源码库中,文件的名字也是“jenkins-k8sdemo”。项目的脚本页面如下图所示。

如果你不熟悉安装和创建Jenkins项目,请参阅在k8s上安装Jenkins及常见问题:

https://blog.csdn.net/weixin_38748858/article/details/102898043

下面就是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命令详情,请参阅Set Up a Jenkins CI/CD Pipeline with Kubernetes:

https://akomljen.com/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 /appCOPY go.mod go.sum ./RUN go mod downloadCOPY . .WORKDIR /app/cmd# Build the Go app
#RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main.exeRUN go build -o main.exe######## Start a new stage from scratch #######
FROM alpine:latestRUN apk --no-cache add ca-certificatesWORKDIR /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镜像文件以及踩过的坑:https://blog.csdn.net/weixin_38748858/article/details/102714799

部署镜像:

下面部署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需要修改什么?:

https://blog.csdn.net/weixin_38748858/article/details/102758381

为什么没用Declarative?

用脚本来写Pipeline有两种方法,“Scripted Pipleline”和“Declarative Pipleline”,这里用的是第一种方法。“Declarative Pipleline”是新的方法,之所以没用它,是因为开始用的是Declarative模式但没调出来,然后就改用“Scripted Pipleline”,结果成功了。后来才发现设置Declarative的方法,特别是如何挂载卷,但看了一下,比起“Scripted Pipleline”要复杂不少,就偷了一下懒,没有再改。

如果你想知道怎样在Declarative模式下设置挂载卷,请参阅Jenkins Pipeline Kubernetes Agent shared Volumes:

https://devops.stackexchange.com/questions/4695/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:

https://stackoverflow.com/questions/39397329/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?:

https://askubuntu.com/questions/317338/how-can-i-increase-disk-size-on-a-vagrant-vm

源码:

完整源码的github链接:https://github.com/jfeng45/k8sdemo

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

附录:

下面是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.0Running on k8sdemopod-030ed100-cb28-4770-b6de-c491970e5baa-twb8s-k9pn3 in /home/jenkins/workspace/jenkins-k8sdemo
[Pipeline] {
[Pipeline] stage
[Pipeline] { (Checkout)
[Pipeline] container
[Pipeline] {
[Pipeline] shecho 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] shdocker 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-storeLogin Succeededdocker 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.6kBStep 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:7docker 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] shkubectl apply -f /home/jenkins/workspace/jenkins-k8sdemo/script/kubernetes/backend/backend-deployment.yaml
deployment.apps/k8sdemo-backend-deployment created
[Pipeline] shkubectl 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

本文由作者「倚天码农」首发自 CSDN 博客,原文链接:

https://blog.csdn.net/weixin_38748858/article/details/102967540,转载请经授权。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/518456.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

html-列表标签

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>列表学习</title> </head> <body><!-- 有序列表 应用范围&#xff1a; 试卷&#xff0c;问答。。。 --> <ol><li>…

阿里云环境中TLS/SSL握手失败的场景分析

TLS/SSL握手是一个相对复杂的过程&#xff0c;在阿里云环境中结合产品&#xff0c;安全等特性&#xff0c;可能会让TLS/SSL握手过程的不定性更多。本文来总结下各种握手失败的场景。 一次TLS/SSL握手的过程 本文不详细介绍TLS/SSL基础知识&#xff0c;相关介绍可以参考文章。…

千亿级的数据难题,优酷工程师怎么解决?

阿里妹导读&#xff1a;优酷一天的日志量会达到千亿级别&#xff0c;面对如此大的数据样本&#xff0c;2017年5月&#xff0c;优酷完成了从Hadoop迁移到阿里云MaxCompute&#xff0c;实现计算消耗和储存的消耗呈下降趋势&#xff0c;得到了非常大的收益。今天&#xff0c;阿里数…

热搜!华为:这类程序员领10亿,程序员:真香!你怎么看?

人工智能真的玩大了吗&#xff1f;人工智能行业的人才真的“爆发了&#xff1f;”华为&#xff1a;10亿培养AI人才程序员&#xff1a;真香&#xff01;你怎么看&#xff1f;最近&#xff0c;在AI圈里&#xff0c;发生了这样一件大事,华为宣布&#xff1a;计划投入10亿元人民币用…

html-表格标签

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>表格学习</title> </head> <body> <!-- 表格table 行 tr rows 列 td --> <table border"1px"><tr><…

玩转运维编排服务的权限:Assume Role+Pass Role

什么是运维编排服务&#xff1f; 阿里云运维编排服务&#xff08;Operation Orchestration Service&#xff0c;简称OOS&#xff09;是云上的自动化运维平台&#xff0c;提供运维任务的管理和执行。典型使用场景包括&#xff1a;事件驱动运维&#xff0c;批量操作运维&#xf…

机器学习在高德搜索建议中的应用优化实践

导读&#xff1a;高德的愿景是&#xff1a;连接真实世界&#xff0c;让出行更美好。为了实现愿景&#xff0c;我们要处理好LBS大数据和用户之间的智能链接。信息检索是其中的关键技术&#xff0c;而搜索建议又是检索服务不可或缺的组成部分。 本文将主要介绍机器学习在高德搜索…

IntelliJ IDEA 2020.x 入门到爱不释手

文章目录一、默认快捷键二、案例演示2.1. 查看最近浏览过的文件 | ctrle2.2. 根据行号定位代码 | ctrlg2.3. 导航栏快速切换2.4. 按照文本的内容替换-整个项目 |CtrlShiftr2.5. 按照文本的内容查找-整个项目 | CtrlShiftF2.6. 快速生成|ALTENTER2.7. 生成try..catch..等方法块 …

【IPF2020】浪潮集团副总裁、渠道管理部总经理王峰:赋能智慧生态 筑基新基建

目前关键计算的传统数据中心和科学计算的超算中心已经发展多年&#xff0c;而未来作为核心生产力的智慧计算的基础设施就是智算中心&#xff0c;这也是国家提出的新基建最重要的基础设施之一。 智慧生态作为智算中心建设的核心力量&#xff0c;浪潮将继续强化智慧生态的基础策…

html-媒体元素

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>媒体元素学习</title> </head> <body><!-- 音频和视频 src 资源路径 必填 不写 controls 视频不显示 &#xff0c; 控制条 autop…

如何在云上使用confd+ACM管理敏感数据

在前面的一些文章中&#xff0c;我们介绍了如何在云上安全的存放配置数据&#xff0c;但是上面的方法都是有代码侵入性的&#xff0c;也就是说需要修改应用程序&#xff0c;本文会讲解如何使用 confdACM 在不修改代码的情况下动态修改应用所需的配置&#xff0c;并且可以重新启…

windows下RocketMQ下载、安装、部署、控制台

linux 环境 RocketMQ 4.8.0 安装、部署控制台 https://gblfy.blog.csdn.net/article/details/116269833 文章目录一、软件下载二、安装、启动、配置2.1. 安装jdk1.8及maven2.2. 解压2.3. 配置环境变量2.4. 启动三、安装可视化插件3.1. github下载3.2. 解压3.3. 修改配置文件3.4…

阿里云InfluxDB® Raft HybridStorage实现方案

背景 阿里云InfluxDB是阿里云基于开源版InfluxDB打造的一款时序数据库产品&#xff0c;提供更稳定的持续运行状态、更丰富强大的时序数据计算能力。在现有的单节点版本之外&#xff0c;阿里云InfluxDB团队还将推出多节点的高可用版本。 我们知道现有的开源版InfluxDB只提供单…

灰度测试试验流量“洗牌”

什么是灰度测试 A/B测试系统的一个常用场景是App/小程序/后端服务精细化运营过程中的上线迭代管理&#xff0c;通常被称为灰度测试或者灰度上线。 详细来说&#xff0c;如果软件产品要在不久的将来推出一个全新的功能&#xff0c;或者做一次比较重大的改版的话&#xff0c;要…

html-页面结构分析

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>页面结构分析</title> </head> <body><header><h2>网页头部</h2></header><sectiokn><h2>网页…

阿里资深技术专家的10年感悟

阿里妹导读&#xff1a;阿里有许多土话&#xff0c;比如“方法总比困难多”、“不淘汰自己就会被别人淘汰”、“你感觉不舒服的时候&#xff0c;就是成长的时候”。每一句都在激励我们向前。生活总不缺困难和磨练&#xff0c;痛苦的时候&#xff0c;只有转变思维&#xff0c;才…

AnalyticDB for MySQL 3.0基础版重磅发布

随着大数据技术的迅速发展以及对数据价值的认识逐渐加深&#xff0c;大数据已经融合到各行各业。据可靠权威数据显示&#xff0c;超过39.6%的企业正在应用数据并从中获益&#xff0c;超过89.6%的企业已经成立或计划成立相关的大数据分析部&#xff0c;超过六成的企业在扩大数据…

主键索引 or 辅助索引?一文告诉你 Mysql limit 优化时的索引选择!

作者 | 吴海存责编 | 徐威龙封图| CSDN下载于视觉中国导读&#xff1a;本文主要针对limit分页时&#xff0c;是优先基于主键索引还是辅助索引等层面展开分析&#xff0c;对limit及offset的用法以及是否该用索引不会过多赘述。我们知道&#xff0c;在Mysql中可以通过limit实现快…

html-iframe内联框架

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>内联框架学习</title> </head> <body><!-- iframe内联框架 src : 地址 w-h : 宽度高度 --><iframe src"https://www.…

一个阿里产品经理眼中的“垃圾分类”

我叫切斯&#xff0c;是阿里巴巴的一名产品经理。今天和大家说说一个上线刚刚6天&#xff0c;已被500多万网友疯玩的AI——垃圾图像识别&#xff0c;可见垃圾分类苦天下网友久矣。 以下是一个产品经理的碎碎念~ 有人说它是“国内首款真正的垃圾图像识别产品”&#xff0c;对着…