此前 java 项目工作流程: 提交代码创建合并请求 -- 等待代码合并 -- 到 jenkins 控制台 build (全部配置均于控制台完成,构建,打包镜像,推送镜像) 等待完成 -- 到后端机器拉取镜像并停止镜像更新 (生产环境使用多实例依次切负载均衡更新)
整套流程下来还是比较费时,不同项目和环境的配置修改也不方便,且没有版本管理,为了提高效率,改为由 git 管理的 pipeline 流水线打包,打包完成后远程后端机器在 docker swarm 管理下自动热更新镜像
流程变为: 提交代码创建合并请求 -- 代码合并发送 webhook 到 jenkins -- jenkins 更新读取 pipeline 配置进行打包并远程后端机器部署新镜像 -- 发送更新成功或失败通知到企业微信
即开发人员提交代码后就全部自动化了
#1. 备份当前 jenkins 所有项目的配置
#!/usr/bin/python3
"""备份 jenkins 配置, 需要调用 jenkins-cli.jar"""
import subprocess
jenkins_host = ""
jenkins_user = ""
jenkins_password = ""
jenkins_cli_path = r'C:\Users\Administrator\Desktop\sh\jenkins-cli.jar'
jenkins_cmd = f'java -jar {jenkins_cli_path} -s {jenkins_host} -auth {jenkins_user}:{jenkins_password} -webSocket'
def execute_command(cmd):
output = subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0]
output_string = str(output, encoding='utf-8')
return output_string
# 获取所有任务
jobs = execute_command(f'{jenkins_cmd} list-jobs').split('\n')[:-1]
# 备份所有任务配置到 xml 文件
for job in jobs:
job_config = execute_command(f'{jenkins_cmd} get-job {job}')
with open(f'{job}-backup.xml', 'w', encoding='utf-8') as f:
f.write(job_config)
print(job)
# 如需恢复,调用 update-job api 导入即可
#2. 新建 pipeline, docker-swarm, dockerfile 配置文件仓库
# 说明:
# 在 jenkins 机器克隆本配置仓库, jenkins 构建时 pipeline 脚本会 pull 此仓库根据 Dockerfile 开始编译打包上传镜像,调用 swarm 下脚本远程后端机器更新镜像,发送通知
# 后续构建改动都直接在此仓库提交即可,方便管理
# 仓库目录树
/root/jenkins-pipeline-config
├── dev
│ ├── dockerfile
│ ├── pipeline
│ └── swarm
├── old-xml-config
├── prod
│ ├── dockerfile
│ └── pipeline
└── test
├── dockerfile
├── pipeline
└── swarm
# dockerfile 示例:
# cat /root/jenkins-pipeline-config/dev/dockerfile/docker-swarm.Dockerfile
#Nginx Dockerfile
FROM centos:centos7.7.1908
LABEL maintainer="stomach@ley.best"
RUN yum install -y gcc gcc-c++ pcre pcre-devel zlib zlib-devel openssl openssl-devel \
&& useradd -r -s /sbin/nologin nginx \
&& yum clean all
ADD nginx-1.16.1.tar.gz /usr/local/src/
RUN cd /usr/local/src/nginx-1.16.1 \
&& ./configure --prefix=/apps/nginx \
&& make \
&& make install \
&& rm -rf /usr/local/src/nginx*
ADD nginx.conf /apps/nginx/conf/nginx.conf
COPY index.html /apps/nginx/html/
RUN ln -s /apps/nginx/sbin/nginx /usr/sbin/nginx
EXPOSE 80 443
CMD ["nginx","-g","daemon off;"]
# pipeline 示例:
# cat /root/jenkins-pipeline-config/dev/pipeline/docker-swarm.pipeline
def project_name="docker-swarm"
def tag = "dev"
def build_project="middleware-docker-swarm"
def registry_domain = 'registry.cn-shanghai.aliyuncs.com'
def registry_addr = "'${registry_domain}'/xxxxxx"
def username = ''
def password = ''
def workspace = "/var/jenkins_home/workspace/docker-swarm"
def jk_conf_dir = "/opt/jenkins-pipeline-config"
def swarm_script = "'$jk_conf_dir'/'$tag'/swarm/'$project_name'.sh"
def dockerfile = "'$jk_conf_dir'/'$tag'/dockerfile/'$project_name'.Dockerfile"
def wework_notify(msg) {
sh """
curl -s 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxx' \
-H 'Content-Type: application/json' \
-d '
{
"msgtype": "markdown",
"markdown": {
"content": "${msg}"
}
}'
"""
}
pipeline {
agent any
options {
timeout(time: 15, unit: 'MINUTES')
}
stages {
stage('pull jenkins config code') {
steps {
script {
sh """
#!/bin/bash +x
git -C '$jk_conf_dir' pull
ssh kong "git -C '$jk_conf_dir' pull"
"""
}
}
}
stage('Clone code') {
steps {
echo 'clone code..'
git branch:"swarm", url: 'ssh://git@gitlab.xxxxxx.io:2222/xxx/xxxx.git'
}
}
stage('Docker build') {
steps {
echo "docker build ..."
sh """
cd '$workspace'
/var/bin/docker build -t '$build_project':'$tag' -f '$dockerfile' .
"""
}
}
stage('Docker login') {
steps {
echo "docker login ..."
sh """
/var/bin/docker login --username='$username' '$registry_domain' -p '$password'
"""
}
}
stage('Docker tag') {
steps {
echo "docker tag ..."
sh """
/var/bin/docker tag '$build_project':'$tag' '$registry_addr'/'$build_project':'$tag'
"""
}
}
stage('Docker push') {
steps {
echo "docker push ..."
sh """
/var/bin/docker push '$registry_addr'/'$build_project':'$tag'
"""
}
}
stage('Clean image') {
steps {
echo "clean push ..."
sh """
/var/bin/docker rmi -f '$registry_addr'/'$build_project':'$tag'
/var/bin/docker rmi -f '$build_project':'$tag'
"""
}
}
stage('docker swarm update image') {
steps {
script {
sh """
ssh kong "/bin/bash '$swarm_script' update"
"""
}
}
}
}
post {
success {
script {
def changeString = getChangeString()
def branch = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
def msg = "#### ${project_name}已更新部署\n\n分支:${branch}\n\n\n\n${changeString}"
wework_notify(msg)
}
}
failure {
script {
def changeString = getChangeString()
def branch = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
def msg = "#### ${project_name}构建失败\n\n分支:${branch}\n\n\n\n${changeString}\n\n\n\n构建详情: [${env.BUILD_URL}](${env.BUILD_URL})"
wework_notify(msg)
}
}
aborted {
script {
def changeString = getChangeString()
def branch = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
def msg = "#### ${project_name}构建超时\n\n分支:${branch}\n\n\n\n${changeString}\n\n\n\n构建详情: [${env.BUILD_URL}](${env.BUILD_URL})"
wework_notify(msg)
}
}
}
}
@NonCPS
def getChangeString() {
MAX_MSG_LEN = 100
def changeString = ""
def changeLogSets = currentBuild.changeSets
for (int i = 0; i < changeLogSets.size(); i++) {
def entries = changeLogSets[i].items
for (int j = 0; j < entries.length; j++) {
def entry = entries[j]
truncated_msg = entry.msg.take(MAX_MSG_LEN)
changeString += " - ${truncated_msg} (${entry.author})\n"
}
}
if (!changeString) {
changeString = " - No new changes"
}
if (changeString.length() > 4000) {
changeString = changeString[0..4000]
}
return changeString
}
# swarm 示例:
# cat /root/jenkins-pipeline-config/dev/swarm/docker-swarm.sh
#!/bin/bash
# first-run: bash $0
# update image: bash $0 update
NAME="d_docker-swarm"
IMAGE_ADDR="registry.cn-shanghai.aliyuncs.com/xxxxx/middleware-docker-swarm:dev"
PORT="11111"
INTERNAL_PORT="80"
ENV="dev"
REGISTRY_DOMAIN="registry.cn-shanghai.aliyuncs.com"
R_USERNAME="xxxx"
R_PASSWORD="xxxx"
WORKID=0
SWARM_NETWORK="xxxx_net"
REPLICAS="2"
if [ $(docker service ls |grep replicated |wc -l) -eq 0 ];then
docker swarm init &>/dev/null
docker swarm update --task-history-limit=1
# 多机器需要将 worker 加入集群 docker swarm join-token worker
fi
if [ $(docker network ls -f name="${SWARM_NETWORK}" |grep -v SCOPE |wc -l) -eq 0 ];then
docker network create -d overlay "${SWARM_NETWORK}"
fi
if [ $(cat /root/.docker/config.json |grep "${REGISTRY_DOMAIN}" |wc -l) -eq 0 ];then
docker login -u "${USERNAME}" -p "${PASSWORD}" "${REGISTRY_DOMAIN}"
fi
if [ $(docker service ls -f name="${NAME}" |grep -v MODE |wc -l) -eq 0 ];then
docker service create --mount "type=bind,src=/data/wwwlogs,dst=/log" --replicas="${REPLICAS}" --with-registry-auth --name="${NAME}" --network="${SWARM_NETWORK}" -p ${PORT}:${INTERNAL_PORT} --limit-memory=4g -e "spring.profiles.active=${ENV}" -e "snow.workId=${WORKID}" --health-cmd="curl -s localhost:${INTERNAL_PORT} -o /dev/null -w "%{http_code}" |grep -E '404|200' ||exit 1" --health-interval=5s --health-retries=24 --health-timeout=2s "${IMAGE_ADDR}"
docker service update --update-delay=30s --update-failure-action=rollback --update-order=start-first "${NAME}"
fi
# if [ $# -gt 0 ];then
# updateImageAddr="$1"
# docker service update --image="${updateImageAddr}" "${NAME}"
# fi
if [ "${1}" = "update" ];then
docker service update --image="${IMAGE_ADDR}" --with-registry-auth "${NAME}"
fi
docker images | grep '' | awk '{print $3}' | xargs docker rmi &>/dev/null
echo "done."
#3. 配置 jenkins 任务的 Build Triggers 和 Pipeline
i. 勾选 Build when a change is pushed to GitLab. GitLab webhook URL: http://192.168.xx.xx:18091/project/xxxxxxxx
ii. 在 gitlab 对应项目仓库的 Webhooks 填入上述链接,勾选 push events
iii. Pipeline 配置示例如下,Repository URL 填写 #2 新建的仓库地址:
Comments | NOTHING