声明:部分截图来自于网络
略
通过Jenkins Pipeline + github + docker,实现代码提交以后自动触发环境搭建和测试流程,包括拉取代码、构建镜像、测试镜像、发布镜像、远程部署镜像、回归测试等自动化流程。可以实现开发同学在提交代码后的自动化测试流程,并且测试环境的可移植性较强,受人为因素影响程度较低,整个流程的自动化程度较高,且环境稳定性较高。在此基础上,未来目标是可以实现测试环境可配置化,提升各个测试环境的定制化程度和灵活性。
展示一张最终效果截图:
硬件环境 | 用途 | 描述 |
---|---|---|
服务器 1 | jenkins服务的宿主机 centos 7以上,用于安装、运行Jenkins相关服务和插件 | |
服务器 2 | 测试环境的宿主机 centos 7以上,用于模拟在远程机器部署测试环境 |
软件环境 | 用途 | 描述 |
---|---|---|
docker | 运行Jenkins服务的容器 | 基于Docker环境的Jenkins服务的搭建,对系统依赖程度较低,对系统的污染最低 |
Jenkins | 配置基于Jenkins Pipeline的流水线任务 | 基于Jenkins Pipeline,完成对github、docker插件的集成 |
插件环境 | 用途 | 描述 |
---|---|---|
Pipeline | Jenkins流水线,使用脚本实现整个应用的下载、编译、测试、发布等流程 | 基于Jenkinsfile,定义若干stage和steps来完成代码拉取、编译、构建、测试、发布、远程部署等自动化流程 |
Pipeline: Stage View | 构建复杂流水线的可视化工具 | 方便查看每个版本,每个阶段的执行状态和日志 |
Blue Ocean | 构建复杂流水线的可视化工具 | 重新设计的Jenkins Pipeline,快速只管的查看每个阶段的执行状态和日志 |
SSH Plugin | 使用SSH协议执行远程shell命令 | |
docker build step | 自动化管理docker | |
github | 管理github代码 | 提供github代码库管理功能;在Jenkins服务端提供github-webhook接口,用于实现代码提交以后的通知机制 |
其他环境 | 用途 | 描述 |
---|---|---|
github.com | 整个Pipeline的触发起点 | 当有代码提交动作和分支合并动作时,实时推送给Jenkins Pipeline,触发Pipeline任务的构建 |
hub.docker.com | 用于维护镜像管理 | 基于Dockerfile的镜像构建、拉取和推送 |
添加admin用户
adduser admin
passwd admin
给admin用户设置sudo权限
vi /etc/sudoers
# 添加下面一行配置
admin ALL=(ALL) ALL
切换到admin用户,后续操作都在admin用户下执行
sudo su admin
安装必要的系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
添加软件源
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
执行安装
sudo yum install docker-ce docker-ce-cli containerd.io
启动docker
sudo service docker start
修改/etc/docker/daemon.json,添加国内镜像地址,修改本地默认docker graph地址
{
"registry-mirrors": ["https://xisih51q.mirror.aliyuncs.com"],
"graph": "/home/admin/tools/docker"
}
重启docker
sudo service docker restart
查看配置是否生效(需要使用root账号执行)
docker info
添加docker组
sudo groupadd docker
在docker组中添加当前用户
sudo usermod -aG docker $USER
更新docker组
newgrp docker
查看配置生效(非root用户执行)
docker info
通过docker run命令拉取并启动jenkins镜像
docker run -d -p 8080:8080 -p 50000:50000 -v jenkins-data:/var/jenkins_home -v /var/run/docker.sock:/var/run/docker.sock jenkinsci/blueocean --name jenkins-blueocean
docker exec -it jenkins-blueocean /bin/bash
访问jenkins主页
http://47.93.193.48:8080/
按照官方给出的解释是:将持续集成实现和实施集成到jenkins中;流水线的定义被写在一个文本文件中(Jenkinsfile),该文件被提交到github仓库中(这是流水线即代码的基础),将CD流水线作为应用程序的一部分
参考https://www.jenkins.io/zh/doc/book/pipeline/syntax/
概念:用户定义的一个CD流水线模型,流水线的代码定义了整个构建过程,包括构建、测试和交付等阶段
node(节点):是一个机器,它是jenkins还击的一部分
stage(阶段):stage块定义了整个流水线的执行任务的不同的子集(比如Build、Test、Deploy等)
steps(步骤):一个单一的任务,一个step告诉jenkins在特定的时间点要做什么。例如:要执行shell命令,使用sh步骤:sh ‘make’
进入github --> setting --> Developer settings --> Personal Access Token --> Generate new token
Note:finalbattle
勾选repo和admin:repo_hook两个选项
下载ngrok
wget https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip
启动ngrok
./ngrok http 8080(注:指定的8080端口是jenkins启动所占用的端口)
生成xxxxxxxx.ngrok.io
Session Status online
Session Expires 7 hours, 31 minutes
Version 2.3.35
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://406282713900.ngrok.io -> http://localhost:8080
Forwarding https://406282713900.ngrok.io -> http://localhost:8080
Connections ttl opn rt1 rt5 p50 p90
1 0 0.00 0.00 7.63 7.63
HTTP Requests
-------------
POST /github-webhook/ 200 OK
进入GitHub上指定的项目 --> setting --> WebHooks --> add webhook
# 填写生成好的ngrok地址:
http://406282713900.ngrok.io/github-webhook/
jenkins系统管理 --> 系统设置 --> GitHub --> Add GitHub Sever
找到github选项,Name和API URL都输入https://api.github.com
添加Credentials(凭证提供者)配置
Domain:Global credentials(unrestricted)
kind:Secret text
Secret:c2***********************************ba
ID: github_token
Description: github_token
点击高级配置 →勾选【覆盖 HOOK URL】
点击连接测试,出现Credentials verified for user xxxxx, rate limit: xxxx,说明连接测试成功
在github代码库中添加一个Jenkinsfile文件
pipeline {
agent {
docker {
image 'python:3.5.2'
args '-v $HOME/tools/docker'
}
}
stages {
stage('build') {
steps {
sh 'python --version'
}
}
stage('Test') {
steps {
sh 'python --version'
}
}
}
}
添加一个token(注:多分支里的token由于只支持用户名和密码,其他方式的token会被过滤掉)
用户名:finalbattle
密码:*********
ID:github_token_user_passwd
Description:github_token_user_passwd
添加github地址
https://github.com/finalbattle/templates.git
注:确保https://github.com/finalbattle/templates.git已经配置了webhook
至此,一个简单的Jenkins Pipeline已经搭建起来,可以正常运行
node {
// 定义环境变量
def app
def myRepo = checkout scm
def gitCommit = myRepo.GIT_COMMIT
def gitBranch = myRepo.GIT_BRANCH
def shortGitCommit = "${gitCommit[0..10]}"
def previousGitCommit = sh(script: "git rev-parse ${gitCommit}~", returnStdout: true)
env.VERSION = "0.0.1"
env.server_credentialsId = '47.93.193.48_user_admin_password_xxxxx'
env.host = '47.93.193.48'
env.PRO_ENV = 'test'
env.docker_credentialsId = 'hub_docker_user_finalbattle_password_peng0351atbj'
env.git_credentialsId = 'username_finalbattle_password_Peng0351atbj'
def imageName = 'finalbattle/templates_demo1'
def WORKSPACE_HOME = '/home/admin/projects'
def LOG_HOME = '/home/admin/logs'
def CONF_HOME = '/home/admin/conf'
def SUPERVISOR_HOME = '/home/admin/supervisor'
def SUPERVISOR_CONF_HOME = '/home/admin/supervisor/conf'
def tagName = "${env.VERSION}_${env.PRO_ENV}_${BUILD_NUMBER}"
def imageTagName = "${imageName}:${env.VERSION}_${env.PRO_ENV}_${BUILD_NUMBER}"
stage('Build') {
app = docker.build("finalbattle/templates_demo1", "--build-arg PRO_ENV=${env.PRO_ENV} --build-arg WORKSPACE=${WORKSPACE} . -f Dockerfile.templates_demo1")
app.inside('-u root -itd -v /usr/bin/docker:/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock') {
sh "cp -r ${WORKSPACE}/* /home/admin/projects"
}
}
stage('Test') {
app.inside {
sh 'echo "Test passed"'
}
}
stage('Publish') {
docker.withRegistry('https://registry.hub.docker.com', env.docker_credentialsId) {
echo "Pushing ${tagName}"
// Push tagged version
app.push("${tagName}")
echo "Pushed!"
}
}
stage('Deploy') {
// 部署的目标服务器
withCredentials([usernamePassword(credentialsId: env.server_credentialsId, usernameVariable: 'USER', passwordVariable: 'PWD')]) {
def otherArgs // 区分不同环境的启动参数
def remote = [:]
remote.name = 'ssh-deploy'
remote.allowAnyHosts = true
remote.host = env.host
remote.user = USER
remote.password = PWD
if(env.PRO_ENV == "pro") {
otherArgs = '-p 8000:8000'
}else{
otherArgs = '-p 7001:7001'
}
try {
sshCommand remote: remote, command: "docker rm -f demo"
} catch (err) {
}
echo 'imageName: ${imageName}'
echo 'PRO_ENV: ${env.PRO_ENV}'
sshCommand remote: remote, command: "docker run -itd --name demo -v /etc/localtime:/etc/localtime -e PRO_ENV='${env.PRO_ENV}' ${otherArgs} ${imageTagName} /bin/bash"
}
// 删除旧的镜像
sh "docker rmi -f ${imageName.replaceAll("_${BUILD_NUMBER}", "_${BUILD_NUMBER - 1}")}"
}
stage('Regression Test') {
sh 'echo "Test passed"'
}
}
在代码库中添加如下目录和文件:
APP_META/
└── environment
└── common
├── bin
│ └── appctl.sh
└── conf
└── supervisor.ini
appctl.sh
#!/bin/bash
. /etc/rc.d/init.d/functions
ARGV="$1"
ERROR=0
if [ "x$ARGV" = "x" ] ; then
echo "$0 {start|stop|restart|status}"
exit 0
fi
before_start () {
check_supervisor
update_supervisor
}
check_supervisor() {
if [ ! -f "/home/admin/conf/supervisor.ini" ]; then
echo "/home/admin/conf/supervisor.ini does not exist!"
ERROR=1
exit 0
fi
if [ ! -f "/home/admin/supervisor/conf/supervisor.ini" ]; then
ln -s /home/admin/conf/supervisor.ini /home/admin/supervisor/conf/supervisor.ini
fi
ERROR=0
}
update_supervisor() {
/opt/app-root/bin/supervisorctl -c /home/admin/supervisor/supervisord.conf update
ERROR=0
}
start() {
before_start
/opt/app-root/bin/supervisorctl -c /home/admin/supervisor/supervisord.conf start all
}
restart() {
before_start
/opt/app-root/bin/supervisorctl -c /home/admin/supervisor/supervisord.conf status all
}
status() {
/opt/app-root/bin/supervisorctl -c /home/admin/supervisor/supervisord.conf status all
}
stop() {
/opt/app-root/bin/supervisorctl -c /home/admin/supervisor/supervisord.conf stop all
}
case "$ARGV" in
start)
start
;;
restart)
restart
;;
stop)
stop
;;
status)
status
;;
esac
if [ "x$ERROR" != "x0" ] ; then
echo_failure
else
echo_success
fi
exit $ERROR
supervisor.ini
[program:appone]
directory=/home/admin/projects/appone
command=python /home/admin/projects/appone/start_server.py --settings=/home/admin/projects/appone/settings/testing.yaml --port=7001
autostart=true
autorestart=true
stdout_logfile=/home/admin/logs/appone.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
[program:celery_app]
directory=/home/admin/projects/appone
command=celery -A celery_app worker --loglevel=info -B
autostart=true
autorestart=true
stdout_logfile=/home/admin/logs/celery_app.log
stdout_logfile_maxbytes=100MB
stdout_logfile_backups=10
默认的Dockerfile为基础镜像,包含各个环境共同的基础依赖
Dockerfile.templates_demo1为测试环境镜像,继承自Dockerfile,主要提供测试环境的一些个性化配置,方便与其他环境的Dockerfile做区分,而不会污染基础镜像。除非整个应用依赖包需要进行扩展,需要在基础镜像中进行修改,否则不需要对基础镜像进行变动。
Dockerfile
FROM centos/python-38-centos7:latest
MAINTAINER admin [email protected]
USER root
ENV LANG en_US.UTF-8
RUN ln -s -f /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN yum install -y wget net-tools openssl-devel libffi-devel
RUN echo "[global]" > /etc/pip.conf
RUN echo "index-url = http://pypi.douban.com/simple" >> /etc/pip.conf
RUN echo "trusted-host = pypi.douban.com" >> /etc/pip.conf
RUN echo "disable-pip-version-check = true" >> /etc/pip.conf
RUN pip install celery==4.4.6 docopt==0.6.2 future==0.18.2 gevent==20.6.2 hbmqtt==0.9.6 ipython==7.15.0 ipython-genutils==0.2.0 Jinja2==2.11.2 \
kombu==4.6.11 lazy-object-proxy==1.4.3 MarkupSafe==1.1.1 mccabe==0.6.1 numpy==1.19.0 pickleshare==0.7.5 pika==0.11.2 prompt-toolkit==3.0.5 \
ptyprocess==0.6.0 Pygments==2.6.1 pylint==2.5.3 python3-pika==0.9.14 pytz==2020.1 PyYAML==5.3.1 pyzmq==19.0.1 redis==3.5.1 six==1.15.0 \
toml==0.10.1 tornado==4.5.3 tornado-celery==0.3.5 traitlets==4.3.3 transitions==0.8.1 ujson==3.0.0 vine==1.3.0 wcwidth==0.2.4 websockets==8.1 \
wrapt==1.12.1 zope.event==4.4 zope.interface==5.1.0 voluptuous==0.11.7 supervisor
ENV WORKSPACE_HOME /home/admin/projects
ENV LOG_HOME /home/admin/logs
ENV CONF_HOME /home/admin/conf
ENV SUPERVISOR_HOME /home/admin/supervisor
ENV SUPERVISOR_CONF_HOME /home/admin/supervisor/conf
RUN mkdir -p $SUPERVISOR_HOME && echo_supervisord_conf > $SUPERVISOR_HOME/supervisord.conf
RUN sed -i "s/\/tmp/\/home\/admin\/supervisor/g" $SUPERVISOR_HOME/supervisord.conf
RUN sed -i "s/;\[include\]/\[include\]/g" $SUPERVISOR_HOME/supervisord.conf
RUN echo "files = /home/admin/supervisor/conf/*.ini" >> $SUPERVISOR_HOME/supervisord.conf
Dockerfile.templates_demo1
FROM finalbattle/templates:latest
MAINTAINER admin [email protected]
ENV WORKSPACE_HOME /home/admin/projects
ENV LOG_HOME /home/admin/logs
ENV BIN_HOME /home/admin/bin
ENV CONF_HOME /home/admin/conf
ENV SUPERVISOR_CONF_HOME /home/admin/supervisor/conf
RUN mkdir -p ${WORKSPACE_HOME} && mkdir -p ${LOG_HOME} && mkdir -p ${BIN_HOME} && mkdir -p ${CONF_HOME} && mkdir -p ${SUPERVISOR_CONF_HOME}
RUN echo "#!/bin/bash" > /home/admin/start.sh && chmod a+x /home/admin/start.sh
RUN echo "echo 'start'" >> /home/admin/start.sh
RUN echo "nohup /opt/app-root/bin/supervisord -c /home/admin/supervisor/supervisord.conf &" >> /home/admin/start.sh
RUN echo "/home/admin/bin/appctl.sh start" >> /home/admin/start.sh
COPY ${WORKSPACE}/appone /home/admin/projects/appone
COPY ${WORKSPACE}/APP_META /home/admin/projects/APP_META
COPY ${WORKSPACE}/APP_META/environment/common/bin /home/admin/bin
COPY ${WORKSPACE}/APP_META/environment/common/conf /home/admin/conf
ENTRYPOINT /home/admin/start.sh > /home/admin/start.log && tail -f /home/admin/start.log
添加好相应的文件和目录以后,当提交代码到github库的时候,github会将提交信息同步到Jenkins服务(webhook),Jenkins Pipeline收到消息通知后,自动触发执行job任务中的Build、Test、Publish、Deploy、Regression Test各个阶段。
后续扩展:
基于当前实现的自动化Pipeline流程,可以做一些更高级的扩展,比如在部署测试环境阶段之前,进行人工干预确认;可以在构建时增加其他的集成测试环境、预发环境、灰度环境以及生产环境。
解决上述问题方法:
jenkins在下载插件之前会先检查网络连接,其会读取这个文件中的网址。默认是:
访问谷歌,这就很坑了,服务器网络又不能,肯定监测失败,所以将图下的google改为www.baidu.com即可,更改完重启服务。
该文件为jenkins下载插件的源地址,改地址默认jenkins默认为:https://updates.jenkins.io/update-center.json,就是因为https的问题,此处我们将其改为http即可,之后重启jenkins服务即可。
其他国内备用地址(也可以选择使用):
https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/update-center.json
http://mirror.esuni.jp/jenkins/updates/update-center.json
解决上述问题方法:
确保当前用户和jenkins用户拥有docker组权限:
sudo groupadd docker
sudo usermod -aG docker $USER
sudo usermod -aG docker jenkins
newgrp docker
sudo service jenkins restart
解决问题方法:
使用远程部署方式,避免在本地的主任务中执行docker环境部署
withCredentials([usernamePassword(credentialsId: env.server_credentialsId, usernameVariable: ‘USER’, passwordVariable: ‘PWD’)]) {
…
}
源代码:
docker.withRegistry('https://registry.hub.docker.com/', docker_credentialsId) {
registry_url = "http://registry.hub.docker.com/"
sh "docker push ${imageTagName}"
}
解决问题方法:
app = docker.build("finalbattle/templates_demo1", "--build-arg PRO_ENV=${env.PRO_ENV} --build-arg WORKSPACE=${WORKSPACE} . -f Dockerfile.templates_demo1")
docker.withRegistry('https://registry.hub.docker.com', env.docker_credentialsId) {
echo "Pushing ${tagName}"
// Push tagged version
app.push("${tagName}")
echo "Pushed!"
}
注:不能使用docker push的命令来推送镜像,而是需要使用Jenkins Pipeline中的customImage.push()函数,参考https://www.jenkins.io/zh/doc/book/pipeline/docker/#custom-registry
解决问题方法:
启动docker容器时,增加如下参数:
app.inside('-u root -itd -v /usr/bin/docker:/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock') {
......
}
java.net.ConnectException: Connection refused (Connection refused)
Caused: org.kohsuke.github.HttpException: Server returned HTTP response code: -1, message: 'null' for URL: https://api.github.com/
org.jenkinsci.plugins.github_branch_source.GitHubSCMSource.checkApiUrlValidity(GitHubSCMSource.java:1528)
Caused: java.io.IOException: It seems https://api.github.com is unreachable
原因是国内网络访问github,CDN域名遭到DNS污染,导致我们无法连接使用github的加速服务,因此访问速度缓慢,参考文档:https://zhuanlan.zhihu.com/p/107334179
解决问题方法:
修改/etc/hosts文件:
140.82.114.5 api.github.com