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