为什么要使用外置Jenkins 云上部署的项目少或者使用云环境的开发团队少,用原生的方式基本没问题。但随着你上云的项目越来越多和上云的团队越来越多,再使用这种方式一定会碰到这样一个问题: Jenkins的调度时间越来越长,流水线执行越来越慢。 我们甚至出现过执行一个流水线需要等上10分钟才开始调度的情况,极大影响了开发效率。而我们使用云平台还有一个原则, 就是要让云平台尽可能的轻量化。 基于上述2个原因我们最终选择了将流水线中的 CI部分 外置,通过外置的Jenkins来实现编译、构建、推送镜像功能,在流水线中只保留持续部署CD 的功能。
核心思想
如上图所示我们将流水线一分为二,拆成CI 和CD 两部分。CI 部分利用外部的Jenkins集群实现,在CI的最后一步通过命令修改了远程配置仓库的镜像版本,而后利用webhook触发钩子实现集群的自动部署。
当然这里在最后一步实际还是用的推模式,最后还是利用kubectl命令将应用部署到集群中。
GitOps GitOps在2017年被提出作为Kubernetes集群管理和应用交付的一种方式,利用Git作为 声明性基础设施和应用程序的单一真实源(single source of truth) 。在GitOps中,集群中运行的软件代理可以在运行状态和Git发生任何差异时发出告警,Kubernetes协调器会根据情况自动更新或回滚集群。Git作为交付流水线的中心,开发人员可以使用熟悉的工具发出pull request,从而加速和简化Kubernetes的应用部署和操作任务。
GitOps 基于拉模式构建交付流水线。此时,开发人员发布一个新功能的流程如下:
通过 pull request 向主分支提交包含新功能的代码。
代码审核通过后将被合并至主分支。
合并行为将触发 CI 系统进行构建和一系列的测试,并将新生成的镜像推送至镜像仓库。
GitOps 检测到有新的镜像,会提取最新的镜像标记,然后同步到 Git 配置仓库的清单中。
GitOps 检测到集群状态过期,会从配置仓库中拉取更新后的清单,并将包含新功能的镜像部署到集群里。
GitOps的核心思想是有一个 操作器 ,我们利用Git作为声明性基础设施,在git中声明我们期望的状态,比如副本数设置为3同时镜像版本为1000。操作器会将存储库中的期望状态与所部署基础架构的实际状态进行比较。每当注意到实际状态与存储库中的期望状态存在差异时,操作器便会更新部署的应用,将其副本数调整为3,同时将镜像版本调整为1000。
与拉模式相对应的就是推(push)模式了,比如前面文章中基于KubeSphere实现的流水线就是推模式。 这种模式一般都会在 CI 流水线运行完成后执行一个命令(比如 kubectl)将应用部署到目标环境中。
Git仓库分离 之前我们是将部署的配置文件 Deploy.yml 与代码仓库放置在一起,基于GitOps的思想我们会准备一个单独的配置仓库,用于存放部署文件。
CI部分使用的是代码仓库,CD部分拉取的是配置仓库。
将Jenkinsfile以及模块中的 Deploy.yml 全部迁移到配置仓库中。
修改Jenkinsfile 在git配置仓库创建文件Jenkinsfile,文件内容如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pipeline { agent { node { label 'maven' } } stages { stage('发布到测试环境') { agent none steps { container('maven') { sh 'kubectl version --short --client' withCredentials([kubeconfigFile(credentialsId: "$KUBECONFIG_CREDENTIAL_ID", variable: 'KUBECONFIG')]) { script { sh "ls" services_str = "koalas-payment" //将要发布的服务 def services = services_str.split(",") for (int i = 0; i < services.size(); ++i){ service_name = services[i].replaceAll("\"", "") echo "选择的服务:${service_name}" sh "envsubst < ${service_name}/deploy.yml | kubectl apply -f -" } } } } } } } environment { DOCKER_CREDENTIAL_ID = 'harbor' //平台中的harbor凭证ID KUBECONFIG_CREDENTIAL_ID = 'demo-kubeconfig' //kube凭证ID REGISTRY = "192.168.1.121:8098" //harbor地址 DOCKERHUB_NAMESPACE ="koalas-cloud" //工作空间 TAG = "develop-15" //标签 } }
然后将每个服务的Deploy.yml 独立存放,项目结构为
创建CD流水线 在KubeSphere中通过基于代码仓库生成流水线,这样在git配置仓库中修改镜像版本时可以通过webhook触发钩子运行CD流水线。
记住这里生成的webhook,等下需要用到。
安装Jenkins 基于Docker安装Jenkins 下载镜像 1 docker pull jenkins/jenkins
创建 jenkins 工作目录并赋予权限 1 2 mkdir -p /home/admin/docker_app/jenkins chmod 777 /home/admin/docker_app/jenkins
启动容器 1 2 3 4 5 docker run -d -p 8080:8080 -p 50000:50000 -u root -v /home/admin/docker_app/jenkins:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock -v $(which docker):/usr/bin/docker -v /home/admin/docker_app/maven/apache-maven-3.8.5:/usr/local/maven -v /etc/localtime:/etc/localtime --name jenkins121 jenkins/jenkins
-v /home/admin/docker_app/maven/apache-maven-3.8.5 这里是你本地的maven路径
启动成功后修改镜像加速 1 2 3 4 5 6 7 8 9 10 vim hudson.model.UpdateCenter.xml <?xml version='1.1' encoding='UTF-8'?> <sites> <site> <id>default</id> <url>https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/updatecenter.json</url> </site> </sites>
访问页面,并输入初始化的密码 1 2 vim secrets/initialAdminPassword
选择社区推荐的插件安装即可 版本升级 去开源社区下载 jenkins 稳定版本的 war 包
1 wget http://ftp-nyc.osuosl.org/pub/jenkins/warstable/2.277.3/jenkins.war /home/admin/docker_app/jenkins
进入容器内部,并替换容器内的 war 包
1 docker exec -it -u root jenkins121 bash cd /usr/share/jenkins cp jenkins.war jenkins.war.backup mv /var/jenkins_home/jenkins.war /usr/share/jenkins/jenkins.war
重启容器
1 docker restart jenkins121
配置Jenkins插件 安装默认插件
这里建议直接安装推荐的插件
安装Extended Choice Parameter 为什么要使用Extended Choice Parameter 使用参数化构建发布可以在构建的时候自由选择要更新哪些服务构建,方便我们精确控制项目中需要更新的服务。还可以在后面的其他配置项目里面使用,甚至可以用来当作Shell脚本的参数。通过设计更多的参数可以实现更加复杂的发布配置,大家可以根据自己的情况设计。
怎么使用 Extended Choice Parameter进入Jenkins——系统管理——插件管理——可选插件——搜索Extended Choice Parameter——选中——install
安装Git Parameter 为什么要使用Git Parameter 使用参数化构建发布可以在构建的时候自由选择要更新到哪个分支,方便准确的更新到版本分支,以及快速回滚
怎么使用Git Parameter 进入Jenkins——系统管理——插件管理——可选插件——搜索Git Parameter——选中——install
安装JDK Parameter 为什么要使用JDK Parameter
使用docker安装的Jenkins默认的jdk版本为 openjdk11,如果你本地环境为8或者其他版本的时候,就需要额外的增加jdk版本然后以参数的形式使用
怎么使用JDK Parameter 进入Jenkins——系统管理——插件管理——可选插件——搜索JDK Parameter——选中——install
配置凭证 首先选中用户名
选择凭证
然后选中全局凭证
添加凭证
类型说明
Secret text - API token之类的token (如GitHub个人访问token)
Username and password - 可以为独立的字段,也可以为冒号分隔的字符串:username:password(更多信息请参照 处理 credentials)
Secret file - 保存在文件中的加密内容
SSH Username with private key - SSH 公钥/私钥对
Certificate - a PKCS#12 证书文件 和可选密码
Docker Host Certificate Authentication credentials.
我们这里选择Username and password 然后将你的账户密码写进去,ID可以自己写,也可以自动生成
增加git仓库用户密码或者ssh 依照上面的步骤添加git仓库信息
增加harbor仓库密码 依照上面的步骤添加harbor仓库信息
添加流水线脚本 添加流水线
设置参数
丢弃旧构造
建议最大个数根据自己主机以及实际情况设置一下,个人建议设置为5
添加Extended Choice Parameter
根据你的需要填写 有下拉框 多选框 单选框
添加布尔值参数
用于是否重新构造
添加Git Parameter
类型可以选择多种类型,标签 分支 等
编写pipeline script脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 pipeline { agent any environment { PROJECT_NAME = "项目的命名空间" HARBOR_URL = "habor地址信息" HARBOR_ID = "jenkins的harbor凭证ID" PROJECT_HOME = "/var/jenkins_home/workspace/你的job名称" BRANCH_TAG = "${BRANCH_TAG}" } stages { //拉取代码 stage('拉取代码Gitea') { steps { echo 'Gitea 拉取代码 Checkout' script{ println("${BRANCH_TAG}") checkout([$class: 'GitSCM', branches: [[name: "${BRANCH_TAG}"]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '你的git代码凭证ID', url: "你的代码地址http地址"]]]) } } } // 代码编译 stage('Maven编译代码') { when { expression { return (params.IS_BUILD_AND_PACKAGE == true) } } steps { sh ''' /usr/local/maven/bin/mvn clean package -Dmaven.test.skip=true ''' } post { success { echo '编译代码成功!' } } } // 上传镜像 stage('打包镜像并上传到Harbor仓库'){ when { expression { return (params.IS_BUILD_AND_PACKAGE == true) } } steps { script { def chooses = "${SERVICES}" echo "选择的服务:${SERVICES}" } withCredentials([usernamePassword(credentialsId: "${HARBOR_ID}", passwordVariable: 'password', usernameVariable: 'username')]) { //登录 Harbor sh "docker login -u ${username} -p ${password} ${HARBOR_URL}" } script { services_str = "${SERVICES}" def services = services_str.split(",") for (int i = 0; i < services.size(); ++i){ service_name = services[i].replaceAll("\"", "") echo "选择的服务:${service_name}" // 制作镜像 if("${service_name}".contains("koalas-gateway") || "${service_name}".contains("koalas-auth")){ //基础镜像构建 sh " docker build -t ${service_name}:${BRANCH_TAG}-$BUILD_NUMBER ${PROJECT_HOME}/${service_name}/ " }else{ sh " docker build -t ${service_name}:${BRANCH_TAG}-$BUILD_NUMBER ${PROJECT_HOME}/koalas-modules/${service_name}/ " } // 镜像标签 sh " docker tag ${service_name}:${BRANCH_TAG}-$BUILD_NUMBER ${HARBOR_URL}/${PROJECT_NAME}/${service_name}:${BRANCH_TAG}-$BUILD_NUMBER " // // 上传镜像 sh " docker push ${HARBOR_URL}/${PROJECT_NAME}/${service_name}:${BRANCH_TAG}-$BUILD_NUMBER " } //删除悬虚镜像 sh "docker image prune -a -f" // 登出私有仓库 sh "docker logout ${HARBOR_URL}" } } post { success { echo '镜像打包并上传成功!' } } } // 执行目标服务器脚本 stage('部署服务'){ steps { dir(path: "./project"){ echo 'Gitea 拉取代码 ' //拉取代码 git url: "你的脚本代码仓库地址", credentialsId: "脚本代码仓库git凭证ID", branch: "master" withCredentials([usernamePassword(credentialsId:"脚本代码仓库git凭证ID", usernameVariable: "GIT_USERNAME", passwordVariable: "GIT_PASSWORD")]){ sh """ ls git config --local --replace-all credential.helper "!p() { echo username=\\$GIT_USERNAME; echo password=\\$GIT_PASSWORD; }; p" git config --global user.email "fengbb@neusensetech.com" git config --global user.name "fengbb" git branch -a git pull -p --rebase origin master git add * sed -i 's/services_str =.*/services_str = "${SERVICES}"/g' Jenkinsfile sed -i 's/REGISTRY =.*/REGISTRY = "${HARBOR_URL}"/g' Jenkinsfile sed -i 's/DOCKERHUB_NAMESPACE =.*/DOCKERHUB_NAMESPACE ="${PROJECT_NAME}"/g' Jenkinsfile sed -i 's/TAG =.*/TAG = "${BRANCH_TAG}-$BUILD_NUMBER"/g' Jenkinsfile cat Jenkinsfile git commit -am "update tag: ${BRANCH_TAG}-$BUILD_NUMBER" && git push -u origin master curl 上文中cd最后的webhook地址 """ } } } post { success { echo '部署服务成功!' } } } } }
效果展示