进学阁

业精于勤荒于嬉,行成于思毁于随

0%

使用外置Jenkins更新集群内应用

为什么要使用外置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 基于拉模式构建交付流水线。此时,开发人员发布一个新功能的流程如下:

  1. 通过 pull request 向主分支提交包含新功能的代码。
  2. 代码审核通过后将被合并至主分支。
  3. 合并行为将触发 CI 系统进行构建和一系列的测试,并将新生成的镜像推送至镜像仓库。
  4. GitOps 检测到有新的镜像,会提取最新的镜像标记,然后同步到 Git 配置仓库的清单中。
  5. 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 '部署服务成功!'
}
}
}

}
}

效果展示