Python DevOps 实战:从脚本执行到 CI/CD 与日志的四个关键环节

2026-05-26 17 预计阅读时间:1 分钟
来源:realpython.com AI 摘要 原文链接

免责声明:本文为 AI 摘要整理,建议结合原文阅读。摘要可能省略上下文、版本差异或边界条件,不作为官方说明。

预计阅读时间:7 分钟

日常开发中,Python 经常扮演 DevOps 的"粘合语言"——写个脚本批量操作服务器、用 pip 管理依赖、在 GitHub Actions 里跑流水线、靠日志排查问题。这些环节看似基础,踩坑频率却很高:脚本权限不对、依赖版本飘移、CI 配置漏了缓存、日志格式不统一导致排查困难。下面逐个拆解,给出可直接落地的做法。

脚本执行:别只靠 python xxx.py

在 DevOps 场景下,脚本往往要在 cron、容器或远程主机上运行,直接 python script.py 会遇到几个常见问题:

  • Python 版本不一致——服务器上 python 可能指向 2.7,而非你期望的 3.x。
  • 缺少虚拟环境——全局安装依赖,不同脚本互相污染。
  • 没有错误处理——脚本中途失败却无人知晓。

推荐做法:

# 明确使用 python3,而非模糊的 python
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt

# 执行脚本时加上 -u,禁用输出缓冲,确保日志实时可见
python3 -u deploy.py

脚本本身也应做基本防护:

#!/usr/bin/env python3
"""部署辅助脚本——检查服务健康状态并滚动重启"""

import sys
import logging

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(message)s",
)

def main():
    try:
        logging.info("开始健康检查...")
        # 你的业务逻辑
    except Exception as e:
        logging.error(f"脚本执行失败: {e}")
        sys.exit(1)

if __name__ == "__main__":
    main()

关键点:sys.exit(1) 让调用方(cron、CI)知道脚本失败了;#!/usr/bin/env python3 让脚本可直接 chmod +x 后执行。

pip 依赖管理:锁定版本才是正经事

pip install xxx 装完就跑,在本地没问题,到了 CI 或同事机器上就可能炸。核心原则:永远用 requirements.txt 锁版本,且区分开发与生产依赖

# 生成带精确版本的 requirements.txt
pip freeze > requirements.txt

pip freeze 会把所有包都写进去,包括调试工具。更好的做法是手动维护,或用 pip-tools

pip install pip-tools

# 写一个 requirements.in,只列出顶层依赖
# requirements.in 内容:
#   requests==2.31.0
#   flask==3.0.0

# 编译出带完整子依赖和版本的 requirements.txt
pip-compile requirements.in

开发依赖单独放:

# requirements-dev.in
#   pytest==8.1.1
#   black==24.2.0
#   mypy==1.8.0

pip-compile requirements-dev.in -o requirements-dev.txt

CI 里安装时只装生产依赖,本地开发才装 dev 依赖:

# CI 环境
pip install -r requirements.txt

# 本地开发
pip install -r requirements.txt -r requirements-dev.txt

GitHub Actions CI/CD:缓存与矩阵是效率关键

Python 项目最常见的 CI 流程是:安装依赖 → 跑测试 → (可选)发布。下面是一个可直接改造的完整 YAML:

name: Python CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]

    steps:
      - uses: actions/checkout@v4

      - name: Set up Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          # 缓存 pip 依赖,加速后续运行
          cache: "pip"

      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt

      - name: Lint with ruff
        run: ruff check .

      - name: Run tests
        run: pytest --tb=short -q

      - name: Type check
        run: mypy src/

几个值得注意的细节:

  • cache: "pip"——setup-python 自带 pip 缓存支持,不用手动写缓存步骤,首次慢、后续快。
  • strategy.matrix——多版本并行测试,确保兼容性。矩阵里版本号用字符串,避免 YAML 把 3.10 解析成 3.1
  • 步骤拆细——lint、test、type check 分开,哪步失败一目了然,不要全塞进一个 run 里。

日志:结构化输出,别用 print

DevOps 脚本和服务的日志最终会进 ELK、Loki 或 CloudWatch。print("something happened") 在这些系统里几乎无法检索。用 logging 模块,输出结构化、可过滤的信息:

import logging
import json

class JsonFormatter(logging.Formatter):
    def format(self, record):
        log_obj = {
            "timestamp": self.formatTime(record, self.datefmt),
            "level": record.levelname,
            "message": record.getMessage(),
            "module": record.module,
            "func": record.funcName,
            "line": record.lineno,
        }
        if record.exc_info:
            log_obj["exception"] = self.formatException(record.exc_info)
        return json.dumps(log_obj, ensure_ascii=False)

handler = logging.StreamHandler()
handler.setFormatter(JsonFormatter())

logging.root.handlers = [handler]
logging.root.setLevel(logging.INFO)

# 使用
logging.info("服务启动", extra={"port": 8080, "env": "staging"})
logging.error("数据库连接失败", extra={"db_host": "10.0.1.5", "retry": 3})

输出效果:

{"timestamp":"2024-06-01 12:03:22","level":"INFO","message":"服务启动","module":"app","func":"main","line":42,"port":8080,"env":"staging"}

在日志平台里,你可以直接用 level=ERRORdb_host=10.0.1.5 过滤,比在纯文本里 grep 高效得多。

落地检查清单

把上面四个环节串起来,跑一遍这个清单:

检查项 达标标准
脚本入口 使用 #!/usr/bin/env python3 + sys.exit 错误码
依赖管理 requirements.txt 且版本锁定,dev 依赖分离
CI 流水线 pip 缓存开启、多版本矩阵、lint/test/typecheck 分步
日志输出 使用 logging 模块,格式可被日志平台索引
虚拟环境 脚本运行前激活 venv,不污染全局

每个点都不复杂,但组合起来就是 Python DevOps 的基本纪律。先把这些做稳,再考虑 Docker 多阶段构建、PyPI 自动发布等进阶流程——地基不牢,上层再花哨也会塌。


相关推荐