目录
一、实验
1.环境
(1)主机
表1 主机
主机 | 架构 | 版本 | IP | 备注 |
master1 | K8S master节点 | 1.20.6 | 192.168.204.180 | jenkins slave (从节点) |
node1 | K8S node节点 | 1.20.6 | 192.168.204.181 | |
node2 | K8S node节点 | 1.20.6 | 192.168.204.182 | |
jenkins | jenkins主节点 | 2.414.2 | 192.168.204.15:8080 | gitlab runner (从节点) |
harbor私有仓库 | 1.2.2 | 192.168.204.15 | ||
gitlab | gitlab 主节点 | 12.10.14 | 192.168.204.8:82 | jenkins slave (从节点) |
sonarqube | 9.6 | 192.168.204.8:9000 |
2.GitLab 更新deployment文件
(1)项目新建目录,用于存放上传的deployment 替换文件
(2)准备更新模板文件deployment.yaml
__APPNAME__(应用名称)对应Jenkins作业名称
__NAMESPACE__ (名称空间) 对应业务名称
__INAGENAME__(镜像名称)
(3)更新完成
(4)项目目录如下:
3.GitLab更新共享库前端项目CI与CD流水线
(1)查看项目架构
(2)更新K8S CI流水线 (k8sci.jenkinsfile)
@Library("mylib@master") _
import org.devops.*
def checkout = new Checkout()
def build = new Build()
def unittest = new UnitTest()
def sonar = new Sonar()
def gitlabutil = new Gitlab()
pipeline {
agent { label "build"}
options {
skipDefaultCheckout true
}
stages{
stage("Checkout"){
steps{
script {
println("GetCode")
checkout.GetCode("${env.srcUrl}","${env.branchName}")
}
}
}
stage("build"){
steps{
script{
println("Build")
build.CodeBuild("${env.buildTool}")
}
}
}
stage("UnitTest"){
steps{
script{
println("Test")
unittest.CodeTest("${env.buildTool}")
}
}
}
stage("SonarScan"){
steps {
script {
groupName = "${JOB_NAME}".split("/")[0]
projectName ="${JOB_NAME}".split("/")[-1].split("_")[0]
sonar.CodeSonar("${env.buildTool}",projectName,groupName)
}
}
}
stage("PushImage"){
steps {
script {
repoName = "${JOB_NAME}".split("/")[0]
projectName ="${JOB_NAME}".split("/")[-1].split("_")[0]
env.registry = "192.168.204.15"
env.imageName = "${env.registry}/${repoName}/${projectName}:${env.branchName}"
withCredentials([usernamePassword(credentialsId: '8c662308-4991-4576-9826-74a5417de685', passwordVariable: 'DOCKER_PASSWD', usernameVariable: 'DOCKER_USER')]) {
sh """
#重写HTML首页
echo "${env.imageName}" > dist/index.html
#构建镜像
docker build -t ${env.imageName} .
#登录镜像仓库
docker login -u ${DOCKER_USER} -p ${DOCKER_PASSWD} ${env.registry}
#上传镜像
docker push ${env.imageName}
#删除镜像
sleep 2
docker rmi ${env.imageName}
"""
}
}
}
}
stage("ReleaseFile"){
steps{
script{
// 获取模板文件
fileData = gitlabutil.GetRepoFile(22,"deployment.yaml", "master")
sh "rm -fr deployment.yaml"
writeFile file: 'deployment.yaml', text: fileData
// 替换模板文件内容
namespace = "${JOB_NAME}".split("/")[0]
appName ="${JOB_NAME}".split("/")[-1].split("_")[0]
sh """
sed -i 's#__PORT__#${env.port}#g' deployment.yaml
sed -i 's#__APPNAME__#${appName}#g' deployment.yaml
sed -i 's#__NAMESPACE__#${namespace}#g' deployment.yaml
sed -i 's#__IMAGENAME__#${env.imageName}#g' deployment.yaml
"""
// 上传替换后的版本文件(新建文件或者更新文件)
newYaml = sh returnStdout: true, script: 'cat deployment.yaml'
println(newYaml)
//更新gitlab文件内容
base64Content = newYaml.bytes.encodeBase64().toString()
// 会有并行问题,同时更新报错
try {
gitlabutil.UpdateRepoFile(22,"${appName}%2f${env.branchName}.yaml",base64Content, "master")
} catch(e){
gitlabutil.CreateRepoFile(22,"${appName}%2f${env.branchName}.yaml",base64Content, "master")
}
}
}
}
}
}
(3)更新K8S CD流水线 (k8scd.jenkinsfile)
@Library("mylib@master") _
import org.devops.*
def gitlabbutil = new Gitlab()
env.groupName = "${JOB_NAME}".split("/")[0]
env.projectName ="${JOB_NAME}".split("/")[-1].split("_")[0]
pipeline {
agent { label "k8s"}
options {
skipDefaultCheckout true
}
stages{
stage("GetDeployFile"){
steps{
script {
env.appName = "${env.projectName}"
env.deployFile = "${env.appName}/${env.branchName}.yaml"
//println("GetCode")
fileData = gitlabbutil.GetRepoFile(22,"${env.appName}%2f${env.branchName}.yaml", "master")
//println(fileData)
sh "rm -fr ${env.deployFile}"
writeFile file: "${env.deployFile}", text: fileData
//sh "ls -l; cat deployment.yaml"
sh "ls -l "
}
}
}
stage("DeployAPP"){
steps{
script{
env.namespace = "${env.groupName}"
sh """
## 发布应用
kubectl apply -f ${env.deployFile} -n ${env.namespace}
"""
// 获取应用状态
5.times{
sh "sleep 2; kubectl -n ${env.namespace} get pod | grep ${env.appName} "
}
}
}
}
stage("RollOut"){
input {
message "是否进行回滚"
ok "提交"
submitter "david,aa"
parameters {
choice(choices: ['yes','no'], name: 'opts')
}
}
steps{
script{
switch ("${opts}"){
case "yes":
sh " kubectl rollout undo deployment/${env.appName} -n ${env.namespace}"
break
case "no":
break
}
}
}
}
}
}
4.K8S查看前端项目版本
(1)外部测试访问(当前版本为1.1.7)
# curl http://devops03-devops-ui.devops.com:31291
(2) 另开一个终端用watch命令观察pod变化
# watch -n 1 "kubectl get pod -n devops03"
5.Jenkins 构建前端项目
(1)Jenkins给前端项目CI流水线添加参数添加字符参数port
(2)Jenkins给前端项目CD流水线添加参数添加字符参数branchName
(3) 构建前端项目CI流水线
(4)成功
(5)GitLab查看deployment部署文件已自动上传(RELEASE-1.1.5.yaml)
(6) 构建前端项目CD流水线
(7) 观察pod变化
(8)外部测试访问(当前版本为1.1.5)
# curl http://devops03-devops-ui.devops.com:31291
(9)不进行回滚
(10)完成
6.Jenkins 再次构建前端项目
(1) 构建前端项目CI流水线
(2)成功
(3)GitLab查看deployment部署文件已自动上传(RELEASE-1.1.6.yaml)
(4) 构建前端项目CD流水线
(5) 观察pod变化
(6)外部测试访问(当前版本为1.1.6)
# curl http://devops03-devops-ui.devops.com:31291
(7)不进行回滚
(8)完成
二、问题
1. Jenkins 构建CI 流水线报错
(1)报错
(2)原因分析
函数名错误
(3)解决方法
修改函数名。
修改前:
修改后:
2. Jenkins 构建CI 流水线弹出脚本报错
(1)报错
(2)原因分析
script不允许使用静态方法
(3)解决方法
运行script使用静态方法
根据弹出提示页面,点击进入。
点击Approve
完成
重写构建项目成功
3. Jenkins 构建CD 流水线报错
(1) 报错
(2)原因分析
yaml文件格式错误
(3)解决方法
修改deploymeny模板文件
修改前:
修改后:
成功:
4.URL中特殊字符实现哪些功能
(1)URL特殊字符
有些符号在URL中是不能直接传递的,如果要在URL中传递这些特殊符号,那么就要使用他们的编码了。
编码的格式为:%加字符的ASCII码,即一个百分号%,后面跟对应字符的ASCII(16进制)码值。例如 空格的编码值是"%20"。
如果不使用转义字符,这些编码就会当URL中定义的特殊字符处理。
(2)URL特殊符号及编码 十六进制值
1) + URL 中+号表示空格 %2B
2) 空格 URL中的空格可以用+号或者编码 %20
3) / 分隔目录和子目录 %2F
4) ? 分隔实际的 URL 和参数 %3F
5) % 指定特殊字符 %25
6) # 表示书签 %23
7) & URL 中指定的参数间的分隔符 %26
8) = URL 中指定参数的值 %3D
5.sed如何实现替换特殊字符
(1)普通操作可以使用冒号(:)井号(#)正斜杠(/)来作为分隔符
sed -i 's#abc#def#g' geng.file ---将文件geng中的abc替换成def
cat geng.file | sed 's/abc/def/g' ---打印文件geng,并将其中的abc替换成def
(2)对于变量做替换
sed 若是单引号括起来的,变量上得再额外加个单引号才能引用生效;
若是双引号括起来的,可直接引用生效。
1)举例
pa='127.0.0.1/32'; field='ip_allow=123'; \
echo $field | sed 's#^ip_allow=.*#ip_allow=${pa}#g'
结果:ip_allow=${pa} --变量替换未生效
2)更改
echo $field | sed 's#^ip_allow=.*#ip_allow='${pa}'#g'
结果:ip_allow=127.0.0.1/32
3)更改
echo $field | sed "s#^ip_allow=.*#ip_allow=${pa}#g"
结果:ip_allow=127.0.0.1/32
(3) 特殊字符替换,反斜杠、正斜杠、双引号、$符
单个转义:多加个反斜杠做转义即可:反斜杠(\\)、正斜杠(\/)、双引号(\")
单转多个:参考如下列表
表2 特殊字符转换
实现目标 | 方法 | 能否用单引号还是双引号括起来 | ||
单引号 | 双引号 | 为什么 | ||
反斜杠(\)替换成两个反斜杠(\\) | sed -i 's#\\#\\\\#g' file 或sed -i 's:\\:\\\\:g' file | √ | × | 反斜杠用双引号括起来会报错 |
反斜杠(\)替换成正斜杠(/) | sed -i 's#\\#\/#g' file | √ | × | 反斜杠用双引号括起来会报错 |
双引号(")替换成两个双引号("") | sed -i 's#\"#\"\"#g' file sed -i "s#\"#\"\"#g" file | √ | √ | |
单引号(')替换成两个单引号('') | sed -i "s#'#''#g" file | × | √ | 不能用单引号括起来,分不清了 |
($)替换成\$ | sed -i 's:\$:\\\$:g' file | √ | × | 不能用双引号,否则会认为是$(正则匹配结尾位置)行的结果追加字符呢 |
(4)curl时用的变量,sed转化
curl -H 'Content-Type: application/json' -X POST -d 参数
(参数中涉及到特殊字符都得转义,而且要多转一层,即$得转成\\$,才能原封不动的供后续使用)
#值替换单引号、反斜杠、双引号 curl的时候用,多一层转义,所以\要用\\
sed -i "s#'#''#g" ${file} ---单引号要转成两个单引号
sed -i 's#\\#\\\\\\\\#g' ${file} ---反斜杠
sed -i "s:\":\\\\\":g" ${file} ---双引号
sed -i 's:\$:\\\\\$:g' ${file} ---$符
curl引用参数的这种形式有两种写法:
1)直接引用单个参数变量
curl -H 'Content-Type: application/json' -X POST -d '{"type":"0","name":" ' ${pa_name} ' "}'
这种需要对变量额外加上一个单引号,才能引用生效。
2)整个参数变量作为一个整体(推荐)
param="{\"type\":\"0\", \"name\":\"${pa_name}\"}"
curl -H 'Content-Type: application/json' -X POST -d "${param}"