打包与部署 — Docker、AWS,和那个「s」
打包与部署 — Docker、AWS,和那个「s」
这一章讲一件事: 免费午餐吃完之后,怎么把一个 Streamlit 应用送上 职业化的运行环境——自己的域名、加密的连接、可控的资源。 路径是:Docker 打包 → AWS Elastic Beanstalk 部署 → 挂域名 → 上 HTTPS。 读完你会理解「应用在云上跑」这个抽象底下到底有哪些零件, 以及密钥在这趟旅程里要换三次住处的原因。
1. 这一章讲什么
前五章(以及第 05 章)用 Streamlit Community Cloud 部署,它是免费的、 为 Streamlit 定制的、连 GitHub 就上线——但四堵墙明确:不能绑自己的域名 (永远挂在 .streamlit.app 下)、资源有上限(超限被限速或杀掉)、闲置约 12 小时就休眠、 环境不可定制(想装非 Python 的软件没门)1。书里点破根因: 这些限制大多因为它免费——绕开限制通常要花钱,但练手可以控制在几美分2。
替代方案按复杂度排成一个谱(书里全览3):
| 档位 | 代表 | 特点 |
|---|---|---|
| 全托管平台 | Render、Heroku、Streamlit in Snowflake | 接近 Community Cloud 的省心,付费解限;规模大了会贵 |
| PaaS(平台即服务) | AWS Elastic Beanstalk、Google App Engine(Flexible) | 便利与控制的中间档;本章选它 |
| IaaS(基础设施即服务) | AWS EC2/S3、Azure、GCP 裸服务 | 终极灵活,全部自己拼;对多数 Streamlit 应用过头 |
一个硬约束贯穿选型:Streamlit 依赖 WebSocket,不支持它的平台跑不了 (所以书里点名 Google App Engine Standard 不行、Flexible 才行)4。
本章路线是刻意选「最涨功」而非「最省事」的:Docker 和 AWS 是行业标准; Docker 给应用可移植性;Elastic Beanstalk 让你「看得见」EC2、负载均衡、S3 这些零件而不必手拼5。
2. 核心原理(一):Docker——把环境打包进盒子
问题。 应用在你机器上跑得好好的,发给同事就缺依赖、Python 版本不对、 操作系统不支持——环境差异吞噬的时间比写功能还多6。
解法三件套,层层递进:Dockerfile(配方,文本文件)→ 镜像(image)(按配方做出来的只读蓝图,分层堆叠)→ 容器(container)(镜像跑起来的实例,一台「电脑里的迷你电脑」)7。 容器和宿主机的关系两层抽象:macOS/Windows 上,Docker Desktop 先起一个 Linux 虚拟机提供内核(Docker 容器共享宿主机内核,不自带);容器里是最小化 Linux + Python + 你的代码和依赖8。
本章的 Dockerfile(照原书 listing 12.2,以 Fact Frenzy 为例),逐指令:
FROM python:3.12-slim # 地基:官方 Python 3.12 精简镜像(底层是 Debian Linux)
WORKDIR /app # 容器内的工作目录
COPY requirements.txt . # 先复制依赖清单……
RUN pip install --no-cache-dir -r requirements.txt # ……先装依赖(分层缓存的关键,见下)
COPY . . # 再复制全部代码(代码常变、依赖少变,顺序省构建时间)
EXPOSE 8501 # 声明容器将监听 8501(文档性质,不真正开端口)
ENV STREAMLIT_SERVER_PORT=8501 # 三个环境变量:端口、
ENV STREAMLIT_SERVER_HEADLESS=true # headless(不自动开浏览器)、
ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0 # 监听所有网络接口(0.0.0.0 = 任何门都接受连接)
CMD ["streamlit", "run", "main.py"] # 容器启动时执行的命令(区别于构建期指令)四个知识点各钉一根桩:分层缓存——每个指令是一层快照,依赖层排在代码层之前,
只改代码时重建飞快;构建 vs 运行——FROM/COPY/RUN 在 docker build 时执行,
CMD 在 docker run 时才执行;0.0.0.0——网络接口是「进电脑的门」,
0.0.0.0 表示所有门都接受外部连接(容器不外听就收不到转发进来的请求);
配置三通道——STREAMLIT_ 环境变量、.streamlit/config.toml、
streamlit run 的 flag 三选一,streamlit config show 看全部9。
跑起来,以及端口映射这个必考概念:
docker build -t fact-frenzy . # 建镜像
docker run -p 8502:8501 fact-frenzy # 起容器:宿主机 8502 → 容器 8501
浏览器访问 localhost:8502 # ← 注意!不是容器里提示的 0.0.0.0:8501
图说:容器是电脑里的电脑。容器内的 Streamlit 只听 8501;
你在「外机」打开浏览器,必须把外机的一个端口(-p 左边的)接到容器的 8501 上。
忘了映射,应用明明在跑,浏览器就是连不上。3. 核心原理(二):密钥的第三次搬家
.streamlit/secrets.toml 在第 05 章进了 .gitignore;现在 COPY . . 会把它
烤进镜像——而镜像是要分发的制品,谁拿到镜像都能把密钥剥出来,哪怕你只是
无意分享10。挡法是 .dockerignore(语法同 .gitignore):
__pycache__/
.git/
.gitignore
.streamlit/ ← 密钥不进镜像但这样容器里又没密钥了,应用起不来。第二次搬家:运行时挂载——
-v ./.streamlit/:/app/.streamlit,把宿主机目录「嫁接」进容器,
镜像保持干净、密钥只在运行时出现11。第三次搬家留给云(见第 5 节)——
因为云上没有「宿主机目录」给你挂。密钥管理是本章真正的暗线:
同一份机密,开发时、容器化时、上云时各有一种正确姿势,错误姿势都长一个样
(进了制品)。
4. 核心原理(三):Docker Compose——把命令变成声明
docker run -p 8502:8501 -v ./.streamlit/:/app/.streamlit fact-frenzy
又长又容易记错。Docker Compose 把这条命令写成 YAML 文件存下来
(docker-compose.yml):services 定义服务,build / ports / volumes 三段
与命令行 flag 一一对应;以后只需 docker compose up12。
声明式配置的价值在云上兑现:Elastic Beanstalk 直接吃这份 docker-compose.yml——本地怎么跑,云上怎么跑,一条声明两处生效13。
5. 核心原理(四):AWS 与 Elastic Beanstalk——请求到底经过了谁
AWS 是一小时一小时租给你的巨型工具箱。本章碰到的零件14: EC2(云上的虚拟机)、S3(云存储)、IAM(身份与权限管理)、 ACM(证书管理器)、CloudWatch(监控)。Elastic Beanstalk 是铺在这些零件 上面的「部署管家」:你交出代码 + Dockerfile,它代你把开虚拟机、装 Docker、 配网络、架负载均衡、接监控全办了15。
先看终点架构(书中 figure 12.4,用一条请求走完全程):
用户浏览器 → fact-frenzy.us-east-2.elasticbeanstalk.com
│
▼
① 负载均衡器(听 80 端口,HTTPS 后是 443) ← EB 架的
│
▼
② 挑一台 EC2 实例(多台之间分流量)
│
▼
③ 实例上的 Docker 容器(你的镜像跑起来的)
│
▼
④ 容器里的 Streamlit 照常处理请求 → 响应沿原路返回
图说:部署的本质没变——还是 streamlit run;变的是它外面包了几层
「替它接客、替它分流、替它挡刀」的基础设施。部署的命令流,三段:
准备工作(网页):IAM 用户 + 访问密钥(给 CLI 用,别用 root!)+
EC2 实例角色(让 EC2 能访问 S3、向 EB 汇报健康)
eb init 交互式初始化:选区域(就近降延迟)→ 输密钥 → 命名应用
→ 认平台(Docker)→ 配 SSH(强烈建议,不然实例出问题进不去)
eb create 交互式建环境:命名环境(fact-frenzy-prod)→ 选 DNS 前缀
→ 负载均衡器选 application 型 → 自动建服务角色
幕后:打包代码 zip → 传 S3 → 建 EC2、配负载均衡、安全组、
自动扩展组、CloudWatch → 启动
eb deploy 改完代码一键再部署;eb open 直接打开应用(省钱注记:默认会建带负载均衡的多实例部署,负载均衡器不在免费档;
练手可用 eb create --single --instance-type t2.micro 起单实例版,
跑完记得全部关掉,费用能压到几美分16。)
6. 核心原理(五):密钥的第三次搬家——.ebextensions
云上没有本地 .streamlit 目录可挂,挂载方案失效。Elastic Beanstalk 的
.ebextensions 机制救场:部署前在服务器上执行的环境准备指令。
secrets_file.config 让 EB 在服务器上生成 /var/app/secrets/.streamlit/secrets.toml
(content 字段照抄密钥全文,管道符 + 缩进),docker-compose.yml 的 volumes
相应改成挂这个绝对路径、端口映射改成 80:8501(EB 要求应用在 80 端口)17。
部署顺序:先 .ebextensions(造密钥文件)→ 再 docker compose(挂它、起容器)
防护:.ebextensions 含密钥,进 .gitignore 和 .dockerignore
(连同 .elasticbeanstalk/ 一起)这条路径的优雅之处:镜像依旧干净,密钥从构建物里彻底消失,只在服务器运行时 由平台注入——和第 05 章「不进代码」、第 3 节「不进镜像」连成完整的三段式纪律。
7. 核心原理(六):域名、证书,和那个「s」
剩下两件事:URL 像样(http://fact-frenzy.us-east-2.elasticbeanstalk.com 既不短也不体面),和连接加密(默认 HTTP,登录信息明文裸奔,浏览器标 「Not Secure」)18。
域名 + DNS。 注册商(GoDaddy、Cloudflare、Route 53……)买个域名,
一年 $20 以内(.com 约 $11)。DNS 是互联网的电话簿;在它里面加一条
CNAME 记录:www.factfrenzyapp.com → fact-frenzy.us-east-2.elasticbeanstalk.com,
意思是「这个域名是别名,把人送那边去」。生效要几分钟到一小时(全球同步)19。
证书 + HTTPS。 浏览器凭什么信这个服务器真的是你的、不是中间人? 靠数字证书——网站的身份证,由证书颁发机构(CA)签发,而 CA 只给 验证了域名控制权的人签发。AWS Certificate Manager 免费代办: 申请 → 它给你一条验证用的 CNAME → 你加进 DNS → 状态变 Issued20。 (为什么必须自有域名:CA 不会给 .elasticbeanstalk.com 签发,因为那是亚马逊的域名、 你证明不了控制权21。)
最后一步,把证书挂到负载均衡器上——它本来就坐在用户和应用之间, 是处理加密的最佳位置(应用本身不用懂 TLS):在 EB 的 Configuration → Listeners 里加一条 443 端口的 HTTPS 监听器,选刚签发的证书。 完事访问 https://你的域名——**URL 里那个 s,就是这一整章的终点**22。
8. 作者的判断与证据
判断:「最涨功」路线优先于「最省事」路线。 明说的教学取舍23。 我们认可学习场景的判断,但补注:生产选型里「省事」(Render/Heroku 类) 往往是正确起点,本章路线适合「想要控制权 + 想懂原理」的人和内部工具; 两种判断的分界是团队有没有运维人手。
判断:eb create 默认的负载均衡方案值得用它走完全程。 依据是后续 HTTPS 章节依赖负载均衡器(单实例也能上 HTTPS 但繁琐,书里明说超范围)24。 结构性判断,证据充分(在本章架构内)。
判断:SSH 进实例的能力要一开始就配。 理由:没有这个,实例出问题时 你没有任何直接诊断手段25。经验之谈,非常同意。
9. 边界与局限
- 点击流全为时点快照:AWS 控制台、IAM 策略名(AdministratorAccess-AWSElasticBeanstalk)、 EB CLI 交互选项,都会漂移;概念(角色、策略、实例配置文件)稳定,界面不保证。
- HTTPS 终止在负载均衡器:负载均衡到 EC2 那一段仍是明文(内网)。 多数场景可接受,高合规场景要端到端加密,本章未涉。
- 自动扩展只提了一个词:EB 建了 auto-scaling group,但扩缩容的触发条件、 冷启动延迟、Streamlit 会话粘性(多实例间 session_state 不共享!)全是深水区—— 多实例部署时,第 04 章的 session_state 模型直接失效,这是本章埋下的最大暗雷, 书里未讨论。
- 成本只是「提醒关掉」级别:负载均衡器 + 多实例的持续月成本、 数据传输费,真实预算要另算。
- 密钥第三次搬家的姿势仍不完美:secrets 明文写在 .ebextensions 的 config 里, 只是换了个文件;更稳的姿势是 AWS Secrets Manager + 运行时拉取,超出本章范围。
判断(我们的,不是书里的): 本章最值钱的不是 AWS 手艺,而是两条 可迁移的主线。一是制品纪律:密钥永远不进构建物——镜像、git 仓库、 部署包,这条纪律跨一切语言和平台,本章给了它第三次也是最难的一次实操。 二是请求路径的心智模型:「浏览器 → 负载均衡器 → 虚拟机 → 容器 → 应用」 这条链是 2020 年代 Web 部署的通用解剖图,懂了它,Render、Fly.io、 Kubernetes 都只是这条链的变奏。对系统研究,这章是「抽象层如何叠加」 的好教材:每一层(容器、IaaS、PaaS、托管平台)都是往上一层买卖的, 复杂性不会消失,只会被转租。 如果错,会错在: 这条链本身在变化——serverless 和边缘计算正在把 「虚拟机 → 容器」这一段进一步蒸发,Streamlit 对 WebSocket 的长连接需求 在 serverless 世界里是二等公民。本章的解剖图是当下主流,不是终局形态; 但「请求经过哪些信任边界」的问法不会过时。
10. 可带走的
- Community Cloud 四限:无自定义域名、资源上限、闲置休眠、环境不可定制——根因是免费。
- 部署谱系:全托管(Render/Heroku)< PaaS(Elastic Beanstalk)< IaaS(裸 EC2);硬约束是 WebSocket 支持。
- Docker 三件套:Dockerfile → 镜像(分层) → 容器;CMD 是运行期指令,其余是构建期。
- 分层缓存:依赖先 COPY 先装,代码后 COPY,重建才快。
- 端口映射 -p 宿主:容器;0.0.0.0 让 Streamlit 听所有接口。
- 配置三通道:STREAMLIT_ 环境变量、config.toml、命令行 flag。
- 密钥纪律三连:不进 git(.gitignore)→ 不进镜像(.dockerignore + 运行时挂载)→ 云上用平台注入(.ebextensions)。
- .dockerignore 挡掉的文件,用 -v 挂载在运行时补回来。
- docker-compose.yml 把 docker run 命令声明化;EB 直接消费它。
- 云上请求链:负载均衡器(443/80)→ EC2 → 容器 → Streamlit。
- IAM 两件套:用户的访问密钥(给人/CLI)+ EC2 实例角色(给机器);别给 root 发密钥。
- eb init(区域/密钥/平台/SSH)→ eb create(环境/DNS/负载均衡)→ eb deploy(再部署)。
- 域名 + CNAME 指向 EB 地址;ACM 证书 + DNS 验证域名所有权;443 监听器挂证书 = HTTPS。
- 多实例部署下 session_state 不跨实例——弹性扩展和会话状态是天生的冤家。
11. 原文地图
| 主题 | 原书章 | 原文位置 |
|---|---|---|
| Community Cloud 的局限 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:55搜「No custom domain support」) |
| 替代方案谱系与 WebSocket 约束 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:81搜「Deployment alternatives」) |
| 本章路线选择及理由 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:158搜「most potential for learning」) |
| Docker 三件套与分层 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:207搜「layer by layer」) |
| 容器架构与端口转发 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:276搜「Port forwarding」) |
| Dockerfile 逐指令讲解 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:291搜「Creating a Dockerfile」) |
| 0.0.0.0 与网络接口 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:429搜「doors」) |
| Streamlit 配置三通道 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:453搜「config show」) |
| .dockerignore 与卷挂载 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:482搜「.dockerignore」) |
| docker build/run 与端口映射 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:529搜「docker run -p」) |
| docker-compose.yml | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:577搜「docker-compose.yml」) |
| AWS 零件(EC2/S3/IAM/ACM) | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:632搜「massive online toolbox」) |
| Elastic Beanstalk 定位 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:645搜「deployment」) |
| 部署终点架构与请求流 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:678搜「mental model」) |
| IAM 用户与访问密钥 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:792搜「best practice」) |
| EC2 实例角色 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:819搜「instance profile」) |
| .ebextensions 注入 secrets | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:872搜「secrets_file.config」) |
| docker-compose 改 80 端口 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:908搜「80:8501」) |
| eb init 流程 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:934搜「eb init」) |
| eb create 与幕后动作 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1058搜「eb create」) |
| eb deploy 与再部署 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1159搜「eb deploy」) |
| HTTP 的风险与动机 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1187搜「Not Secure」) |
| 买域名与 CNAME 记录 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1232搜「phone book」) |
| 证书原理与 ACM 申请 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1274搜「identity document」) |
| DNS 验证域名所有权 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1293搜「pending validation」) |
| 443 监听器挂证书 | 12 Packaging and deploying | text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1346搜「Listeners」) |
Footnotes
-
出处:「12 Packaging and deploying」第 38–69 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:55,搜「No custom domain support」)。 ↩ -
出处:「12 Packaging and deploying」第 71–79 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:72,搜「doesn’t make any」)。 ↩ -
出处:「12 Packaging and deploying」第 81–151 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:81,搜「Deployment alternatives」)。 ↩ -
出处:「12 Packaging and deploying」第 146–151 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:147,搜「WebSockets」)。 ↩ -
出处:「12 Packaging and deploying」第 153–179 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:164,搜「real-world skills」)。 ↩ -
出处:「12 Packaging and deploying」第 195–214 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:197,搜「error about missing depen」)。 ↩ -
出处:「12 Packaging and deploying」第 195–214 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:204,搜「blueprint」)。 ↩ -
出处:「12 Packaging and deploying」第 227–289 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:254,搜「Linux virtual machine」)。内核说明见第 266–269 段。 ↩ -
出处:「12 Packaging and deploying」第 291–469 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:351,搜「recipe」)。分层缓存见第 392–395 段;CMD 见第 511–514 段。 ↩ -
出处:「12 Packaging and deploying」第 471–497 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:472,搜「security problem」)。 ↩ -
出处:「12 Packaging and deploying」第 499–567 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:555,搜「-v flag」)。-p 的左右分工见第 542–548 段。 ↩ -
出处:「12 Packaging and deploying」第 569–609 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:574,搜「configuration manager」)。 ↩ -
出处:「12 Packaging and deploying」第 611–627 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:605,搜「docker-compose up」)。 ↩ -
出处:「12 Packaging and deploying」第 629–654 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:650,搜「lego set」)。 ↩ -
出处:「12 Packaging and deploying」第 656–677 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:668,搜「takes Streamlit app code and sets up the servers」)。 ↩ -
出处:「12 Packaging and deploying」第 752–760 与 1064–1070 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:752,搜「free tier」)。 ↩ -
出处:「12 Packaging and deploying」第 843–935 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:862,搜「ebextensions」)。.ebextensions 的 git/docker 双忽略见第 923–935 段。 ↩ -
出处:「12 Packaging and deploying」第 1176–1191 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1185,搜「cafe」)。 ↩ -
出处:「12 Packaging and deploying」第 1193–1257 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1210,搜「registrar」)。CNAME 条目见第 1232–1244 段。 ↩ -
出处:「12 Packaging and deploying」第 1259–1317 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1279,搜「auto-renew」)。 ↩ -
出处:「12 Packaging and deploying」第 1199–1206 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1204,搜「self-signed」)。 ↩ -
出处:「12 Packaging and deploying」第 1319–1371 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1332,搜「perfect place」)。 ↩ -
出处:「12 Packaging and deploying」第 157–160 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:157,搜「not the easiest way」)。 ↩ -
出处:「12 Packaging and deploying」第 1336–1339 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1338,搜「fairly complicated」)。 ↩ -
出处:「12 Packaging and deploying」第 1013–1027 段(
text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1025,搜「no direct access to the EC2」)。 ↩