现在这家单位的CICD比较的混乱,然后突发奇想,想改造下,于是就用pipeline做了一个简单的流水线,下面是关于它的一些介绍

写一个简单的流水线



大概就是这么个流程简单来说就是:拉代码---》编译---》打镜像---》推镜像---》部署到k8s中,下面的pipeline就是在这条主线上进行,根据情况进行增加

  1. pipeline {
  2. agent { label 'pdc&&jdk8' }
  3. environment {
  4. git_addr = "代码仓库地址"
  5. git_auth = "拉代码时的认证ID"
  6. pom_dir = "pom文件的目录位置(相对路径)"
  7. server_name = "服务名"
  8. namespace_name = "服务所在的命名空间"
  9. img_domain = "镜像地址"
  10. img_addr = "${img_domain}/cloudt-safe/${server_name}"
  11. // cluster_name = "集群名"
  12. }
  13. stages {
  14. stage('Clear dir') {
  15. steps {
  16. deleteDir()
  17. }
  18. }
  19. stage('Pull server code and ops code') {
  20. parallel {
  21. stage('Pull server code') {
  22. steps {
  23. script {
  24. checkout(
  25. [
  26. $class: 'GitSCM',
  27. branches: [[name: '${Branch}']],
  28. userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]
  29. ]
  30. )
  31. }
  32. }
  33. }
  34. stage('Pull ops code') {
  35. steps {
  36. script {
  37. checkout(
  38. [
  39. $class: 'GitSCM',
  40. branches: [[name: 'pipeline-0.0.1']], //拉取的构建脚本的分支
  41. doGenerateSubmoduleConfigurations: false,
  42. extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'DEPLOYJAVA']], //DEPLOYJAVA: 把代码存放到此目录中
  43. userRemoteConfigs: [[credentialsId: 'chenf-o', url: '构建脚本的仓库地址']]
  44. ]
  45. )
  46. }
  47. }
  48. }
  49. }
  50. }
  51. stage('Set Env') {
  52. steps {
  53. script {
  54. date_time = sh(script: "date +%Y%m%d%H%M", returnStdout: true).trim()
  55. git_cm_id = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
  56. whole_img_addr = "${img_addr}:${date_time}_${git_cm_id}"
  57. }
  58. }
  59. }
  60. stage('Complie Code') {
  61. steps {
  62. script {
  63. withMaven(maven: 'maven_latest_linux') {
  64. sh "mvn -U package -am -amd -P${env_name} -pl ${pom_dir}"
  65. }
  66. }
  67. }
  68. }
  69. stage('Build image') {
  70. steps {
  71. script {
  72. dir("${env.WORKSPACE}/${pom_dir}") {
  73. sh """
  74. echo 'FROM 基础镜像地址' > Dockerfile //由于我这里进行了镜像的优化,只指定一个基础镜像地址即可,后面会详细的说明
  75. """
  76. withCredentials([usernamePassword(credentialsId: 'faabc5e8-9587-4679-8c7e-54713ab5cd51', passwordVariable: 'img_pwd', usernameVariable: 'img_user')]) {
  77. sh """
  78. docker login -u ${img_user} -p ${img_pwd} ${img_domain}
  79. docker build -t ${img_addr}:${date_time}_${git_cm_id} .
  80. docker push ${whole_img_addr}
  81. """
  82. }
  83. }
  84. }
  85. }
  86. }
  87. stage('Deploy img to K8S') {
  88. steps {
  89. script {
  90. dir('DEPLOYJAVA/deploy') {
  91. //执行构建脚本
  92. sh """
  93. /usr/local/python3/bin/python3 deploy.py -n ${server_name} -s ${namespace_name} -i ${whole_img_addr} -c ${cluster_name}
  94. """
  95. }
  96. }
  97. }
  98. // 做了下判断如果上面脚本执行失败,会把上面阶段打的镜像删除掉
  99. post {
  100. failure {
  101. sh "docker rmi -f ${whole_img_addr}"
  102. }
  103. }
  104. }
  105. stage('Clear somethings') {
  106. steps {
  107. script {
  108. // 删除打的镜像
  109. sh "docker rmi -f ${whole_img_addr}"
  110. }
  111. }
  112. post {
  113. success {
  114. // 如果上面阶段执行成功,将把当前目录删掉
  115. deleteDir()
  116. }
  117. }
  118. }
  119. }
  120. }

优化构建镜像

上面的pipeline中有一条命令是生成Dockerfile的,在这里做了很多优化,虽然我的Dockerfile就写了一个FROM,但是在这之后又会执行一系列的操作,下面我们对比下没有做优化的Dockerfile

未优化

  1. FROM 基础镜像地址
  2. RUN mkdir xxxxx
  3. COPY *.jar /usr/app/app.jar
  4. ENTRYPOINT java -jar app.jar

优化后的

  1. FROM 基础镜像地址

优化后的Dockerfile就这一行就完了。。。。。 下面简单介绍下这个ONBUILD

ONBUILD可以这样理解,就比如我们这里使用的镜像,是基于java语言做的一个镜像,这个镜像有两部分,一个是包含JDK的基础镜像A,另一个是包含jar包的镜像B,关系是先有A再有B,也就是说B依赖于A。

假设一个完整的基于Java的CICD场景,我们需要拉代码,编译,打镜像,推镜像,更新pod这一系列的步骤,而在打镜像这个过程中,我们需要把编译后的产物jar包COPY到基础镜像中,这就造成了,我们还得写一个Dockerfile,用来COPY jar包,就像下面这个样子:

  1. FROM jdk基础镜像
  2. COPY xxx.jar /usr/bin/app.jar
  3. ENTRYPOINT java -jar app.jar

这样看起来也还好,基本上三行就解决了,但是能用一行就解决为什么要用三行呢?

  1. FROM jdk基础镜像
  2. ONBUILD COPY target/*.jar /usr/bin/app.jar
  3. CMD ["/start.sh"]

打成一个镜像,比如镜像名是:java-service:jdk1.8,在打镜像的时候,ONBUILD后面的在本地打镜像的过程中不会执行,而是在下次引用时执行的

  1. FROM java-service:jdk1.8

只需要这一行就可以了,并且这样看起来更加简洁,pipeline看起来也很规范,这样的话,我们每一个java的服务都可以使用这一行Dockerfile了。

使用凭据

有时候使用docker进行push镜像时需要进行认证,如果我们直接在pipeline里写的话不太安全,所以得进行脱敏,这样的话我们就需要用到凭据了,添加凭据也是非常简单,由于我们只是保存我们的用户名和密码,所以用Username with password类型的凭据就可以了,如下所示

比如说:拉取git仓库的代码需要用到,然后这里就添加一个凭据,对应与pipeline里的下面这段内容:

  1. stage('Pull server code') {
  2. steps {
  3. script {
  4. checkout(
  5. [
  6. $class: 'GitSCM',
  7. branches: [[name: '${Branch}']],
  8. userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]
  9. ]
  10. )
  11. }
  12. }
  13. }

这里的变量${git_auth}就是添加凭据时设置的ID,如果不设置ID会随机生成一个ID

然后docker push时会需要进行认证,也需要添加凭据,添加方式和上面是一样的,不过我们可以用pipeline的语法来生成一个,方式如下:

点击Pipeline Syntax

选择withCredentials: Bind credentials to variables

然后和之前添加的凭据进行绑定,这里选择类型为:Username and password (separated)

设置用户名和密码的变量名,然后选择刚才添加好的凭据

点击生成即可,就是上面pipeline里的下面这段:

  1. withCredentials([usernamePassword(credentialsId: 'faabc5e8-9587-4679-8c7e-54713ab5cd51', passwordVariable: 'img_pwd', usernameVariable: 'img_user')]) {
  2. sh """
  3. docker login -u ${img_user} -p ${img_pwd} ${img_domain}
  4. docker build -t ${img_addr}:${date_time}_${git_cm_id} .
  5. docker push ${whole_img_addr}
  6. """
  7. }

credentialsId: 这个ID就是随机生成的ID

执行脚本进行更新镜像

这里是使用python写了一个小脚本,来调用kubernetes的接口做了一个patch的操作完成的。先来看下此脚本的目录结构



核心代码:deploy.py

核心文件:config.yaml 存放的是kubeconfig文件,用于和kubernetes的认证

下面贴一下deploy.py的脚本内容,可以参考下:

  1. import os
  2. import argparse
  3. from kubernetes import client, config
  4. class deployServer:
  5. def __init__(self, kubeconfig):
  6. self.kubeconfig = kubeconfig
  7. config.kube_config.load_kube_config(config_file=self.kubeconfig)
  8. self._AppsV1Api = client.AppsV1Api()
  9. self._CoreV1Api = client.CoreV1Api()
  10. self._ExtensionsV1beta1Api = client.ExtensionsV1beta1Api()
  11. def deploy_deploy(self, deploy_namespace, deploy_name, deploy_img=None, deploy_which=1):
  12. try:
  13. old_deploy = self._AppsV1Api.read_namespaced_deployment(
  14. name=deploy_name,
  15. namespace=deploy_namespace,
  16. )
  17. old_deploy_container = old_deploy.spec.template.spec.containers
  18. pod_num = len(old_deploy_container)
  19. if deploy_which == 1:
  20. pod_name = old_deploy_container[0].name
  21. old_img = old_deploy_container[0].image
  22. print("获取上一个版本的信息\n")
  23. print("当前Deployment有 {} 个pod, 为: {}\n".format(pod_num, pod_name))
  24. print("上一个版本的镜像地址为: {}\n".format(old_img))
  25. print("此次构建的镜像地址为: {}\n".format(deploy_img))
  26. print("正在替换当前服务的镜像地址....\n")
  27. old_deploy_container[deploy_which - 1].image = deploy_img
  28. else:
  29. print("只支持替换一个镜像地址")
  30. exit(-1)
  31. new_deploy = self._AppsV1Api.patch_namespaced_deployment(
  32. name=deploy_name,
  33. namespace=deploy_namespace,
  34. body=old_deploy
  35. )
  36. print("镜像地址已经替换完成\n")
  37. return new_deploy
  38. except Exception as e:
  39. print(e)
  40. def run():
  41. parser = argparse.ArgumentParser()
  42. parser.add_argument('-n', '--name', help="构建的服务名")
  43. parser.add_argument('-s', '--namespace', help="要构建的服务所处在的命名空间")
  44. parser.add_argument('-i', '--img', help="此次构建的镜像地址")
  45. parser.add_argument('-c', '--cluster',
  46. help="rancher中当前服务所处的集群名称")
  47. args = parser.parse_args()
  48. if not os.path.exists('../config/' + args.cluster):
  49. print("当前集群名未设置或名称不正确: {}".format(args.cluster), 'red')
  50. exit(-1)
  51. else:
  52. kubeconfig_file = '../config/' + args.cluster + '/' + 'config.yaml'
  53. if os.path.exists(kubeconfig_file):
  54. cli = deployServer(kubeconfig_file)
  55. cli.deploy_deploy(
  56. deploy_namespace=args.namespace,
  57. deploy_name=args.name,
  58. deploy_img=args.img
  59. )
  60. else:
  61. print("当前集群的kubeconfig不存在,请进行配置,位置为{}下的config.yaml.(注意: config.yaml名称写死,不需要改到)".format(args.cluster),
  62. 'red')
  63. exit(-1)
  64. if __name__ == '__main__':
  65. run()

写的比较简单,没有难懂的地方,关键的地方是:

  1. new_deploy = self._AppsV1Api.patch_namespaced_deployment(
  2. name=deploy_name,
  3. namespace=deploy_namespace,
  4. body=old_deploy
  5. )

这一句是执行的patch操作,把替换好新的镜像地址的内容进行patch。

然后就是执行就可以了。

其他

这里有一个需要注意的地方是pipeline里加了一个异常捕获,如下所示:

  1. post {
  2. success {
  3. // 如果上面阶段执行成功,将把当前目录删掉
  4. deleteDir()
  5. }
  6. }

生命式的pipeline和脚本式的pipeline的异常捕获的写法是有区别的,声明式写法是用的post来进行判断,比较简单,可以参考下官方文档

另外还有一个地方使用了并行执行,同时拉了服务的代码,和构建脚本的代码,这样可以提高执行整个流水线的速度,如下所示:

  1. parallel {
  2. stage('Pull server code') {
  3. steps {
  4. script {
  5. checkout(
  6. [
  7. $class: 'GitSCM',
  8. branches: [[name: '${Branch}']],
  9. userRemoteConfigs: [[credentialsId: "${git_auth}", url: "${git_addr}"]]
  10. ]
  11. )
  12. }
  13. }
  14. }
  15. stage('Pull ops code') {
  16. steps {
  17. script {
  18. checkout(
  19. [
  20. $class: 'GitSCM',
  21. branches: [[name: 'pipeline-0.0.1']], //拉取的构建脚本的分支
  22. doGenerateSubmoduleConfigurations: false,
  23. extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'DEPLOYJAVA']], //DEPLOYJAVA: 把代码存放到此目录中
  24. userRemoteConfigs: [[credentialsId: 'chenf-o', url: '构建脚本的仓库地址']]
  25. ]
  26. )
  27. }
  28. }
  29. }
  30. }

嗯,情况就是这么个情况,一个简简单单的流水线就完成了,如果想快速使用流水线完成CICD,可以参考下这篇文章。

欢迎各位朋友关注我的公众号,来一起学习进步哦

结合k8s和pipeline的流水线,并通过k8s接口完成镜像升级的更多相关文章

  1. Jenkins + Pipeline 构建流水线发布

      Jenkins + Pipeline 构建流水线发布 利用Jenkins的Pipeline配置发布流水线 参考: https://jenkins.io/doc/pipeline/tour/depl ...

  2. Beam概念学习系列之Pipeline 数据处理流水线

    不多说,直接上干货! Pipeline 数据处理流水线 Pipeline将Source PCollection ParDo.Sink组织在一起形成了一个完整的数据处理的过程. Beam概念学习系列之P ...

  3. 【K8S学习笔记】Part2:获取K8S集群中运行的所有容器镜像

    本文将介绍如何使用kubectl列举K8S集群中运行的Pod内的容器镜像. 注意:本文针对K8S的版本号为v1.9,其他版本可能会有少许不同. 0x00 准备工作 需要有一个K8S集群,并且配置好了k ...

  4. 【K8S】基于单Master节点安装K8S集群

    写在前面 最近在研究K8S,今天就输出部分研究成果吧,后续也会持续更新. 集群规划 IP 主机名 节点 操作系统版本 192.168.175.101 binghe101 Master CentOS 8 ...

  5. Blazor+Dapr+K8s微服务之基于WSL安装K8s集群并部署微服务

         前面文章已经演示过,将我们的示例微服务程序DaprTest1部署到k8s上并运行.当时用的k8s是Docker for desktop 自带的k8s,只要在Docker for deskto ...

  6. K8S 使用Kubeadm搭建高可用Kubernetes(K8S)集群 - 证书有效期100年

    1.概述 Kubenetes集群的控制平面节点(即Master节点)由数据库服务(Etcd)+其他组件服务(Apiserver.Controller-manager.Scheduler...)组成. ...

  7. jenkins的Pipeline代码流水线管理

    1.新建一个pipline任务 2.自写一个简单的pipline脚本 a.Pipeline的脚本语法在Pipeline Syntax中,片段生成器,示例步骤中选择builf:Build a job b ...

  8. jenkisn Pipeline的流水线发布,自动化部署

    创建一个流水线job,这只是个简单的流水线发布教程,写的不好~

  9. [k8s]kubeadm k8s免费实验平台labs.play-with-k8s.com,k8s在线测试

    k8s实验 labs.play-with-k8s.com特色 这玩意允许你用github或dockerhub去登录 这玩意登录后倒计时,给你4h实践 这玩意用kubeadm来部署(让你用weave网络 ...

随机推荐

  1. 前端监控SDK开发分享

    目录 前言 收集哪些数据 性能 错误 辅助信息 小结 客户端SDK(探针)相关原理和API Web 微信小程序 编写测试用例 单元测试 流程测试 提供Web环境的方式 Mock Web API的方式 ...

  2. django学习-4.url动态传值

    1.前言 我们在浏览器访问一个网页A是通过一个指定的url地址去访问的.但在浏览器用一个不存在的url地址去执行访问是打不开正确的网页的,只会打开一个浏览器自带的有错误提示的网页. 在django框架 ...

  3. Python3网络爬虫-- 使用代理,轮换使用各种IP访问

    # proxy_list 代理列表 run_times = 100000 for i in range(run_times): for item in proxy_list: proxies = { ...

  4. python常用内置方法index\extend\count\reverse\sort

    定义列表:(有2个值相同) a = ['XiaoBao','aiaoHao','biaoLiao','ciaoQing','eiaoLi','QiBao','biaoLiao'] 列表的索引: fir ...

  5. WPF -- 构建动画

    写在前面:本文代码摘自<Head First C#> 本文使用ObjectAnimationUsingKeyFrames + Storyboard构建一个动画. ObjectAnimati ...

  6. nginx+php-fpm docker镜像合二为一

    一.概述 在上一篇文章介绍了nginx+php-fpm,链接如下: https://www.cnblogs.com/xiao987334176/p/12918413.html nginx和php-fp ...

  7. 关于 JMeter 5.4.1 的一点记录

    APACHE JMeter table { border: 0; border-collapse: collapse; background-color: rgba(255, 245, 218, 1) ...

  8. 基于Hi3559AV100的SVP(NNIE)开发整体流程

    在之后的hi3559AV100板载开发中,除了走通V4L2->VDEC->VPSS->VO(HDMI)输出,还有需要进行神经网络的开发学习,进行如face detection的开发等 ...

  9. JS(ES6)、Vue.js、node.js

    JS行为(ESMAScript, JSdom, bom)$.ajax() <- (xmlhttpRequest由这个封装来的)  -> axios(vue版)  =  ajax技术jque ...

  10. 使用伪类(::before/::after)设置图标

    使用伪类(::before/::after)设置文本前后图标.减少标签的浪费,使页面更加整洁. 如图: <!DOCTYPE html> <html> <head> ...