Linux 与 Docker:运行配置、日志和持久化

课前先保留判断:课前预习;课后用你的结果复验:课后复习。

今天从哪里开始

昨天的订单接口在本机能运行,换到 Linux 后却可能找不到依赖、读不到配置,或端口根本没有监听。今天保留同一版代码,先在另一环境还原运行条件,再学习用镜像固定应用环境。

先弄清终端连接的是哪台机器,逐层检查路径、解释器和依赖;再区分镜像、容器与数据。最后重启应用,查询同一笔订单,验证数据没有随着进程消失。

能力与项目增量

今天要学会什么

  • Linux 路径权限环境变量
  • 依赖重建
  • 镜像容器与端口
  • Dockerfile、健康检查和持久化

课堂结束时,用自己的项目检查:

  • 能做出来:制造一个端口或环境变量故障并定位,并展示正常输入的结果。
  • 能查出问题:针对“把容器 localhost 当宿主机、重建时误删数据、只看 running”中的风险,说明输入、预期与实际差异;失败后核对数据没有被误改。
  • 能解释修改:提交代码、测试和修改前后结果,讲清自己接受或拒绝了哪一处 AI 建议。

先把我自己的运营工具变成可交接命令

为什么学:D01 的商品读取器已经能工作,但同伴换目录启动就可能找不到模块,导入工具时还可能意外执行文件写入。商城除了网页服务,也有运营批处理命令;今天先把同一个 commerce_ops 包运行清楚,再装进 Linux 和容器。

模块是一个可导入的 Python 文件,包组织多个模块。import 首次加载模块时会执行其顶层代码;__name__ 在直接运行的入口为 "__main__",被导入时为模块名。

# 独立小例 entry_demo.py
def main():
    print("本次才执行运营任务")
 
if __name__ == "__main__":
    main()

分别运行 python entry_demo.py 和 python -c "import entry_demo",后一条不应执行任务。入口保护可以避免导入时写文件或启动长任务;它不是权限控制。

当天给出 commerce_ops/__main__.py 作为薄入口,它调用本人补的 runtime.main()。从包含 commerce_ops 文件夹的项目根执行 python -m commerce_ops;-m 按模块/包定位入口,包入口文件是 __main__.py。不要直接执行包内文件再靠任意修改 sys.path 消除相对导入错误。

环境变量是文本,读取时必须决定缺省与失败

运行同一份代码时,运营只需要切换输入和输出文件。我用 os.getenv 读取 COMMERCE_IMPORT_SOURCE 与 COMMERCE_IMPORT_OUTPUT,配置缺失返回退出码 2,成功返回 0;输入输出同路径必须拒绝,避免把原始 CSV 截断。

Windows PowerShell 从本人项目根运行:

$env:COMMERCE_IMPORT_SOURCE='data/products.csv'
$env:COMMERCE_IMPORT_OUTPUT='data/products.review.jsonl'
python -m commerce_ops
$LASTEXITCODE

Linux 在同样项目根运行:

export COMMERCE_IMPORT_SOURCE=data/products.csv
export COMMERCE_IMPORT_OUTPUT=data/products.review.jsonl
python -m commerce_ops
echo $?

变量在进程创建时从父进程继承;另一个终端的环境不一定相同。getenv 返回文本,不会自动把 "false" 变成 False,bool("false") 仍为 True。端口、批大小等数字配置需要显式转换与校验,不能直接拿字符串参与运算。

我完成 runtime.py 时只调用本人 D01 的读取和导出,不复制第二套金额规则。缺配置、路径相同、坏行失败都返回非零,不打印整份环境或数据库密码。

logging 让同伴知道程序走到了哪里

为什么学:运营工具在 CI 或容器里运行,没有人一直盯着屏幕。logging 按级别记录事件,日志与业务输出可以分别处理;print 仍适合即时演示,但不应承担全部排障记录。

import logging
 
logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")
logger = logging.getLogger(__name__)
count = 3
logger.info("import_complete count=%s", count)
logger.warning("import_partial_output_discarded")

getLogger(__name__) 给模块命名;DEBUG 用于细节,INFO 记录正常进展,WARNING 提醒异常情况,ERROR 记录本次失败。级别过滤会影响是否可见。logger.exception 在异常处理内可保留栈,但不能把敏感值写进异常消息后再原样外发。

我们的运行器只记录完成数量或错误类型,输出清单仍写 JSONL。import_complete count=3 表示本地文件完整生成,不表示已经写 MySQL。中途失败留下的部分文件必须丢弃,不能交给下一步当成完整清单。

一次启动就是一个进程

进程有自己的内存、工作目录、环境和进程号。os.getpid() 可观察当前进程号;运行器完成后退出,Web 服务则持续等请求。进程结束,内存变量就不再是下一次启动的数据来源;文件和数据库是否留下,由存储位置决定。

我先运行 python check_d04_python.py 验证本地入口,再运行实际命令核对日志、退出码和文件。Ctrl+C 请求中断;不能把一个仍活着的进程等同于健康服务。W03 会在此基础上比较线程共享内存与多进程独立内存。

宿主机、虚拟机、终端、Shell 和进程

宿主机到 Python 进程的执行层次

层课堂载体它负责什么
宿主机Windows运行 VMware、保存本地文件
虚拟机VMware提供一台隔离虚拟计算机
客体 OSCentOS 7管理 Linux 文件、用户和进程
终端客户端FinalShell连接并显示远端 Shell(命令解释器)
Shellbash解析命令、变量和重定向
Python 进程Python 3.12执行订单工程

FinalShell 连接成功只证明网络与 SSH 入口可用,不证明 Python、Git、依赖、仓库或订单项目可运行。

第一次登录先收集事实

uname -a
cat /etc/centos-release
id
pwd
ls -la
git --version
python3 --version
python3 -c "import sys; print(sys.executable)"

每条命令回答不同问题:

命令证明
uname -a内核与系统架构
id当前用户和组
pwd当前工作目录
ls -la文件名、隐藏文件和权限
git --versionGit 是否可调用
python3 --version命令对应版本
sys.executable实际解释器路径

版本检查缺失时,不进入项目命令。课程镜像应由教师课前 G0 配好 Python 3.12、Git 和网络;课堂不现场编译 Python。

Linux 路径与大小写

路径、大小写与权限

Linux 从 / 开始形成目录树。普通用户通常在 /home/<user> 下工作。

pwd
mkdir -p ~/workspace
cd ~/workspace
ls -la

Orders.json 与 orders.json 在 Linux 中是两个文件。下面的 Windows 隐藏假设会在 Linux 暴露:

Path("data/Orders.json")   # 合同实际文件名是 orders.json

最小实验

mkdir case-lab
cd case-lab
touch orders.json
ls Orders.json

保留原始错误,再把文件名和代码合同统一。不要为了“兼容”同时创建两个大小写文件。

权限位只讲项目所需深度

ls -ld .
ls -l data

r/w/x 对文件和目录含义不同。遇到 Permission denied,先判断当前用户、目录所有者和权限位。

错误做法:

chmod -R 777 .

它放大权限并掩盖目录归属。课堂优先在你有权限的工作目录重新 clone;确需调整时只修改明确目标和最小权限。

环境变量怎样影响命令

Shell 按 PATH 顺序寻找 python、git 等命令:

echo "$PATH"
which python
which python3
type -a python3

环境变量不改变 Python 语法,却会决定启动哪个解释器、程序读取什么配置。不要用修改 PYTHONPATH 作为包结构问题的默认修复。

为什么不能复制 Windows .venv

虚拟环境包含解释器路径、脚本格式和可能的系统相关二进制。Windows 虚拟环境的解释器位于 Scripts 子目录,不能把整套目录复制到 Linux 运行。

在 Linux 重新创建:

python3.12 -m venv .venv
source .venv/bin/activate
python --version
which python
python -c "import sys; print(sys.executable)"

三条解释器证据都应指向当前项目 .venv。

如果 python3.12 不存在

保留:

command -v python3.12
python3.12 --version

课堂切换到教师已验收的补救 VM/快照,并把本机状态记 Pending。不要在课堂现场搜索随机脚本编译 Python,也不要降低版本标准后假装通过。

为什么“我电脑能运行”不足以交付给企业使用?

目标机的解释器、依赖、端口、环境变量和数据库地址可能不同。需要固定依赖与运行配置,用真实请求和日志验证目标环境。

根据声明重建依赖

python -m pip --version
python -m pip install -r requirements.txt
python -m pip check

若依赖下载失败,先区分网络、证书、镜像和包版本。保存原始输出;教师提供的离线依赖可以帮助你继续学习,但不能据此把你尚未跑通的 CentOS 环境写成“已通过”。

ModuleNotFoundError 怎样定位

按顺序问:

  1. 当前 python 是谁?
  2. 包是否已安装到该解释器?
  3. 当前目录是不是项目根?
  4. pyproject.toml 的包发现路径是否正确?
  5. 包名大小写是否一致?
python -c "import sys; print('\n'.join(sys.path))"
python -m pip show w02-order-management
find . -maxdepth 3 -type f | sort

不要先手工修改 PYTHONPATH,它可能只让当前终端暂时“看见”错误结构。

分层排障顺序

环境故障分层排查

网络/认证
→ 仓库地址和 commit
→ 当前目录、文件名、权限
→ venv 与 sys.executable
→ 依赖和包安装
→ 业务数据/模型/Storage

一次只执行一个能排除一层的命令。让 AI(人工智能) 给排障建议时,要求它同时说明“这个命令能证伪什么”。

Docker 解决什么问题

手工在 Linux 重建一次后,我们知道环境包含哪些条件,也看到反复安装容易漏项。Docker 接下来负责把这些构建步骤写清;数据库地址、端口和持久化仍需单独配置。

Docker 把应用运行需要的文件、依赖和启动方式打成可重复构建的镜像。

但 Docker 不会自动修好:

  • 业务错误;
  • 事务错误;
  • 错误合同;
  • 前端字段不一致。

先让系统本地通过,再容器化。

不要把 Docker 当成“万能运行按钮”。


Image、Container、Volume

Dockerfile、构建上下文、镜像、容器、运行配置和证据的关系|700

先区分四件事:Dockerfile 是配方,image 是构建结果,container 是一次运行,运行证据是实际执行后留下的结果。看到仓库里有 Dockerfile,只能说明“配方存在”。

镜像、容器与数据卷

Image 是只读构建结果。

Container 是镜像的运行实例。

Volume 独立于容器生命周期保存数据。

MySQL 数据必须落到正确 Volume。

应用容器自身的临时写层不应该承担数据库持久化。


Dockerfile 只是构建说明

下面按本周基础 API 入口阅读 Dockerfile。完整商城参考文件默认使用 W03 的 app.main;W02 的本人 Dockerfile 使用 app.foundation,避免提前依赖 Redis 和员工配置:

FROM python:3.12-slim
 
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1
 
WORKDIR /app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
 
EXPOSE 8000
HEALTHCHECK --interval=10s --timeout=3s --retries=6 CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=2)"
CMD ["uvicorn", "app.foundation:app", "--host", "0.0.0.0", "--port", "8000"]

逐行说明见 Dockerfile 与 CI/CD 脚本阅读指南。课堂先抓住:

  • 基础镜像;
  • 工作目录;
  • 为什么先复制依赖清单、安装依赖,再复制经常变化的应用代码;
  • 非敏感环境默认值;
  • RUN 在 build(构建) 时执行,CMD 在 container 启动时执行;
  • EXPOSE 只是声明,不会自动发布主机端口;
  • HEALTHCHECK 只检查最小健康信号;
  • 启动命令为什么监听 0.0.0.0。

先别看答案:如果把 RUN pip install ... 改成 CMD pip install ...,容器每次启动会发生什么?如果只写 EXPOSE 8000,为什么浏览器仍可能访问不到?

Dockerfile 存在不代表:

  • 能 build;
  • 能 run;
  • 能访问;
  • 能看日志;
  • 能持久化。

所以今天验五段真实证据。


Dockerfile(镜像构建文件)与启动时配置分别承担什么?

Dockerfile 描述基础环境、依赖、文件与默认启动入口;运行时提供端口映射、环境变量和存储连接。数据库密码不能烘焙进镜像或提交仓库。

构建上下文

docker build 会把构建上下文发送给 Docker。

不要把这些内容放进去:

  • .venv
  • .git
  • 测试缓存;
  • 本地数据;
  • .env
  • 密钥;
  • 大型无关文件。

.dockerignore 不是装饰。

它影响速度、镜像内容和安全边界。


端口映射

端口映射

容器内 Uvicorn 要监听:

0.0.0.0:8000

主机发布:

docker run -p 8000:8000 ...

如果只监听容器内 127.0.0.1,主机端口可能无法访问。

排错要区分进程监听和端口发布。


环境变量

应用需要数据库连接:

COMMERCE_DATABASE_URL=mysql+pymysql://...

正确做法:

  • .env.example 只列变量名和安全示例;
  • 真实密码不进 Git;
  • 运行时注入;
  • README 说明来源;
  • 日志不回显完整连接串。

镜像层里写死密码会进入历史。

不要这么做。


应用与 MySQL 的运行关系

容器能启动了,还不代表它找得到数据库。先画出应用和 MySQL 分别在哪里运行,再选择地址;不要把每一处 localhost 都当成自己的 Windows 电脑。

Docker 运行拓扑

浏览器或 curl 从主机访问 API。

如果 MySQL 在同一 Compose(多容器配置)中,API 用数据库服务名连接,并为数据库配置命名卷;如果 MySQL 在宿主机,则使用该平台可达的宿主地址。先确认当前实验采用哪一种,不混抄两套地址。

API 用 /health 表达服务响应状态。

注意:health(健康检查) 通过不代表下单、事务或浏览器全部通过。

它只是最小健康信号。


容器里的 localhost 为什么连不到宿主机 MySQL?

容器有自己的网络命名空间,localhost 指容器本身。Docker Desktop 中访问宿主机用相应宿主入口,本课为 host.docker.internal;同 Compose 服务间用服务名。

healthcheck 应该检查什么

最小 /health:

  • Web(网页应用) 进程可以响应;
  • 返回合同版本;
  • 必要时检查数据库可达性,但要避免昂贵操作。

Docker healthcheck:

  • 定期执行轻量命令;
  • 根据退出码判断健康;
  • 允许启动宽限期;
  • 不把完整业务回归塞进 healthcheck。

健康检查和业务验收各有职责。


容器 running 为什么不能证明应用可用?

它只表示主进程还在。还要检查 HTTP、依赖连接和实际业务读取。健康检查、业务请求和日志相互补充,不能只看进程状态。

日志回答什么

日志用于观察运行中的事实:

  • 服务是否启动;
  • 请求是否到达;
  • 状态码;
  • request_id;
  • 错误类型;
  • 数据库连接问题。

日志不能包含:

  • 密码;
  • Token(词元);
  • 真实客户隐私;
  • 完整敏感请求体。

本周数据全部是虚构数据。


Docker 五段证据

Docker 五段证据

  1. Build:镜像真实构建;
  2. Run:容器真实运行;
  3. Health:服务健康可读;
  4. Log:一次请求与日志关联;
  5. Volume:删除并重建容器后,数据仍可查询。

缺一段,就缺一个主张。

教师运行不能代替本人在目标环境运行。


如何向运营证明更新应用后昨天的订单还在?

记录已知订单 ID 和内容,重启或重建应用后读取同一条数据;不能重新导入样本冒充恢复。数据应在约定的持久化位置,不能随应用临时容器丢失。

重启和重建怎样验证数据持久化?

先保存一笔已知订单 ID,再重启/重建应用容器后读取同一笔。数据在宿主机数据库或命名卷中;测试期间不能删除卷或重新导入样本来冒充数据仍在。

先用最小部署建立运行边界

我先画主机、容器、端口、应用进程和数据库的数据目录。应用容器里的 localhost 指向该容器;访问宿主机 MySQL 需要按运行平台配置地址。环境配置与镜像分开,账号密码不能写进 Dockerfile,也不进入 Git。

这次重建只替换应用容器,保留原数据库;如果使用数据库容器,必须先确认持久化卷及其挂载位置。重建前后查询同一订单并比较金额和库存。停止应用不等于删除数据库卷。

课堂基线采用 MySQL 8.4;本机已有 5.7 的兼容运行证据只能证明该版本的行为,不能代替 8.4 验收。平台安装问题先按环境门登记,不能用截图代签你自己跑。

学生自己动手,AI 只能改你允许改的地方

我先保存当前可运行版本,再完成“制造一个端口或环境变量故障并定位”。让 AI 一次只修改负责该行为的文件;修改前预测正常与失败结果,检查差异,再运行回归。每人需要解释一处接受或拒绝的建议。

错误与边界怎样验收

今天重点检查:把容器 localhost 当宿主机、重建时误删数据、只看 running。保留输入、预期、实际输出和修复后的结果,不能只贴全绿截图。未验证的外部依赖如实记为待验证。

留给下一阶段

现在能够分别检查环境、应用入口、数据库连接和旧数据。明天把这些步骤交给同伴和 CI(持续集成)重复执行,让通过结果绑定到明确的代码版本。

官方资料

配套学习资料