上文書實踐了 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 鏡像。