
凌晨两点。熬红的眼睛。冷掉的奶茶。满屏的 CrashLoopBackOff 日志。
就为了部署一个"Hello, World" Flask 应用到 Kubernetes 上,用 Helm。
八个小时。
不是 Python 写得有 bug。不是 Docker 问题。是我把 Helm 当成了某种黑魔法——把从博客里 copy 来的 YAML 往上一贴,一行都看不懂,就知道跑 helm install,跟砸一台坏掉的自动售货机希望掉出零食一样。
然后我突然想明白了——我根本不知道 Helm 是什么。
Helm chart 不是魔法咒语。是模板。可复用。有版本。合理。
但那天晚上我就是不懂。
花了两年、若干生产事故、和一个非常有耐心的 DevOps 主管,才把这个牛仔心态给改掉。
现在?Helm 是我的舒适区。首选用它部署任何 Kubernetes 上的东西——尤其是 Flask 应用。
为什么?因为花 2 小时写一个proper chart,远比花 6 小时调试 copy-paste 自 2018 年 Medium 文章的 YAML 要值。
裸 YAML?Fine。做一次性实验可以。
Helm 提供:可复用(别人写好的 chart 改改就能用)、版本化(每次部署都有记录,可回滚)、可参数化(values.yaml 抽出来,环境适配)、整洁(一个命令升配、升版、回滚)。
不是所有 Flask app 都能顺利迁移到 Kubernetes。
我第一个检查:它是 stateless 吗?
因为 Kubernetes 杀 pod 和重启 pod 是家常便饭。如果你 app 在内存里存 session 数据——死定了。
还有——你绑定的是 0.0.0.0 吗?
不是 127.0.0.1,那是本地 loopback,Pod 网络访问不到。
我曾经有个初级工程师,本地跑得好好的,一进集群就超时。排查了 40 分钟才发现 host 配错了。我们叫它"2022 大停机事件"。
所以,用 host="0.0.0.0"。永远。
一个最小化、云就绪的 Flask app 示例:
from flask import Flask
import os
app = Flask(__name__)
@app.route("/")
def home():
return {
"message": "Namaste from Kubernetes!",
"version": os.getenv("APP_VERSION", "dev"),
"pod": os.getenv("HOSTNAME", "unknown")
}
if __name__ == "__main__":
app.run(host="0.0.0.0", port=int(os.getenv("PORT", 5000)))
看到没,我们打印 pod name?扩缩容的时候很有用,能看到是哪个 pod 处理的请求。
还有 env 驱动的 port 和 version,跨环境测试方便。简单,但到位。
Kubernetes 不跑 Python 文件,跑容器。
所以——Dockerfile 时间。
不要在生产环境用 Flask 的 dev server。(我见过。不再有了。)
用 Gunicorn。久经沙场。处理 workers。跟容器合得来。
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
快速注意——我固定了 Python 版本。slim 镜像。没有 cache。保持层小。
然后构建推送:
docker build -t your-dockerhub/myflaskapp:v1.0.0 .
docker push your-dockerhub/myflaskapp:v1.0.0
tag 要讲究。用 v1.0.0,不是"latest"。(那后面会乱成一锅粥。)
现在开始有趣的部分。
运行:
helm create flask-app
哐——你有了一个完整 chart。但太臃肿了。带了 readiness probes、liveness、tests——都是好东西,但现在用不上。
所以我精简它。
聚焦三件事:
我的精简版 values.yaml:
replicaCount: 2
image:
repository: your-dockerhub/myflaskapp
tag: v1.0.0
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 5000
ingress:
enabled: false
resources:
limits:
memory: "128Mi"
cpu: "200m"
requests:
memory: "64Mi"
cpu: "100m"
deployment template:
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}-flask
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: flask-app
template:
metadata:
labels:
app: flask-app
spec:
containers:
- name: flask
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
ports:
- containerPort: {{ .Values.service.port }}
env:
- name: PORT
value: "{{ .Values.service.port }}"
- name: APP_VERSION
value: "{{ .Values.image.tag }}"
resources:
{{ toYaml .Values.resources | nindent 10 }}
看到那些 {{ }} 块了吗?那是 Helm 模板语法。
install 时自动替换 values。干净利落。
看到了吗,我把 image tag 作为 APP_VERSION 注入了?方便调试。调接口能精确看到哪个版本在跑。
骚操作。(好吧,是从上一份工作的一个 senior 那偷来的——但现在它是我的了。)
好了,发版时间到。
helm install my-release ./flask-app
就这样。Helm 创建一个 release——一个被追踪、有版本的 app 实例。
查看:
helm list
kubectl get pods
有问题?深入:
helm status my-release
kubectl logs <pod-name>
需要更多副本?不用改 YAML:
helm upgrade my-release ./flask-app --set replicaCount=5
新版本爆了?回滚。瞬间。
helm rollback my-release 1
哐——回到上一个正常工作状态。
这就是为什么有人问我怎么用 Helm 在 Kubernetes 上部署 Flask app 时,我死推 Helm。
不只是部署。是安全的、可回滚的部署。
有一次周五晚上发版我也这么干了。镜像坏了。服务挂了。
30 秒后——rollback。服务恢复。
经理请我去公司旁边那家店喝的真奶茶。值回我读 Helm 文档花的每一秒。
学 Helm 改变了我对发代码这件事的思考方式。
不是写"能用一次"的 YAML。
是构建可复用、有版本、可恢复的系统。
当你用心用 Helm 在 Kubernetes 上部署 Flask app 时,你不只是推送代码——你在构建信任。
信任在于:新来的开发者 10 分钟能起完整环境。
信任在于:回滚不是战争游戏。
信任在于:周五下午 6 点不是周日上午 6 点的事故开始。
而且说真的,这个学习曲线值。
我还记得第一次成功的 helm install。chart 写得很蠢。deployment.yaml 缩进错了一个多小时才找到。
但当我调通接口、看到 "Namaste from Kubernetes!" 时——我笑得跟刚交出我大学第一个项目似的。
因为某种意义上,是的。
只是工具更好了。
而且少了很多惊魂未定。
译者按:原文 https://dev.to/ptp2308/how-to-deploy-flask-app-kubernetes-helm-the-right-way-1d8i