基于 Kubernetes 构建企业 Jenkins 持续集成平台

1、部署Jenkins

新建kube-ops 命名空间

$ kubectl create namespace kube-ops
新建Deployment文件(jenkins2.yaml)
---apiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: jenkins2  namespace: kube-opsspec:  template:    metadata:      labels:        app: jenkins2    spec:      terminationGracePeriodSeconds: 10      serviceAccount: jenkins2      containers:      - name: jenkins        image: jenkins/jenkins:lts        imagePullPolicy: IfNotPresent        ports:        - containerPort: 8080          name: web          protocol: TCP        - containerPort: 50000          name: agent          protocol: TCP        resources:          limits:            cpu: 1000m            memory: 1Gi          requests:            cpu: 500m            memory: 512Mi        livenessProbe:          httpGet:            path: /login            port: 8080          initialDelaySeconds: 60          timeoutSeconds: 5          failureThreshold: 12         readinessProbe:          httpGet:            path: /login            port: 8080          initialDelaySeconds: 60          timeoutSeconds: 5          failureThreshold: 12        volumeMounts:        - name: jenkinshome          subPath: jenkins2          mountPath: /var/jenkins_home        env:        - name: LIMITS_MEMORY          valueFrom:            resourceFieldRef:              resource: limits.memory              divisor: 1Mi        - name: JAVA_OPTS          value: -Xmx$(LIMITS_MEMORY)m -XshowSettings:vm -Dhudson.slaves.NodeProvisioner.initialDelay=0 -Dhudson.slaves.NodeProvisioner.MARGIN=50 -Dhudson.slaves.NodeProvisioner.MARGIN0=0.85 -Duser.timezone=Asia/Shanghai      securityContext:        fsGroup: 1000      volumes:      - name: jenkinshome        persistentVolumeClaim:          claimName: opspvc
---apiVersion: v1kind: Servicemetadata:  name: jenkins2  namespace: kube-ops  labels:    app: jenkins2spec:  selector:    app: jenkins2  type: NodePort  ports:  - name: web    port: 8080    targetPort: web    nodePort: 30002  - name: agent    port: 50000    targetPort: agent

 

创建PVC对象:

我们将容器的 /var/jenkins_home 目录挂载到了一个名为 opspvc 的 PVC 对象上面,所以我们同样还得提前创建一个对应的 PVC 对象,我们可以使用 StorageClass 对象来自动创建。

apiVersion: v1kind: PersistentVolumemetadata:  name: opspvspec:  capacity:    storage: 20Gi  accessModes:  - ReadWriteMany  persistentVolumeReclaimPolicy: Delete  nfs:    server: 192.168.16.239    path: /data/k8s
---kind: PersistentVolumeClaimapiVersion: v1metadata:  name: opspvc  namespace: kube-opsspec:  accessModes:    - ReadWriteMany  resources:    requests:      storage: 20Gi

创建需要用到的 PVC 对象:

$ kubectl  create -f  opspvc.yaml

我们这里还需要使用到一个拥有相关权限的 serviceAccount:jenkins2,我们这里只是给 jenkins 赋予了一些必要的权限,当然如果你对 serviceAccount 的权限不是很熟悉的话,我们给这个 sa 绑定一个 cluster-admin 的集群角色权限也是可以的,当然这样具有一定的安全风险:(rbac.yaml)

apiVersion: v1kind: ServiceAccountmetadata:  name: jenkins2  namespace: kube-ops
---
kind: RoleapiVersion: rbac.authorization.k8s.io/v1beta1metadata:  name: jenkins2  namespace: kube-opsrules:  - apiGroups: ["extensions", "apps"]    resources: ["deployments"]    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]  - apiGroups: [""]    resources: ["services"]    verbs: ["create", "delete", "get", "list", "watch", "patch", "update"]  - apiGroups: [""]    resources: ["pods"]    verbs: ["create","delete","get","list","patch","update","watch"]  - apiGroups: [""]    resources: ["pods/exec"]    verbs: ["create","delete","get","list","patch","update","watch"]  - apiGroups: [""]    resources: ["pods/log"]    verbs: ["get","list","watch"]  - apiGroups: [""]    resources: ["secrets"]    verbs: ["get"]
---apiVersion: rbac.authorization.k8s.io/v1beta1kind: RoleBindingmetadata:  name: jenkins2  namespace: kube-opsroleRef:  apiGroup: rbac.authorization.k8s.io  kind: Role  name: jenkins2subjects:  - kind: ServiceAccount    name: jenkins2    namespace: kube-ops

 

因为默认的镜像使用的是 jenkins 这个用户,而我们通过 PVC 挂载到 nfs 服务器的共享数据目录下面却是 root 用户的,所以没有权限访问该目录,要解决该问题,也很简单,我只需要在 nfs 共享数据目录下面把我们的目录权限重新分配下即可:

$ chown -R 1000 /data/k8s/jenkins2

直接创建 Jenkins 服务:

$ kubectl create -f opspvc.yaml -f   rbac.yaml -f jenkins2.yaml

访问Jenkins:

$ kubectl  get  svc -n kube-opsNAME       TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)                          AGEjenkins2   NodePort   10.111.112.3   <none>        8080:30005/TCP,50000:30340/TCP 3h

访问 30005端口:

 

 

2、Kubernetes 动态创建 Jenkins Slave 配置

 

 

从图上可以看到 Jenkins Master 和 Jenkins Slave 以 Pod 形式运行在 Kubernetes 集群的 Node 上,Master 运行在其中一个节点,并且将其配置数据存储到一个 Volume 上去,Slave 运行在各个节点上,并且它不是一直处于运行状态,它会按照需求动态的创建并自动删除。

这种方式的工作流程大致为:当 Jenkins Master 接受到 Build 请求时,会根据配置的 Label 动态创建一个运行在 Pod 中的 Jenkins Slave 并注册到 Master 上,当运行完 Job 后,这个 Slave 会被注销并且这个 Pod 也会自动删除,恢复到最初状态。

 

那么我们使用这种方式带来了哪些好处呢?

  • 服务高可用,当 Jenkins Master 出现故障时,Kubernetes 会自动创建一个新的 Jenkins Master 容器,并且将 Volume 分配给新创建的容器,保证数据不丢失,从而达到集群服务高可用。

  • 动态伸缩,合理使用资源,每次运行 Job 时,会自动创建一个 Jenkins Slave,Job 完成后,Slave 自动注销并删除容器,资源自动释放,而且 Kubernetes 会根据每个资源的使用情况,动态分配 Slave 到空闲的节点上创建,降低出现因某节点资源利用率高,还排队等待在该节点的情况。

  • 扩展性好,当 Kubernetes 集群的资源严重不足而导致 Job 排队等待时,可以很容易的添加一个 Kubernetes Node 到集群中,从而实现扩展。

 

安装kubernetes plugin,让他能够动态的生成 Slave 的 Pod。

 

 

安装完毕后,点击 Manage Jenkins —> Configure System —> (拖到最下方)Add a new cloud —> 选择 Kubernetes,然后填写 Kubernetes 和 Jenkins 配置信息。

 

 

注意 namespace,我们这里填 kube-ops,然后点击Test Connection,如果出现 Connection test successful 的提示信息证明 Jenkins 已经可以和 Kubernetes 系统正常通信了,然后下方的 Jenkins URL 地址:http://jenkins2.kube-ops.svc.cluster.local:8080,这里的格式为:服务名.namespace.svc.cluster.local:8080,根据上面创建的jenkins 的服务名填写,我这里是之前创建的名为jenkins,如果是用上面我们创建的就应该是jenkins2。


第3步配置 Pod Template,其实就是配置 Jenkins Slave 运行的 Pod 模板,命名空间我们同样是用 kube-ops,Labels 这里也非常重要,对于后面执行 Job 的时候需要用到该值,然后我们这里使用的是 cnych/jenkins:jnlp 这个镜像,这个镜像是在官方的 jnlp 镜像基础上定制的,加入了 kubectl 等一些实用的工具。

 

 

另外需要注意我们这里需要在下面挂载两个主机目录,一个是 /var/run/docker.sock,该文件是用于 Pod 中的容器能够共享宿主机的 Docker,这就是大家说的 docker in docker 的方式,Docker 二进制文件我们已经打包到上面的镜像中了,另外一个目录下 /root/.kube 目录,我们将这个目录挂载到容器的 /home/jenkins/.kube 目录下面这是为了让我们能够在 Pod 的容器中能够使用 kubectl 工具来访问我们的 Kubernetes 集群,方便我们后面在 Slave Pod 部署 Kubernetes 应用。

 

 

另外还有几个参数需要注意,如下图中的Time in minutes to retain slave when idle,这个参数表示的意思是当处于空闲状态的时候保留 Slave Pod 多长时间,这个参数最好我们保存默认就行了,如果你设置过大的话,Job 任务执行完成后,对应的 Slave Pod 就不会立即被销毁删除。

 

 

测试:
Kubernetes 插件的配置工作完成了,接下来我们就来添加一个 Job 任务,看是否能够在 Slave Pod 中执行,任务执行完成后看 Pod 是否会被销毁。

 

在 Jenkins 首页点击create new jobs,创建一个测试的任务,输入任务名称,然后我们选择 Freestyle project 类型的任务:

 

 

注意在下面的 Label Expression 这里要填入haimaxy-jnlp,就是前面我们配置的 Slave Pod 中的 Label,这两个地方必须保持一致

 


然后往下拉,在 Build 区域选择Execute shell

 

 

然后输入我们测试命令:

echo "测试 Kubernetes 动态生成 jenkins slave"echo "==============docker in docker==========="docker info
echo "=============kubectl============="kubectl get pods

 

 

保存后我们直接在页面点击做成的 Build now 触发构建即可,然后观察 Kubernetes 集群中 Pod 的变化

$ kubectl get pods -n kube-opsNAME                       READY     STATUS              RESTARTS   AGEjenkins2-7c85b6f4bd-rfqgv   1/1       Running             3          1djnlp-hfmvd

 

可以看到在我们点击立刻构建的时候可以看到一个新的 Pod:jnlp-hfmvd 被创建了,这就是我们的 Jenkins Slave。任务执行完成后我们可以看到任务信息,比如我们这里是 花费了 5.2s 时间在 jnlp-hfmvd 这个 Slave上面

 

 

到这里证明我们的任务已经构建完成,然后这个时候我们再去集群查看我们的 Pod 列表,发现 kube-ops 这个 namespace 下面已经没有之前的 Slave 这个 Pod 了。

$ kubectl get pods -n kube-opsNAME                       READY     STATUS    RESTARTS   AGEjenkins2-7c85b6f4bd-rfqgv   1/1       Running   3          1d

到这里我们就完成了使用 Kubernetes 动态生成 Jenkins Slave 的方法。

 

3、Jnekins Pipeline 介绍与动态生成 Jenkins Slave 

Jenkins Pipeline 介绍
要实现在 Jenkins 中的构建工作,可以有多种方式,我们这里采用比较常用的 Pipeline 这种方式。Pipeline,简单来说,就是一套运行在 Jenkins 上的工作流框架,将原来独立运行于单个或者多个节点的任务连接起来,实现单个任务难以完成的复杂流程编排和可视化的工作。

 

Jenkins Pipeline 有几个核心概念:

  • Node:节点,一个 Node 就是一个 Jenkins 节点,Master 或者 Agent,是执行 Step 的具体运行环境,比如我们之前动态运行的 Jenkins Slave 就是一个 Node 节点

  • Stage:阶段,一个 Pipeline 可以划分为若干个 Stage,每个 Stage 代表一组操作,比如:Build、Test、Deploy,Stage 是一个逻辑分组的概念,可以跨多个 Node

  • Step:步骤,Step 是最基本的操作单元,可以是打印一句话,也可以是构建一个 Docker 镜像,由各类 Jenkins 插件提供,比如命令:sh 'make',就相当于我们平时 shell 终端中执行 make 命令一样。

 

那么我们如何创建 Jenkins Pipline 呢?

  • Pipeline 脚本是由 Groovy 语言实现的,但是我们没必要单独去学习 Groovy,当然你会的话最好

  • Pipeline 支持两种语法:Declarative(声明式)和 Scripted Pipeline(脚本式)语法

  • Pipeline 也有两种创建方法:可以直接在 Jenkins 的 Web UI 界面中输入脚本;也可以通过创建一个 Jenkinsfile 脚本文件放入项目源码库中

  • 一般我们都推荐在 Jenkins 中直接从源代码控制(SCMD)中直接载入 Jenkinsfile Pipeline 这种方法

 

在 Slave 中构建任务:

我们在添加 Slave Pod 的时候,记的添加的 label 吗?没错,我们就需要用到这个 label,我们重新编辑上面创建的 Pipeline 脚本,给 node 添加一个 label 属性,如下:

node('haimaxy-jnlp') {    stage('Clone') {      echo "1.Clone Stage"    }    stage('Test') {      echo "2.Test Stage"    }    stage('Build') {      echo "3.Build Stage"    }    stage('Deploy') {      echo "4. Deploy Stage"    }}

 

构建之前查看下 kubernetes 集群中的 Pod:

$ kubectl get pods -n kube-opsNAME                       READY     STATUS    RESTARTS   AGEjenkins-7c85b6f4bd-rfqgv   1/1       Running   4          6djnlp-0hrrz                 1/1       Running   0          23s

 

我们发现多了一个名叫jnlp-0hrrz的 Pod 正在运行,隔一会儿这个 Pod 就消失了:这也证明我们的 Job 构建完成了,同样回到 Jenkins 的 Web UI 界面中查看 Console Output,可以看到如下的信息:

 

 

 

4、Jenkins Pipeline 自动化在 kubernetes 部署应用

要部署 Kubernetes 应用,我们就得对我们之前部署应用的流程要非常熟悉才行,我们之前的流程是怎样的:

  • 编写代码

  • 测试

  • 编写 Dockerfile

  • 构建打包 Docker 镜像

  • 推送 Docker 镜像到仓库

  • 编写 Kubernetes YAML 文件

  • 更改 YAML 文件中 Docker 镜像 TAG

  • 利用 kubectl 工具部署应用

  • 现在我们就需要把上面这些流程放入 Jenkins 中来自动帮我们完成(当然编码除外),从测试到更新 YAML 文件属于 CI 流程,后面部署属于 CD 的流程。如果按照我们上面的示例,我们现在要来编写一个 Pipeline 的脚本,应该怎么编写呢?

node('haimaxy-jnlp') {    stage('Clone') {      echo "1.Clone Stage"    }    stage('Test') {      echo "2.Test Stage"    }    stage('Build') {      echo "3.Build Docker Image Stage"    }    stage('Push') {      echo "4.Push Docker Image Stage"    }    stage('YAML') {      echo "5. Change YAML File Stage"    }    stage('Deploy') {      echo "6. Deploy Stage"    }}

 

我们来将一个简单 golang 程序,部署到 kubernetes 环境中,代码链接:(http://gitlab.unmin.club:82/root/jenkins-demo)。如果按照之前的示例,我们是不是应该像这样来编写 Pipeline 脚本:

  • 第一步,clone 代码,这个没得说吧

  • 第二步,进行测试,如果测试通过了才继续下面的任务

  • 第三步,由于 Dockerfile 基本上都是放入源码中进行管理的,所* 以我们这里就是直接构建 Docker 镜像了

  • 第四步,镜像打包完成,就应该推送到镜像仓库中吧

  • 第五步,镜像推送完成,是不是需要更改 YAML 文件中的镜像 TAG 为这次镜像的 TAG

  • 第六步,万事俱备,只差最后一步,使用 kubectl 命令行工具进行部署了

到这里我们的整个 CI/CD 的流程是不是就都完成了。

接下来我们就来对每一步具体要做的事情进行详细描述就行了。


第一步,Clone 代码

  •  
  •  
  •  
stage('Clone') {    echo "1.Clone Stage"    git url: "http://gitlab.unmin.club:82/root/jenkins-demo.git"}

第二步,测试

由于我们这里比较简单,忽略该步骤即可。
第三步,构建镜像

  •  
  •  
  •  
stage('Build') {    echo "3.Build Docker Image Stage"    sh "docker build -t zam2017/jenkins-demo:${build_tag} ."}

我们平时构建的时候是不是都是直接使用docker build命令进行构建就行了,那么这个地方呢?我们上节课给大家提供的 Slave Pod 的镜像里面是不是采用的 Docker In Docker 的方式,也就是说我们也可以直接在 Slave 中使用 docker build 命令,所以我们这里直接使用 sh 直接执行 docker build 命令即可。

但是镜像的 tag 呢?如果我们使用镜像 tag,则每次都是 latest 的 tag,这对于以后的排查或者回滚之类的工作会带来很大麻烦,我们这里采用和git commit的记录为镜像的 tag,这里有一个好处就是镜像的 tag 可以和 git 提交记录对应起来,也方便日后对应查看。但是由于这个 tag 不只是我们这一个 stage 需要使用,下一个推送镜像是不是也需要,所以这里我们把这个 tag 编写成一个公共的参数,把它放在 Clone 这个 stage 中,这样一来我们前两个 stage 就变成了下面这个样子:

 

stage('Clone') {    echo "1.Clone Stage"    git url: "http://gitlab.unmin.club:82/root/jenkins-demo.git"    script {        build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()    }}stage('Build') {    echo "3.Build Docker Image Stage"    sh "docker build -t zam2017/jenkins-demo:${build_tag} ."}

第四步,推送镜像

镜像构建完成了,现在我们就需要将此处构建的镜像推送到镜像仓库中去,当然如果你有私有镜像仓库也可以,我们这里还没有自己搭建私有的仓库,所以直接使用 docker hub 即可。
我们需要提前注册一个 docker hub 的帐号,记住用户名和密码,我们这里需要使用。正常来说我们在本地推送 docker 镜像的时候,是不是需要使用docker login命令,然后输入用户名和密码,认证通过后,就可以使用docker push命令来推送本地的镜像到 docker hub 上面去了。

stage('Push') {    echo "4.Push Docker Image Stage"    sh "docker login -u zam2017 -p xxxxx"    sh "docker push zam2017/jenkins-demo:${build_tag}"}

在 Jenkins 的 Web UI 界面中来完成这个任务的话,我们这里的 Pipeline 是可以这样写的,但是我们是不是推荐使用 Jenkinsfile 的形式放入源码中进行版本管理,这样的话我们直接把 docker 仓库的用户名和密码暴露给别人这样很显然是非常非常不安全的,更何况我们这里使用的是 github 的公共代码仓库,所有人都可以直接看到我们的源码,所以我们应该用一种方式来隐藏用户名和密码这种私密信息,幸运的是 Jenkins 为我们提供了解决方法。

在首页点击 Credentials -> Stores scoped to Jenkins 下面的 Jenkins -> Global credentials (unrestricted) -> 左侧的 Add Credentials:添加一个 Username with password 类型的认证信息,如下:

 

输入 docker hub 的用户名和密码,ID 部分我们输入dockerHub,注意,这个值非常重要,在后面 Pipeline 的脚本中我们需要使用到这个 ID 值。

有了上面的 docker hub 的用户名和密码的认证信息,现在我们可以在 Pipeline 中使用这里的用户名和密码了:

stage('Push') {    echo "4.Push Docker Image Stage"    withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {        sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"        sh "docker push zam2017/jenkins-demo:${build_tag}"    }}

 

我们这里在 stage 中使用了一个新的函数withCredentials,其中有一个 credentialsId 值就是我们刚刚创建的 ID 值,而对应的用户名变量就是 ID 值加上 User,密码变量就是 ID 值加上 Password,然后我们就可以在脚本中直接使用这里两个变量值来直接替换掉之前的登录 docker hub 的用户名和密码,现在是不是就很安全了。

第五步,更改 YAML

上面我们已经完成了镜像的打包、推送的工作,接下来我们是不是应该更新 Kubernetes 系统中应用的镜像版本了,当然为了方便维护,我们都是用 YAML 文件的形式来编写应用部署规则,比如我们这里的 YAML 文件:(k8s.yaml)

apiVersion: extensions/v1beta1kind: Deploymentmetadata:  name: jenkins-demospec:  template:    metadata:      labels:        app: jenkins-demo    spec:      containers:      - image: zam2017/jenkins-demo:<BUILD_TAG>        imagePullPolicy: IfNotPresent        name: jenkins-demo

 

我们使用一个 Deployment 资源对象来管理 Pod,该 Pod 使用的就是我们上面推送的镜像,唯一不同的地方是 Docker 镜像的 tag 不是我们平常见的具体的 tag,而是一个 的标识,实际上如果我们将这个标识替换成上面的 Docker 镜像的 tag,是不是就是最终我们本次构建需要使用到的镜像?怎么替换呢?其实也很简单,我们使用一个sed命令就可以实现了:

stage('YAML') {    echo "5. Change YAML File Stage"    sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"}

第六步,部署

stage('Deploy') {    echo "6. Deploy Stage"    sh "kubectl apply -f k8s.yaml"}

到这里基本流程已经配置完毕了,可以构建测试一下。

 

增加,人工确认

理论上来说我们上面的6个步骤其实已经完成了,但是一般在我们的实际项目实践过程中,可能还需要一些人工干预的步骤,这是为什么呢?比如我们提交了一次代码,测试也通过了,镜像也打包上传了,但是这个版本并不一定就是要立刻上线到生产环境的,对吧,我们可能需要将该版本先发布到测试环境、QA 环境、或者预览环境之类的,总之直接就发布到线上环境去还是挺少见的,所以我们需要增加人工确认的环节,一般都是在 CD 的环节才需要人工干预,比如我们这里的最后两步,我们就可以在前面加上确认,比如:

stage('YAML') {    echo "5. Change YAML File Stage"    def userInput = input(        id: 'userInput',        message: 'Choose a deploy environment',        parameters: [            [                $class: 'ChoiceParameterDefinition',                choices: "Dev\nQA\nProd",                name: 'Env'            ]        ]    )    echo "This is a deploy step to ${userInput.Env}"    sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"}

我们这里使用了 input 关键字,里面使用一个 Choice 的列表来让用户进行选择,然后在我们选择了部署环境后,我们当然也可以针对不同的环境再做一些操作,比如可以给不同环境的 YAML 文件部署到不同的 namespace 下面去,增加不同的标签等等操作:

stage('Deploy') {    echo "6. Deploy Stage"    if (userInput.Env == "Dev") {      // deploy dev stuff    } else if (userInput.Env == "QA"){      // deploy qa stuff    } else {      // deploy prod stuff    }    sh "kubectl apply -f k8s.yaml"}

由于这一步也属于部署的范畴,所以我们可以将最后两步都合并成一步,我们最终的 Pipeline 脚本如下:

node('haimaxy-jnlp') {    stage('Clone') {        echo "1.Clone Stage"        git url: "http://gitlab.unmin.club:82/root/jenkins-demo.git"        script {            build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()        }    }    stage('Test') {      echo "2.Test Stage"    }    stage('Build') {        echo "3.Build Docker Image Stage"        sh "docker build -t zam2017/jenkins-demo:${build_tag} ."    }    stage('Push') {        echo "4.Push Docker Image Stage"        withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {            sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"            sh "docker push zam2017/jenkins-demo:${build_tag}"        }    }    stage('Deploy') {        echo "5. Deploy Stage"        def userInput = input(            id: 'userInput',            message: 'Choose a deploy environment',            parameters: [                [                    $class: 'ChoiceParameterDefinition',                    choices: "Dev\nQA\nProd",                    name: 'Env'                ]            ]        )        echo "This is a deploy step to ${userInput}"        sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"        if (userInput == "Dev") {            // deploy dev stuff        } else if (userInput == "QA"){            // deploy qa stuff        } else {            // deploy prod stuff        }        sh "kubectl apply -f k8s.yaml"    }}

现在我们在 Jenkins Web UI 中重新配置 jenkins-demo 这个任务,将上面的脚本粘贴到 Script 区域,重新保存,然后点击左侧的 Build Now,触发构建,然后过一会儿我们就可以看到 Stage View 界面出现了暂停的情况:

 

 

 

选择PROD,可以看到console控制台打印信息:

 


到服务器查看是否部署成功:

[root@master app]# kubectl  get podNAME                                      READY     STATUS             RESTARTS   AGEjenkins-demo-67446f58cd-mvpj8             0/1       CrashLoopBackOff   5          3mnfs-client-provisioner-6b688c9495-dc9gb   1/1       Running            6          10dnfs-web-0                                 1/1       Running            5          10dnfs-web-1                                 1/1       Running            2          10dnfs-web-2                                 1/1       Running            0          10dnginx-deployment1-6f98c47948-hqgf9        1/1       Running            0          3dstatic-web-master                         1/1       Running            11         99dtest-pod                                  0/1       Completed          0          10dweb-0                                     0/1       Pending            0          10d[root@master app]# kubectl   logs -f  jenkins-demo-67446f58cd-mvpj8Hello, Kubernetes!I'm from Jenkins CI/CD!

 

我们可以看到我们的应用已经正确的部署到了 Kubernetes 的集群环境中。

 

在实际的工作实践中,我们更多的是将 Pipeline 脚本写入到 Jenkinsfile 文件中,然后和代码一起提交到代码仓库中进行版本管理。现在我们将上面的 Pipeline 脚本拷贝到一个 Jenkinsfile 中,将该文件放入上面的 git 仓库中,但是要注意的是,现在既然我们已经在 git 仓库中了,是不是就不需要 git clone 这一步骤了,所以我们需要将第一步 Clone 操作中的 git clone 这一步去掉,可以参考:http://www.unmin.club:82/root/jenkins-demo

然后我们更改上面的 jenkins-demo 这个任务,点击 Configure -> 最下方的 Pipeline 区域 -> 将之前的 Pipeline Script 更改成 Pipeline Script from SCM,然后根据我们的实际情况填写上对应的仓库配置,要注意 Jenkinsfile 脚本路径:

 

 

构建测试:

 

 

上面我们使用了 Jenkins Pipeline 来自动化部署一个 Kubernetes 应用,在实际的项目中,往往一个代码仓库都会有很多分支的,比如开发、测试、线上这些分支都是分开的,一般情况下开发或者测试的分支我们希望提交代码后就直接进行 CI/CD 操作,而线上的话最好增加一个人工干预的步骤。Jenkins 也是支持对代码仓库有多分支的流程。

现在我们新建一个dev分支,依然使用Jenkinsfile 的方式配置。参考代码

node('haimaxy-jnlp') {    stage('Prepare') {        echo "1.Prepare Stage"        checkout scm        script {            build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()            if (env.BRANCH_NAME != 'master') {                build_tag = "${env.BRANCH_NAME}-${build_tag}"            }        }    }    stage('Test') {      echo "2.Test Stage"    }    stage('Build') {        echo "3.Build Docker Image Stage"        sh "docker build -t zam2017/jenkins-demo:${build_tag} ."    }    stage('Push') {        echo "4.Push Docker Image Stage"        withCredentials([usernamePassword(credentialsId: 'dockerHub', passwordVariable: 'dockerHubPassword', usernameVariable: 'dockerHubUser')]) {            sh "docker login -u ${dockerHubUser} -p ${dockerHubPassword}"            sh "docker push zam2017/jenkins-demo:${build_tag}"        }    }    stage('Deploy') {        echo "5. Deploy Stage"        if (env.BRANCH_NAME == 'master') {            input "确认要部署线上环境吗?"        }        sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"        sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"        sh "kubectl apply -f k8s.yaml --record"    }}

 

在第一步中我们增加了checkout scm命令,用来检出代码仓库中当前分支的代码,为了避免各个环境的镜像 tag 产生冲突,我们为非 master 分支的代码构建的镜像增加了一个分支的前缀,在第五步中如果是 master 分支的话我们才增加一个确认部署的流程,其他分支都自动部署,并且还需要替换 k8s.yaml 文件中的环境变量的值。更改完成后,提交 dev 分支到 github 仓库中。

 

5、Jenkins新皮肤:BlueOcean

我们这里使用 BlueOcean 这种方式来完成此处 CI/CD 的工作,BlueOcean 是 Jenkins 团队从用户体验角度出发,专为 Jenkins Pipeline 重新设计的一套 UI 界面,仍然兼容以前的 fressstyle 类型的 job,BlueOcean 具有以下的一些特性:

  • 连续交付(CD)Pipeline 的复杂可视化,允许快速直观的了解 Pipeline 的状态

  • 可以通过 Pipeline 编辑器直观的创建 Pipeline

  • 需要干预或者出现问题时快速定位,BlueOcean 显示了 Pipeline 需要注意的地方,便于异常处理和提高生产力

  • 用于分支和拉取请求的本地集成可以在 GitHub 或者 Bitbucket 中与其他人进行代码协作时最大限度提高开发人员的生产力。
    BlueOcean 可以安装在现有的 Jenkins 环境中,也可以使用 Docker 镜像的方式直接运行,我们这里直接在现有的 Jenkins 环境中安装 BlueOcean 插件:登录 Jenkins Web UI -> 点击左侧的 Manage Jenkins -> Manage Plugins -> Available -> 搜索查找 BlueOcean -> 点击下载安装并重启

 

 

点击开始创建一个新的 Pipeline:

 

 

Blue Ocean 会自动扫描仓库中的每个分支,会为根文件夹中包含Jenkinsfile的每个分支创建一个 Pipeline,比如我们这里有 master 和 dev 两个分支,并且两个分支下面都有 Jenkinsfile 文件,所以创建完成后会生成两个 Pipeline:

 

 

我们可以看到有两个任务在运行了,我们可以把 master 分支的任务停止掉,我们只运行 dev 分支即可,然后我们点击 dev 这个 pipeline 就可以进入本次构建的各步骤详细页面:

 

 

可以看到本次构建的 Docker 镜像的 Tag 为dev-d429515,是符合我们在jenkinsfile文件中的定义,非master分支是需要有分支前缀的。

 


查看dockerHub 仓库镜像是否已上传:

 

 

现在我们本地克隆代码,修改后提交到gitlab仓库,查看jenkins是否基于我们定义的非master分支代码自动触发构建。

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
[root@master app]# git clone http://gitlab.unmin.club:82/root/jenkins-demo.git正克隆到 'jenkins-demo'...remote: Counting objects: 110, done.remote: Compressing objects: 100% (59/59), done.remote: Total 110 (delta 65), reused 93 (delta 50)接收对象中: 100% (110/110), 9.72 KiB | 0 bytes/s, done.处理 delta 中: 100% (65/65), done.[root@master app]# cd jenkins-demo/[root@master jenkins-demo]# git status# 位于分支 master无文件要提交,干净的工作区[root@master jenkins-demo]# git checkout dev分支 dev 设置为跟踪来自 origin 的远程分支 dev。切换到一个新分支 'dev'[root@master jenkins-demo]# vim main.go[root@master jenkins-demo]# git add .[root@master jenkins-demo]# git commit -m " test "[dev aca25d0]  test 1 file changed, 1 insertion(+), 1 deletion(-)[root@master jenkins-demo]# git push origin devUsername for 'http://gitlab.unmin.club:82': rootPassword for 'http://root@gitlab.unmin.club:82':Counting objects: 5, done.Delta compression using up to 2 threads.Compressing objects: 100% (3/3), done.Writing objects: 100% (3/3), 275 bytes | 0 bytes/s, done.Total 3 (delta 2), reused 0 (delta 0)
remote:remote: To create a merge request for dev, visit:remote:   http://gitlab.unmin.club/root/jenkins-demo/merge_requests/new?merge_request%5Bsource_branch%5D=devremote:To http://gitlab.unmin.club:82/root/jenkins-demo.git   d429515..aca25d0  dev -> dev

 

可以看到jenkins已经触发构建,版本号也是我们gitlab的最新提交的版本号:

 

 

构建完成后,我们查看kubernetes是否为我们创建了响应的Pod:

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
[root@master jenkins-demo]# kubectl  get  podNAME                                      READY     STATUS             RESTARTS   AGEjenkins-demo-676769b5bf-q75dg             0/1       CrashLoopBackOff   285        23hnfs-client-provisioner-6b688c9495-dc9gb   1/1       Running            6          17dnfs-web-0                                 1/1       Running            5          17dnfs-web-1                                 1/1       Running            2          17dnfs-web-2                                 1/1       Running            0          17dstatic-web-master                         1/1       Running            11         106dtest-pod                                  0/1       Completed          0          17dweb-0                                     0/1       Pending            0          17d[root@master jenkins-demo]# kubectl logs  -f jenkins-demo-676769b5bf-q75dgHello, Kubernetes!I'm  dev    from Jenkins CI!BRANCH: dev

好的 ,如上所示dev的验证是符合我们的预期。现在我们把dev分支的代码合并到master,验证jenkins是否和我们定义的规则相同。

[root@master jenkins-demo]# git status# 位于分支 dev无文件要提交,干净的工作区[root@master jenkins-demo]# git checkout master切换到分支 'master'[root@master jenkins-demo]# git merge  dev自动合并 main.goMerge made by the 'recursive' strategy. main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)[root@master jenkins-demo]# git push origin masterUsername for 'http://gitlab.unmin.club:82': rootPassword for 'http://root@gitlab.unmin.club:82':Counting objects: 1, done.Writing objects: 100% (1/1), 217 bytes | 0 bytes/s, done.Total 1 (delta 0), reused 0 (delta 0)To http://gitlab.unmin.club:82/root/jenkins-demo.git   992361a..852f3da  master -> master

 

jenkins上master分支代码已经触发自动构建了,可以查看到此处推送的镜像 TAG 为852f3da ,没有分支的前缀,镜像推送完成后,进入 Deploy 阶段的时候我们可以看到出现了一个暂停的操作,让我们选择是否需要部署到线上,我们前面在Jenkinsfile定义的如果是 master 分支的话,在部署的阶段需要我们人工确认:

 

 

 

现在我们点击Proceed按钮发布到生产环境,查看服务器Pod是否为我们合并到master分支上的代码

[root@master jenkins-demo]# kubectl  get podNAME                                      READY     STATUS      RESTARTS   AGEjenkins-demo-6865cbf6bf-ckf2x             0/1       Completed   0          16snfs-client-provisioner-6b688c9495-dc9gb   1/1       Running     6          17dnfs-web-0                                 1/1       Running     5          17dnfs-web-1                                 1/1       Running     2          17dnfs-web-2                                 1/1       Running     0          17dstatic-web-master                         1/1       Running     11         106dtest-pod                                  0/1       Completed   0          17dweb-0                                     0/1       Pending     0          17d[root@master jenkins-demo]# kubectl  logs -f jenkins-demo-6865cbf6bf-ckf2xHello, Kubernetes!I'm   test dev    from Jenkins CI!BRANCH: master

可以看到打印出来的信息是 master,证明部署是没有问题的。

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

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

相关文章

TC 配置插件

转载&#xff1a;http://hi.baidu.com/accplaystation/item/07534686f39dc329100ef310 1、插件下载地址&#xff1a;http://www.topcoder.com/tc?moduleStatic&d1applet&d2plugins 一般用下面三个插件&#xff1a;CodeProcessor&#xff08;2.0&#xff09;&#xff0…

WebClient 访问间歇性返回403解决方案

说明&#xff1a;前段时间做的一个项目莫名的返回403的错误&#xff0c;这种情况也多大是程序员最不喜欢的了&#xff0c;没办法先来分析一下错误信息。之前的代码如下&#xff1a; WebClient webclient new WebClient();string u9Str webclient.DownloadString("http:/…

bootstrap select2 php,JS组件Bootstrap Select2使用方法详解

在介绍select组件的时候&#xff0c;之前分享过一篇JS组件中bootstrap multiselect两大组件较量的文章&#xff0c;这两个组件的功能确实很强大&#xff0c;本文分享下select组件的一些用法和特性。一些通用的单选、多选、分组等功能这里就不多做介绍了&#xff0c;multiselect…

文件删除小助手

亲&#xff0c;还在为删除多个文件而发愁吗&#xff1f;还在为删除不同目录下的N多文件而发愁吗&#xff1f;那你就下载这个吧。 功能以及优点&#xff1a; 1.支持删除某一类文件。 2.支持某一路径下所有文件的检索删除。 3.支持复杂文件名称匹配。 4.免安装&#xff0c;绿色中…

2020-11-29

分布式爬虫往往都需要多个环境&#xff0c;多个进程&#xff0c;如果手动去管理这些环境是很费事的&#xff0c;使用Docker集群就能很好地帮你解决这些问题&#xff0c;让你的爬虫管理变得简单。 主节点安装Docker 这里主节点建议大家使用Ubuntu&#xff0c;从节点随意。 Ubun…

八伟大的工具,Windows用户永远都不想错过

有许多的应用程序你可以找到&#xff0c;但是获得免费并且好用 的程序不是一件容易的事情&#xff0c;每当你需要这样的软件的时候&#xff0c;你就会网上搜啊搜&#xff0c;结果下载下来一运行达不到想要的效果&#xff0c;这就是今天我分享我收藏的8个工具原因&#xff0c;这…

Python3网络设备巡检(交换机篇)

介绍 只写了交换机的&#xff0c;路由器、防火墙、负载等其它设备以后&#xff0c;有需求再写&#xff0c;下面以一组交换机为例。 思路 1、收取交换机的信息 需要哪些信息&#xff0c;哪些命令&#xff0c;不同品牌交换机命令不同&#xff08;本篇以H3C为例&#xff09;&a…

Python 代码优化常见技巧

代码优化能够让程序运行更快&#xff0c;它是在不改变程序运行结果的情况下使得程序的运行效率更高&#xff0c;根据 80/20 原则&#xff0c;实现程序的重构、优化、扩展以及文档相关的事情通常需要消耗 80% 的工作量。优化通常包含两方面的内容&#xff1a;减小代码的体积&…

adb shell——Android虚拟机调试必须知道的命令

2019独角兽企业重金招聘Python工程师标准>>> 之前开发Android的时候喜欢用自己的手机来调试&#xff0c;特别是数据库&#xff01;&#xff01;&#xff01;因为是自己的手机嘛&#xff0c;可以安装一些类似数据库编辑器的工具&#xff0c;调试起来非常方便&#xf…

mysql6.10,MySQL经典50题-第6-10题

MySQL50-4-第6-10题本文中介绍的是第6-10题&#xff0c;涉及到的主要知识点&#xff1a;模糊匹配和通配符使用表的自连接in/not in连接查询的条件筛选image题目6题目需求查询“李”姓老师的数量分析过程使用通配符和like来解决SQL实现select count(t_name) from Teacher where …

增加系统右键菜单

本文介绍添加系统右键菜单&#xff0c;效果如下图&#xff1a; 原理很简单就是给注册表添加值&#xff0c;以下是添加系统右键菜单所要涉及的值。 KEY值&#xff1a;HKEY_CLASSES_ROOT\*\shell&#xff08;所有文件的右键菜单&#xff09;HKEY_CLASSES_ROOT\directory\shell&am…

Nginx-ingress部署及使用

目录 一 手动部署-官网版 1.1 获取资源1.2 安装RBAC1.3 安装基础资源1.4 安装ingress controllers1.5 创建ingress controllers service二 手动部署-github社区版(推荐&#xff09; 2.1 获取资源2.2 创建default backend2.3 确认验证三 ingress使用 3.1 创建demo环境3.2 创建i…

WinRAR(WinZip)压缩与解压实现(C#版Window平台)

本文的原理是借助Windows平台安装的WinRAR(WinZip)实现C#程序的调用&#xff08;注&#xff1a;WinRAR压缩解压WinZip同样适用&#xff09;。 先来看WinRAR&#xff08;WinZip&#xff09;自身的支持调用命令&#xff1a; 压缩命令&#xff1a;a {0} {1} -r 【{0}&#xff1a;…

Docker Consul 安装及使用服务发现

更多配置和原理&#xff1a;https://blog.csdn.net/liuzhuchen/article/details/81913562 从2016年起就开始接触Consul&#xff0c;使用的主要目的就是做服务发现&#xff0c;后来逐步应用于生产环境&#xff0c;并总结了少许使用经验。最开始使用Consul的人不多&#xff0c;为…

Visual Studio扩展工具添加与卸载

简介&#xff1a;vs 作为主流的开发工具之一&#xff0c;其强大的功能无可厚非&#xff0c;但日益增加的需求就使得vs的扩展工具成为优秀dev所必备的利器之一。 Visual Studio扩展工具添加 Visual Studio扩展工具的添加很简单&#xff0c;只需要选择需要的扩展工具然后一般安装…

php充值注入,PHP注入一路小跑

PHP注入一路小跑[ 2006-04-20 14:16:55 | 作者: 承諾 ]字体大小: 大 | 中 | 小很老的了&#xff0c;我是给我自己看的。忘了好多&#xff0c;补习一下‘ or ‘11‘/*‘%23‘ and password‘mypassid-1 union select 1,1,1id-1 union select char(97),char(97),char(97)id1 unio…

Visual Studio 扩展包(.vsix)制作

前言&#xff1a;上篇介绍了 Visual Studio扩展工具添加与卸载&#xff0c;本编要介绍的是Visual Studio 扩展包(.vsix)的制作。 方法&#xff1a; ①、下载并安装Visual Studio 2010 SDK。 vs 2010 开发工具下载SDK安装包官方下载地址&#xff1a;http://www.microsoft.com/d…

CDH6.2 Linux离线安装

1.概述 CDH&#xff0c;全称Clouderas Distribution, including Apache Hadoop。是Hadoop众多分支中对应中的一种&#xff0c;由Cloudera维护&#xff0c;基于稳定版本的Apache Hadoop构建&#xff0c;提供了Hadoop的核心&#xff08;可扩展存储、分布式计算&#xff09;&#…

vs怎么调试php程序,vscode如何调试运行c#程序

前提条件&#xff1a;安装.NET Core SDK安装vscode步骤&#xff1a;安装c#extension插件创建第一个项目&#xff0c;手动创建workspace文件夹在vscode中使用快捷键CTRLKCTRLO选择刚才创建的文件夹使用快捷键CTRLSHIFITY打开控制台使用下面的命令在打开的终端里面创建一个基础的…

Openshift 4.4 静态 IP 离线安装系列:准备离线资源

本系列文章描述了离线环境下以 UPI (User Provisioned Infrastructure) 模式安装 Openshift Container Platform (OCP) 4.4.5 的步骤&#xff0c;我的环境是 VMware ESXI 虚拟化&#xff0c;也适用于其他方式提供的虚拟机或物理主机。离线资源包括安装镜像、所有样例 Image Str…