CI/CD:使用 Jenkins 的 Pipeline 測試 python 程序並製作 docker 鏡像

上文書實踐了 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 中增加了自動執行單元測試的步驟,整體流程是

構建測試鏡像
執行單元測試
將應用打成 docker 鏡像

項目文件

目錄結構

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 任務

  1. 【新建任務】 - 起名,選擇流水線類型 - 確定
  2. 【構建觸發器】 - 【輪詢 SCM】 - 【日程表】填* * * * *
  3. 【流水線】 - 【定義】 - 選【Pipeline script from SCM】 - 【SCM】- 選【Git】,填寫倉庫地址 - 【腳本路徑】 - 填Jenkinsfile - 保存

注意

Jenkinsfile 中根 agent 設置爲 none,這樣可以在後續的 stages 和 stage 中跟別定義不同的 agent,測試使用臨時構建的 docker 鏡像,而打包要使用Jenkins 的 docker 容器執行,容器執行宿主機映射進來的 docker 命令。

完成

這樣,在每次向代碼庫 push 新的代碼以後,Jenkins 會自動拉取代碼,構建測試鏡像測試,然後打包成 docker 鏡像。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章