说实话,以前我觉得自动化部署这词儿挺高端,离我很远。直到前阵子,我被折腾得快崩溃了:

每天改个小 Bug,都要经历“改代码、传代码、进 FinalShell、重启容器”这一套枯燥的动作。最离谱的是,有时候 Windows 的 OpenSSH 服务莫名其妙抽风,连都连不上,时间全浪费在修环境上了。

后来我照着文档,借助claude,研究了几天 Jenkins 和 Docker Compose。虽然中间也踩了不少坑,但跑通的那一刻,看着飞书群里跳出来的“构建成功”通知,还是成就感满满的。

现在的流程:我只管写代码,推到 Gitee,Jenkins 帮我把 Python 环境和 MySQL 数据库打包成镜像并原地重启。

这篇文章就把我这几天实操的完整路径和踩过的所有坑,一次性全掏给你。

一、自动化部署流程

构建的镜像及容器情况:

jenkins:

飞书机器人通知:

二、python文件结构

在我上一篇文章的基础上修改:这里

app.py:

from flask import Flask, Response
import mysql.connector
import os

app = Flask(__name__)

@app.route('/')
def hello():
    conn = mysql.connector.connect(
        host=os.environ.get('DB_HOST', 'db'),
        user=os.environ.get('DB_USER', 'root'),
        password=os.environ.get('DB_PASSWORD', '123456'),
        database=os.environ.get('DB_NAME', 'testdb'),
        charset='utf8mb4'
    )
    cursor = conn.cursor()
    cursor.execute("SELECT message FROM greetings LIMIT 1")
    row = cursor.fetchone()
    conn.close()
    return Response(
        f"数据库说:{row[0]},这条数据来自MySQL!",
        content_type='text/html; charset=utf-8'
    )

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)

作为一个“中间人”,从 MySQL 数据库里取一句话,然后显示在网页上

host='0.0.0.0': 极其重要! 在 Docker 环境下必须设为 0.0.0.0,否则你在容器外(比如浏览器里)是访问不到这个服务的

Dockerfile:

FROM python:3.12-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]

这个文件告诉 Docker 如何把你的 Python 代码封装成一个集装箱。

  • FROM python:3.12-slim:指定基础镜像。用 slim 版本是为了减小体积,只保留最核心的运行环境。

  • WORKDIR /app:设置容器内的工作目录,就像进入了容器里的“D盘”。

  • COPY . .:把本地电脑当前文件夹下的所有文件(包括 app.py)一股脑拷贝到容器里。

  • RUN pip install...:在容器构建时执行,把代码运行需要的依赖装好。

  • CMD ["python", "app.py"]:这是容器启动后的第一条指令,相当于手动输入命令运行程序。

Dockerfile_db:

FROM mysql:8.0
COPY init.sql /docker-entrypoint-initdb.d/init.sql

dockerfile_db和init.sql这两个文件配合使用,让MySQL 容器在启动时就自带数据。

  • Dockerfile_db

    • FROM mysql:8.0:指定使用官方的 MySQL 8.0 镜像。

    • COPY init.sql /docker-entrypoint-initdb.d/:这是 MySQL 官方提供的“特权路径”,只要把 SQL 脚本扔进去,容器第一次启动时就会自动执行它。

  • init.sql

    • CREATE TABLE...:自动创建一个名为 greetings 的表,并指定 utf8mb4 编码防止中文乱码。

    • INSERT INTO...:提前插入一条 'Hello from MySQL' 的原始数据,方便我们一会儿测试。

一开始我把dockerfile_db里的代码写进了dockerfile,因为我看他们结构差不多,应该能写到一起吧,但是答案是不能,必须分开写。

docker-compose.yml:

name: dockerapp
services:
  web:
    build: .
    ports:
      - "8080:5000"
    depends_on:
      - db
    environment:
      DB_HOST: db
      DB_USER: root
      DB_PASSWORD: 123456
      DB_NAME: testdb

  db:
    build:
      context: .
      dockerfile: Dockerfile_db  
    environment:
      MYSQL_ROOT_PASSWORD: "123456"
      MYSQL_DATABASE: testdb
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

这是最重要的文件,它负责把 Python 和 MySQL 两个孤岛连接起来。

  • services:定义了两个服务,一个是 web(Python),一个是 db(MySQL)。

  • build:告诉 Compose 去哪里找刚才那两个 Dockerfile 来构建镜像。

  • ports: "8080:5000":端口映射。你在浏览器访问服务器的 8080 端口,就会转发到容器内部的 5000 端口。

  • depends_on: - db非常关键! 确保 db 容器先启动,web 容器后启动,防止 Python 因为找不到数据库而报错崩溃。

  • environment:环境变量。这里填写的配置会被刚才的 app.py 通过 os.environ.get 读取到,实现了代码和配置的解耦。

Jenkinsfile:

pipeline {
    agent any

    stages {


        stage('1. 拉取代码') {
            steps {
                // 这里其实可以留空,因为你选了 "from SCM",Jenkins 会自动先拉取代码
                echo '代码已由 Jenkins 自动拉取完毕'
            }
        }


        stage('2. 部署容器') {
    steps {
        // 使用 -p 指定项目名,--remove-orphans 清理孤儿容器
        // -v 确保每次数据库都是根据最新的 init.sql 重新生成的
        sh 'docker-compose -p dockerapp down -v --remove-orphans'
        
        // 重新启动
        sh 'docker-compose -p dockerapp up -d --build'
        
        // 留出时间让 MySQL 建表,避免 500 错误
        echo '正在初始化数据库,请等待...'
        sleep 25
            }
        }
    }
    post {
        success {
    sh "curl -X POST 'https://open.feishu.cn/open-apis/bot/v2/hook/1ae5c408-4818' \
    -H 'Content-Type: application/json' \
    -d '{\"msg_type\":\"text\",\"content\":{\"text\":\"🚀 Jenkins 构建成功!\\n项目:${env.JOB_NAME}\\n编号:#${env.BUILD_NUMBER}\\n状态:部署已完成。\"}}'"
}
        failure {
        sh "curl -X POST 'https://open.feishu.cn/open-apis/bot/v2/hook/1ae5c408-4818' \
        -H 'Content-Type: application/json' \
        -d '{\"msg_type\":\"text\",\"content\":{\"text\":\"❌ Jenkins 构建失败!\\n项目:${env.JOB_NAME}\\n编号:#${env.BUILD_NUMBER}\\n请登录 Jenkins 查看控制台日志。\"}}'"
    }
    }
}

这段代码采用了 声明式流水线 (Declarative Pipeline) 语法,主要分为三个部分:

1. 核心部署阶段 (Stages)
  • stage('1. 拉取代码'):由于你在 Jenkins 任务配置里选了 "Pipeline script from SCM",Jenkins 会在流水线开始前自动把 Gitee 仓库的代码拉到本地。这一步主要起到一个日志记录的作用。

  • stage('2. 部署容器')

    • docker-compose -p dockerapp down -v:先清理现场。-v 参数非常关键,它会删除旧的数据卷,确保你的 init.sql 能够重新执行,保证环境始终是最新的。

    • up -d --build:后台启动并强制重新构建镜像,确保你刚刚提交的代码改动能立即生效。

    • sleep 25:因为 MySQL 启动和初始化表结构需要时间,如果不等这 25 秒,Python 程序连过去就会报错。

2. 消息通知机制 (Post)

这利用了 Jenkins 的 post 钩子,根据构建结果自动触发:

  • success (构建成功):当上面的部署步骤全部跑通时,通过 curl 命令调用飞书机器人的 Webhook 接口。

  • failure (构建失败):如果任何一步报错(比如代码写错了,或者端口被占用),飞书会立刻弹窗告诉你具体哪个任务、哪次构建出了问题。

3. 动态变量引用

代码中的 ${env.JOB_NAME}${env.BUILD_NUMBER} 是 Jenkins 内置的变量:

  • 它们会自动把任务名称(如 dockerapp)和构建序号(如 #12)填入消息内容中,让通知看起来非常专业。

1)这里的部署阶段stage本来第二步是“构建镜像”:

但是后来发现起来的容器没有用这个镜像的啊,原因如下:

所以这一步直接删了,反正会自动构建和命名嘛。

2)第二个问题就是success和failure里的格式改了好几次,一开始是,sh 后面用三引号括住,报错,改成单引号也报错,后面改成了双引号,加了些斜杠才对。

init.sql:

CREATE TABLE IF NOT EXISTS greetings (
    id INT AUTO_INCREMENT PRIMARY KEY,
    message VARCHAR(255)
) CHARACTER SET utf8mb4;

INSERT INTO greetings (message) VALUES ('Hello from MySQL');

requirements.txt:

flask
mysql-connector-python

三、代码提交到Gitee

推代码之前,得先在gitee里建好仓库

登录 Gitee → 右上角 + → 新建仓库:

  • 仓库名:flask_docker
  • 是否开源:私有(学习项目建议私有)
  • 不要勾选初始化仓库(因为你本地已经有文件了)
  • 点创建

这时候页面上会有个url地址,复制它

回到咱们pycharm,在刚才那个项目里打开pycharm终端,依次输入以下命令:

# 初始化 git
git init

# 把所有文件加入暂存区
git add .

# 提交,引号里是描述
git commit -m "flask + mysql + docker compose 学习项目"

# 关联 Gitee 仓库(换成你刚才复制的url)
git remote add origin https://gitee.com/你的用户名/dockerapp.git

# 推送
git push -u origin master
```

推送时会弹出登录框,输入你的 Gitee 账号密码就行。

---

刷新一下Gitee页面就能看到推送成功。

四、起Jenkins容器

可以先把Docker desktop里的没用的容器都先删了,避免页面太乱。

在docker desktop右下角终端里执行即可,\ 是为了换行,你如果想一行写完也可以的

docker run -d \
  --name jenkins \
  -p 8090:8080 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts

第一次启动jenkins需要一串密码,执行这个命令来获取密码:

记得复制啊!!!

docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

然后去浏览器打开网址:

http://localhost:8090

输入你刚才的那一串密码吧。

然后一步步地往下走,安装它推荐的插件,不安装的话后面自己再安装就比较麻烦,这里我觉得jenkins版本必须比较高才行,否则后面问题多得很,让你头疼。咱上面起jenkins容器的时候用的镜像是jenkins:lts,稳定版,就很好。我之前全部都安装失败了,可能是版本太老了,这次就很顺滑了,不过也需要等待几分钟。

五、配置Gitee流水线(pipeline)

5.1

进入 Jenkins 主界面后,点击新建一个任务:

-----任务名称flask-docker-test

-----选择流水线 (Pipeline)

-----点击确定

5.2

安装Gitee专用插件:

-----Jenkins 主页点击右上角齿轮(系统管理)

-----点击插件管理

-----在available plugins里下载 Gitee Plugin

-----install完刷新一下jenkins页面/重新打开一下这个网址

5.3

配置Gitee凭据:

-----点击齿轮

-----安全模块里的“凭据管理”

-----add一个凭据,选择 Username with password,填入你的Gitee账号用户名(非昵称)

-----密码别填gitee密码,去gitee个人设置里生成一个“私人令牌”,自己保存好!密码就填这个

-----ID你就起个比较好记的名字,比如gitee-auth,或者别的

5.4

从首页点击任务名字:

-----点击左侧配置

-----选择流水线,配置如下:

url就是最开始你建仓库时复制的那个url,下方就是刚才配置的gitee凭据。

5.5

由于上一步我们在流水线配置里选择了“pipeline script from SCM”,所以脚本就必须写到gitee里的项目文件里jenkinsfile相当于菜谱,python文件相当于做菜的原材料,jenkins是厨师

-----在python项目里新建一个文件jenkinsfile(无后缀),填入代码

-----这个jenkins是咱们起的容器,并没有执行docker命令的资格,我们需要重新启动一下 Jenkins 容器,通过**挂载 Docker 守护进程(Socket)**的方式,让容器里的 Jenkins 能直接调用咱们Windows 上的 Docker,去docker desktop终端执行:

#删除现有容器,jenkins容器id每个人不一样,看docker desktop
docker rm -f dbb57c9dbe28

#重启,加入挂载参数
docker run -d \
  -p 8090:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  -v /var/run/docker.sock:/var/run/docker.sock \
  --name jenkins \
  jenkins/jenkins:lts

再去powershell(管理者身份)执行:

docker exec -u 0 -it jenkins bash

apt-get update && \
apt-get install -y apt-transport-https ca-certificates curl gnupg lsb-release && \
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg && \
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list && \
apt-get update && \
apt-get install -y docker-ce-cli

chmod 666 /var/run/docker.sock

exit

#能输出docker版本信息即可
docker exec jenkins docker version

OK,现在是可执行docker命令了,但是docker-compose命令还是不行,继续在这里执行:

docker exec -u 0 -it jenkins bash

curl -L "https://github.com/docker/compose/releases/download/v2.24.5/docker-compose-linux-x86_64" -o /usr/local/bin/docker-compose

chmod +x /usr/local/bin/docker-compose

ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose

exit

#输出版本就ok
docker exec jenkins docker-compose version

回到jenkins主页面,任务后面有个运行键,点击进行构建:

我第一次第二次都失败了,原因如下,在上面几个步骤里已经规避了

构建成功,打开http://localhost:8080能看到:

六、jenkins感知gitee代码变动,触发构建

先说一下,这个我整了半天也没成功,配置步骤不难,但是就是有各种问题,给我整无语了,所以我就先放弃这一步了。如果成功的话,就是会在你每次提交代码到gitee的时候直接触发构建,就不用你手动点那个构建了。我也只是想体验一下,不重要。我们公司用的是gitlab,那个应该比较容易成功,大家可以试试,我用gitee是因为之前就把代码托管到这里了,懒得再弄gitlab了,后面有机会试试它能成功不。

七、构建结束,飞书通知

-----先在飞书建个群,添加个机器人,复制生成的 Webhook 地址

-----回到jenkins下载个插件:Notification

-----将复制的webhook地址填入jenkinsfile文件:

任务配置里可以配置触发器:

代表每小时构建一次,成功失败都会飞书通知,通知的样式就看你jenkinsfile咋写的了。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐