日常开发中,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=ERROR 或 db_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 自动发布等进阶流程——地基不牢,上层再花哨也会塌。