上文书实践了 Jendkins 的 freestyle 项目,这次使用 pipeline 进行测试和构建打包
加入自动单元测试
启动 Jenkins 的 docker 以后,freestyle 是通过执行 shell 命令来逐步实现测试、构建和打包的,shell 命令是在 Jenkins 的 docker 容器内执行的,如果要将应用打成 docker 镜像,需要将宿主机的 docker 命令和 socket 文件映射进 Jenkins 的容器里。如果要测试 python 程序的话,直接执行 shell 命令会是在 Jenkins 的容器里面,依赖的 python 版本和库都不具备,所以更好的方式是构建一个测试用的 docker 镜像,运行起来进行测试,测试完后销毁。这个镜像和 python 程序最终要打包成的 docker 镜像的依赖完全一致。
使用 Jenkinsfile 定义 pipeline
Jenkins 的流水线(pipeline)构建方式可以根据一个 Jenkinsfile 文件中声明的步骤来逐步执行 CI/CD 的流程,这个 Jenkinsfile 文件可以放在源码包里由 SCM 进行版本控制。
Jenkins 流程
我这次添加了一个单元测试文件,Jenkinsfile 中增加了自动执行单元测试的步骤,整体流程是
项目文件
目录结构
flask_docker_jenkins_demo/
├── Dockerfile
├── Jenkinsfile
├── README.md
├── app.py
├── requirements.txt
└── test.py
app.py
from flask import Flask, jsonify
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello, World!'
@app.route('/hello/<username>')
def hello_user(username):
return f'Hello {username}'
@app.route('/health')
def health_checking():
ret = {'status': 'UP'}
return jsonify(ret)
if __name__ == '__main__':
app.run(port=5000, debug=False)
test.py
import unittest
import app
class TestHome(unittest.TestCase):
def setUp(self):
app.app.testing = True
self.app = app.app.test_client()
def test_home(self):
res = self.app.get('/')
self.assertEqual(res.status, '200 OK')
self.assertEqual(res.data, b'Hello, World!')
def test_hello_user(self):
name = 'Yngwie'
res = self.app.get(f'/hello/{name}')
self.assertEqual(res.status, '200 OK')
self.assertIn(bytearray(f'{name}', 'utf-8'), res.data)
if __name__ == '__main__':
import xmlrunner
runner = xmlrunner.XMLTestRunner(output='test-reports')
unittest.main(testRunner=runner)
unittest.main()
requirements.txt
Flask
gunicorn
xmlrunner
Dockerfile
FROM python:3.6.9-alpine
ADD . /app
RUN pip install --no-cache-dir -i http://mirrors.aliyun.com/pypi/simple/ \
--trusted-host mirrors.aliyun.com -r /app/requirements.txt
ENV GUNICORN_CMD_ARGS="--bind=0.0.0.0:5001 --chdir=./app/ --workers=2"
CMD ["gunicorn", "app:app"]
Jenkinsfile
pipeline {
agent none
stages {
stage('build and test') {
agent { docker { image 'python:3.6.9-alpine' } }
stages {
stage('build'){
steps {
sh 'pip install --no-cache-dir -r requirements.txt'
}
}
stage('test') {
steps {
sh 'python test.py'
}
post {
always {
junit 'test-reports/*.xml'
}
}
}
}
}
stage('build docker image'){
agent any
steps{
sh 'docker build -t my-flask-image:latest .'
sh 'a=`docker images -f "dangling=true" -q | wc -l`'
sh 'if [ $a -ge 0 ];then docker rmi $(docker images -f "dangling=true" -q);fi'
}
}
}
}
Jenkins 配置
Jenkins 任务
- 【新建任务】 - 起名,选择流水线类型 - 确定
- 【构建触发器】 - 【轮询 SCM】 - 【日程表】填
* * * * *
- 【流水线】 - 【定义】 - 选【Pipeline script from SCM】 - 【SCM】- 选【Git】,填写仓库地址 - 【脚本路径】 - 填Jenkinsfile - 保存
注意
Jenkinsfile 中根 agent 设置为 none,这样可以在后续的 stages 和 stage 中跟别定义不同的 agent,测试使用临时构建的 docker 镜像,而打包要使用Jenkins 的 docker 容器执行,容器执行宿主机映射进来的 docker 命令。
完成
这样,在每次向代码库 push 新的代码以后,Jenkins 会自动拉取代码,构建测试镜像测试,然后打包成 docker 镜像。