跳转至

打包与部署 — 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. 可带走的

  1. Community Cloud 四限:无自定义域名、资源上限、闲置休眠、环境不可定制——根因是免费。
  2. 部署谱系:全托管(Render/Heroku)< PaaS(Elastic Beanstalk)< IaaS(裸 EC2);硬约束是 WebSocket 支持。
  3. Docker 三件套:Dockerfile → 镜像(分层) → 容器;CMD 是运行期指令,其余是构建期。
  4. 分层缓存:依赖先 COPY 先装,代码后 COPY,重建才快。
  5. 端口映射 -p 宿主:容器;0.0.0.0 让 Streamlit 听所有接口。
  6. 配置三通道:STREAMLIT_ 环境变量、config.toml、命令行 flag。
  7. 密钥纪律三连:不进 git(.gitignore)→ 不进镜像(.dockerignore + 运行时挂载)→ 云上用平台注入(.ebextensions)。
  8. .dockerignore 挡掉的文件,用 -v 挂载在运行时补回来。
  9. docker-compose.yml 把 docker run 命令声明化;EB 直接消费它。
  10. 云上请求链:负载均衡器(443/80)→ EC2 → 容器 → Streamlit。
  11. IAM 两件套:用户的访问密钥(给人/CLI)+ EC2 实例角色(给机器);别给 root 发密钥。
  12. eb init(区域/密钥/平台/SSH)→ eb create(环境/DNS/负载均衡)→ eb deploy(再部署)。
  13. 域名 + CNAME 指向 EB 地址;ACM 证书 + DNS 验证域名所有权;443 监听器挂证书 = HTTPS。
  14. 多实例部署下 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」)


  1. 出处:「12 Packaging and deploying」第 38–69 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:55,搜「No custom domain support」)。 ↩

  2. 出处:「12 Packaging and deploying」第 71–79 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:72,搜「doesn’t make any」)。 ↩

  3. 出处:「12 Packaging and deploying」第 81–151 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:81,搜「Deployment alternatives」)。 ↩

  4. 出处:「12 Packaging and deploying」第 146–151 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:147,搜「WebSockets」)。 ↩

  5. 出处:「12 Packaging and deploying」第 153–179 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:164,搜「real-world skills」)。 ↩

  6. 出处:「12 Packaging and deploying」第 195–214 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:197,搜「error about missing depen」)。 ↩

  7. 出处:「12 Packaging and deploying」第 195–214 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:204,搜「blueprint」)。 ↩

  8. 出处:「12 Packaging and deploying」第 227–289 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:254,搜「Linux virtual machine」)。内核说明见第 266–269 段。 ↩

  9. 出处:「12 Packaging and deploying」第 291–469 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:351,搜「recipe」)。分层缓存见第 392–395 段;CMD 见第 511–514 段。 ↩

  10. 出处:「12 Packaging and deploying」第 471–497 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:472,搜「security problem」)。 ↩

  11. 出处:「12 Packaging and deploying」第 499–567 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:555,搜「-v flag」)。-p 的左右分工见第 542–548 段。 ↩

  12. 出处:「12 Packaging and deploying」第 569–609 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:574,搜「configuration manager」)。 ↩

  13. 出处:「12 Packaging and deploying」第 611–627 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:605,搜「docker-compose up」)。 ↩

  14. 出处:「12 Packaging and deploying」第 629–654 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:650,搜「lego set」)。 ↩

  15. 出处:「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」)。 ↩

  16. 出处:「12 Packaging and deploying」第 752–760 与 1064–1070 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:752,搜「free tier」)。 ↩

  17. 出处:「12 Packaging and deploying」第 843–935 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:862,搜「ebextensions」)。.ebextensions 的 git/docker 双忽略见第 923–935 段。 ↩

  18. 出处:「12 Packaging and deploying」第 1176–1191 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1185,搜「cafe」)。 ↩

  19. 出处:「12 Packaging and deploying」第 1193–1257 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1210,搜「registrar」)。CNAME 条目见第 1232–1244 段。 ↩

  20. 出处:「12 Packaging and deploying」第 1259–1317 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1279,搜「auto-renew」)。 ↩

  21. 出处:「12 Packaging and deploying」第 1199–1206 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1204,搜「self-signed」)。 ↩

  22. 出处:「12 Packaging and deploying」第 1319–1371 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1332,搜「perfect place」)。 ↩

  23. 出处:「12 Packaging and deploying」第 157–160 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:157,搜「not the easiest way」)。 ↩

  24. 出处:「12 Packaging and deploying」第 1336–1339 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1338,搜「fairly complicated」)。 ↩

  25. 出处:「12 Packaging and deploying」第 1013–1027 段(text/21-ch12-12-packaging-and-deploying-streamlit-apps.txt:1025,搜「no direct access to the EC2」)。 ↩