打通jenkins-pipeline的流程,实现简单部署
一. 简单的pipeline例子
新建 Job:在 Web UI 中点击 New Item -> 输入名称:pipeline-demo -> 选择下面的 Pipeline -> 点击 OK
首先确保Jenkins上已经有pipeline相关插件
配置:在最下方的 Pipeline 区域输入如下 Script 脚本,然后点击保存。
node {
stage('Clone') {
echo "1.Clone Stage"
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Stage"
}
stage('Deploy') {
echo "4. Deploy Stage"
}
}
此时测试是构建不成功的,一直在执行中,日志中报jenkins -pending—Jenkins is reserved for jobs with matching label expression,网上搜可能是节点标签用法的设置问题
修改脚本加上节点的指定标签即可构建成功, 此时会运行在slave节点上
node('slave-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"
}
}
二. 部署gitlab
在nfs服务器上部署gitlab
mkdir /opt/gitlab
GITLAB_HOME=/opt/gitlab # 数据持久化目录
docker run --detach \
--hostname gitlab.ctnrs.com \
--publish 443:443 \
--publish 88:80 \
--publish 2222:22 \
--name gitlab \
--restart always \
--volume $GITLAB_HOME/config:/etc/gitlab \
--volume $GITLAB_HOME/logs:/var/log/gitlab \
--volume $GITLAB_HOME/data:/var/opt/gitlab \
gitlab/gitlab-ce:13.12.5-ce.
访问地址:http://IP:88 初次会先设置管理员密码 ,然后登陆,默认管理员用户名 root,密码shiyan823
三. 部署harbor
nfs服务器上部署harbor
项目地址:https://github.com/goharbor/harbor
部署方式
# tar zxvf harbor-offline-installer-v2.0.0.tgz
# cd harbor
# cp harbor.yml.tmpl harbor.yml
# vi harbor.yml
hostname: 192.168.2.35
https: # 先注释https相关配置
harbor_admin_password: Harbor12345
# ./prepare
# ./install.sh --with-chartmuseum
# docker-compose ps
四. 部署 Kubernetes 应用
1.gitlab中新建一个项目jenkins-test1
新版本默认分支名为main,需要改成master分支名,因为jenkins拉取gitlab时默认使用master分支名
进入项目后,选择settings,找到Default branch改名为master即可
此时我们的项目地址为:http://gitlab.ctnrs.com/root/jenkins-test1.git
因为域名在容器内不能解析,访问项目时需要使用IP: http://192.168.2.35:88/root/jenkins-test1.git
2.项目中创建Dockerfile测试文件
FROM busybox
WORKDIR /usr
CMD sleep 300
经测试拉取centos7镜像会403错误,原因未知
3.jenkins中编写pipeline脚本如下
我们这里采用和git commit的记录为镜像的 tag,这里有一个好处就是镜像的 tag 可以和 git 提交记录对应起来,也方便日后对应查看。但是由于这个 tag 不只是我们这一个 stage 需要使用,下一个推送镜像是不是也需要,所以这里我们把这个 tag 编写成一个公共的参数,把它放在 Clone 这个 stage 中
node('slave-jnlp') {
stage('Clone') {
echo "1.Clone Stage"
git url: "http://192.168.2.35:88/root/jenkins-test1.git"
script {
//git rev-parse --short HEAD命令来获取git仓库最新提交的简短commit id,并使用trim()函数去除字符串两端的空白字符
build_tag = sh(returnStdout: true, script: 'git rev-parse --short HEAD').trim()
}
}
stage('Test') {
echo "2.Test Stage"
}
stage('Build') {
echo "3.Build Stage"
sh "docker build -t 192.168.2.35/myrepo/jenkins-demo:${build_tag} ."
}
stage('Push') {
echo "4.Push Docker Image Stage"
sh "docker login -u admin -p Harbor12345 http://192.168.2.35"
sh "docker push 192.168.2.35/myrepo/jenkins-demo:${build_tag}"
}
}
4.添加全局变量
脚本中直接使用harbor的用户名和密码不安全,可以在jenkins中用变量的形式替代
点击页面右上侧的用户名->跳转到用户首页->点击 凭据 ->System 全局 -> Add Credentials:添加一个 Username with password 类型的认证信息,如下
输入 harbor 的用户名和密码,ID 部分我们输入harbor,注意,这个值非常重要,在后面 Pipeline 的脚本中我们需要使用到这个 ID 值
现在我们可以在 Pipeline 中使用这里的用户名和密码了:
stage('Push') {
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'harbor', passwordVariable: 'harborPassword', usernameVariable: 'harborUser')]) {
sh "docker login -u ${harborUser} -p ${harborPassword} http://192.168.2.35"
sh "docker push 192.168.2.35/myrepo/jenkins-demo:${build_tag}"
}
}
注意我们这里在 stage 中使用了一个新的函数withCredentials,其中有一个 credentialsId 值就是我们刚刚创建的 ID 值,而对应的用户名变量就是 ID 值加上 User,密码变量就是 ID 值加上 Password,然后我们就可以在脚本中直接使用这里两个变量值来直接替换掉之前登录harbor的用户名和密码
5.更改YAML
上面我们已经完成了镜像的打包、推送的工作,接下来我们来更新 Kubernetes 系统中应用的镜像版本,当然为了方便维护,我们都是用 YAML 文件的形式来编写应用部署规则,比如我们这里的 YAML 文件:(k8s.yaml)
gitlab的jenkins-test1项目中新建k8s.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: jenkins-demo
spec:
selector:
matchLabels:
app: jenkins-demo
template:
metadata:
labels:
app: jenkins-demo
spec:
containers:
- image: 192.168.2.35/myrepo/jenkins-demo:<BUILD_TAG>
imagePullPolicy: IfNotPresent
name: jenkins-demo
env:
- name: branch
value: <BRANCH_NAME>
注意:
1)因为k8s版本不同,deploy对应的apiVersion值也会不一样,可以使用kubectl api-resources查看下
2)这里我们使用一个 Deployment 资源对象来管理 Pod,该 Pod 使用的就是我们上面推送的镜像。
3)唯一不同的地方是 Docker 镜像的 tag 不是我们平常见的具体的 tag,而是一个 的标识,实际上如果我们将这个标识替换成上面的 Docker 镜像的 tag,是不是就是最终我们本次构建需要使用到的镜像?怎么替换呢?其实也很简单,我们使用一个sed命令就可以实现了:
stage('YAML') {
echo "5. Change YAML File Stage"
sh "sed -i 's/<BUILD_TAG>/${build_tag}/' k8s.yaml"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
}
6.添加部署步骤
stage('Deploy') {
echo "6. Deploy Stage"
sh "kubectl apply -f k8s.yaml"
}
这样到这里我们的整个流程就算完成了。
五.人工确认
我们在实际项目实践过程中,可能还需要一些人工干预的步骤,比如我们提交了一次代码,测试也通过了,镜像也打包上传了,但是这个版本并不一定就是要立刻上线到生产环境的。我们可能需要将该版本先发布到测试环境或者开发环境,所以我们需要增加人工确认的环节,一般都是在 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"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' 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"
}
由于这一步也属于部署的范畴,所以我们可以将最后两步都合并成一步,我们最终的Deploy stage的 Pipeline 脚本如下:
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"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' 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"
}
}
六. Jenkinsfile
在上面的工作中,我们只是完成了一次手动的添加任务的构建过程,在实际的工作实践中,我们更多的是将 Pipeline 脚本写入到 Jenkinsfile 文件中,然后和代码一起提交到代码仓库中进行版本管理。
现在我们将上面的 完整 Pipeline 脚本拷贝到一个 Jenkinsfile 中,将该文件放入上面的 git 仓库中,但是要注意的是,现在既然我们已经在 git 仓库中了,是不是就不需要 git clone 这一步骤了,所以我们需要将第一步 Clone 操作中的 git clone 这一步去掉,现在我们随便更改下源码,比如把 Jenkinsfile 中第一步更改成 Prepare,然后提交代码
可以参考:https://github.com/cnych/jenkins-demo/
在jenkins-test1项目中,新建Jenkinsfile文件,内容如下
node('slave-jnlp') {
stage('prepare') {
echo "1.prepare Stage"
checkout scm
//git url: "http://192.168.2.35:88/root/jenkins-test1.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 Stage"
sh "docker build -t 192.168.2.35/myrepo/jenkins-demo:${build_tag} ."
}
stage('Push') {
echo "4.Push Docker Image Stage"
withCredentials([usernamePassword(credentialsId: 'harbor', passwordVariable: 'harborPassword', usernameVariable: 'harborUser')]) {
sh "docker login -u ${harborUser} -p ${harborPassword} http://192.168.2.35"
sh "docker push 192.168.2.35/myrepo/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"
sh "sed -i 's/<BRANCH_NAME>/${env.BRANCH_NAME}/' k8s.yaml"
if (userInput == "Dev") {
// deploy dev stuff
} else if (userInput == "QA"){
// deploy qa stuff
} else {
echo "do product env"
}
sh "kubectl apply -f k8s.yaml"
}
}
然后我们更改上面的 jenkins-demo1 这个任务,点击 Configure -> 最下方的 Pipeline 区域 -> 将之前的 Pipeline Script 更改成 Pipeline Script from SCM,然后根据我们的实际情况填写上对应的仓库配置,要注意 Jenkinsfile 脚本路径: