DevOps 入门系列:从 Pod 到 Ingress(K8s 核心概念)
你已经会用 Docker 把应用打包成镜像,也会用 GitLab CI 自动构建和推送镜像。
现在你想把这些镜像放到 Kubernetes 里运行起来。
本文用一个真实的 Flask 应用作为例子,带你一步步理解 K8s 最核心的几个概念。
不执行命令,只讲概念和 YAML 的关键部分。
你遇到了什么问题?
假设你写了一个 Flask 应用,镜像已经推到了阿里云 ACR 私有仓库
你现在想在 K8s 集群里运行它,希望:
一直运行 —— 万一容器挂了,能自动重启。
跑 3 个副本 —— 分担流量,一台机器坏了还有别的。
升级版本时不中断服务 —— 从 v1 换到 v2,用户无感知。
密码不能写死在镜像里 —— 数据库密码要安全地传进去。
配置能随时改 —— 比如日志级别、数据库地址,不想重新打包镜像。
能通过域名从外面访问 —— 用户输入 flask.mycompany.com 就能打开页面。
这些问题,K8s 里有对应的资源逐个解决。
现在,我们从最基础的开始:怎么让一个容器在 K8s 里跑起来?
1. Pod:K8s 里最小的 “工人”
你遇到的第一个问题
Docker 里你直接用docker run启动容器。
但在 K8s 里,你不能直接 “跑一个容器”。K8s 的最小调度单位叫 Pod。
Pod 是什么?
定义: Pod 是 Kubernetes 中可以创建和管理的最小部署单元。
一个 Pod 里可以放一个或多个容器。最常见的用法是一个 Pod 只放一个容器(就是你的应用容器)。
Pod 的关键特点
短暂:Pod 可能随时被删除或重建(比如机器坏了、手动删除)。重建后 IP 会变。
最小单位:K8s 不直接操作容器,而是操作 Pod。你告诉 K8s “帮我起一个 Pod”,K8s 才会在里面创建容器。
Pod 的标签:给工人贴上“工牌”
Pod 有一个非常重要的功能:打标签(Label)。
定义: 标签就是贴在 Pod 上的一组 key: value(比如 app: flask、env: prod、tier: backend)。
为什么需要标签?
K8s 集群里可能有成百上千个 Pod。Deployment 怎么知道哪些 Pod 是自己管的?Service 怎么知道把流量转发给哪些 Pod?靠的就是标签。Deployment 和 Service 会说:“我只关心带有 app: flask 这个标签的 Pod”。
在 YAML 里怎么写:
yamlmetadata:labels:app:flask# 标签 key 是 app,value 是 flaskenv:prod重点:
标签的 key 和 value 可以随便起(比如 app: my-app、tier: frontend),只要统一就好。
Deployment 和 Service 里的 selector(选择器)会写相同的标签,表示“我就找这个标签的 Pod”。
一个 Pod 可以打多个标签,用换行分开。
在实际生产过程中,你几乎不会单独创建 Pod。手动创建的 Pod 挂了就真的挂了,没人帮你重启。那谁来帮你管 Pod 的生命周期?答案是Deployment。
2. Deployment:帮你管 Pod 的 “店长”
你遇到的第二个问题
Pod 能跑容器,但 Pod 太 “脆” 了 —— 如果它挂了(比如程序崩溃、机器宕机),没人帮它重启。
你想要的是:一直有 3 个 Pod 在运行,如果少了就自动补上。
另外,以后升级镜像版本(比如 v1 → v2),你希望不停机,一个接一个地换,用户无感知。这就需要Deployment来实现
Deployment 是什么?
定义: Deployment 是 Kubernetes 中用来管理 Pod 的控制器。你可以声明 “我要 3 个 Pod 永远运行”,Deployment 就会维持这个数量,并支持滚动更新和回滚。
关键点
Pod管理:你告诉 Deployment“我要 3 个 Pod,用这个镜像”,它负责创建并监控这些 Pod。
自愈:Pod 意外消失,Deployment 自动重建(全新 Pod,IP 会变)。
滚动更新:改镜像版本后,Deployment 逐步替换旧 Pod,不停机。
回滚:新版有问题,可以一键回到旧版本。
YAML 核心字段
apiVersion:apps/v1kind:Deployment# 声明这是Deployment的配置metadata:name:flask-deployment# 这个 Deployment 的名字spec:replicas:3# 我要 3 个 Podselector:matchLabels:app:flask# 这个 Deployment 只管带 app=flask 标签的 Podtemplate:# Pod 的“模板”metadata:labels:app:flask# Pod 的标签(必须和上面 selector 一致)spec:containers:-name:flaskimage:registry.cn-hangzhou.aliyuncs.com/my-lab/flask-app:v1ports:-containerPort:5000使用重点
你只需要关心
replicas(副本数)、image(镜像地址)、containerPort(容器监听端口)。标签(
app: flask)很重要:Deployment 用它来找到自己管的 Pod,后面 Service 也会用它来找到 Pod。你几乎不会手动创建 Pod,永远是通过 Deployment 来创建。
3. Service:给 Pod 一个 “永不变化的门牌号”
你遇到的第三个问题
Deployment 帮你把 Pod 管得好好的,Pod 挂了会自动重建。但有一个新问题:Pod 每次重建,IP 都会变。
比如你的 Flask 后端 Pod 从10.244.1.5变成了10.244.2.8,前端怎么知道新的 IP 是什么?你不可能每次都去改前端的配置。
你需要一个固定的访问入口,不管后端 Pod 怎么变,这个入口永远不变。
Service 是什么?
定义: Service 为一组 Pod 提供一个固定的访问入口,并自动把请求转发给当前在线的 Pod。
核心
固定 IP:Service 创建后会分配一个固定的ClusterIP(如
10.96.100.1),不会变。靠标签找人:Service 不关心 Pod 的 IP,它通过标签(比如
app: flask)自动找到当前所有带有这个标签的 Pod。负载均衡:如果有多个匹配的 Pod,Service 会把请求轮流转发给它们(默认轮询)。
Service 类型
ClusterIP(默认):只在集群内部可访问。
NodePort:在集群每个节点上开放一个端口(如 30080),外部可以通过
节点IP:30080访问。LoadBalancer:云厂商自动分配一个公网 IP(生产环境常用)。
YAML 核心字段
apiVersion:v1kind:Servicemetadata:name:flask-servicespec:type:ClusterIP# 默认值,不写也行selector:app:flask# 只转发给带这个标签的 Pod(和 Deployment 里的标签一致)ports:-port:80# Service 自己的端口(别人访问这个端口)targetPort:5000# 转发到 Pod 的 5000 端口重点
selector.app必须和 Pod 上的标签(Deployment 里template.metadata.labels)完全一致,否则 Service 找不到 Pod。先有 Deployment 再建 Service(Pod 先跑起来,Service 才能找到它们)。
如果访问 Service 没反应,用
kubectl get endpoints检查 Endpoints 是不是空 —— 空的说明标签没匹配上。
现在 Pod 有固定入口了。但还有两个问题:数据库密码写在哪里?普通配置经常改怎么办?
4. Secret:藏密码的 “保险柜”
你遇到的第四个问题
你的 Flask 应用需要连接 MySQL 数据库。数据库密码是my-secret-pw。
你不想把这个密码写死在 Dockerfile 里(那样任何人都能看到镜像里的密码),也不想写死在代码里(改密码要重新打包镜像)。你想把密码单独存起来,Pod 启动时自己来取。于是你想到了Secret。
Secret 是什么?
定义: Secret 是 Kubernetes 中用来存储敏感信息(密码、Token、SSH 密钥)的对象。
核心
存敏感信息:密码、API 密钥、证书等。
数据是 Base64 编码:不是加密,只是避免肉眼直接看到。生产环境要配合 etcd 加密或外部密钥管理。
Pod 怎么用:可以通过环境变量或挂载文件的方式读取 Secret 里的值。
两种常用类型
Opaque(通用,存任意键值对)
[kubernetes.io/dockerconfigjson](kubernetes.io/dockerconfigjson)(存镜像仓库的账号密码,给 Deployment 拉私有镜像用)
YAML 核心字段
apiVersion:v1kind:Secretmetadata:name:mysql-secrettype:Opaquedata:password:bXktc2VjcmV0LXB3# "my-secret-pw" 的 base64 编码在 Deployment 中引用
env:-name:DB_PASSWORDvalueFrom:secretKeyRef:name:mysql-secretkey:password生产环境须知
创建 Secret 通常用命令
kubectl create secret generic,不用手写 base64,一般也不会手动创建到YAML文件里。永远不要把密码写在 YAML 文件里提交到 Git(除非你用了外部加密工具如 sops)。
Secret 只能被同一个命名空间里的 Pod 引用。
密码解决了。但还有一些不是密码、但经常改的配置(比如日志级别、数据库地址),用 Secret 太麻烦,有没有更简单的?
5. ConfigMap:随手记的 “便利贴”
你遇到的第五个问题
你的 Flask 应用需要知道:
日志级别是 info 还是 debug
数据库主机地址是 mysql-service(这个地址在 K8s 内部是固定的)
这些信息不是秘密,但经常要改(比如开发环境用 debug,生产环境用 info)。你不想每次改日志级别都重新打包镜像。
ConfigMap 是什么?
定义: ConfigMap 是 Kubernetes 中用来存储非敏感配置信息的对象,如环境变量、命令行参数、配置文件。
核心
存普通配置:不敏感的信息(端口、日志级别、地址)。
Pod 怎么用:同 Secret—— 环境变量或挂载文件。
和 Secret 的区别:Secret 存密码等敏感信息,ConfigMap 存非敏感信息。
更新问题:修改 ConfigMap 后,已运行的 Pod 不会自动刷新环境变量。需要重启 Pod(比如删除 Pod 让 Deployment 重建)。
YAML 核心字段
apiVersion:v1kind:ConfigMapmetadata:name:flask-configdata:LOG_LEVEL:infoDB_HOST:mysql-service在 Deployment 中引用
env:-name:LOG_LEVELvalueFrom:configMapKeyRef:name:flask-configkey:LOG_LEVEL-name:DB_HOSTvalueFrom:configMapKeyRef:name:flask-configkey:DB_HOST生产环境须知
开发、测试、生产环境可以用同一个镜像,只换 ConfigMap(和 Secret)—— 好比连锁店,配方一样,每家店的营业时间、菜单价目表各自调整。
ConfigMap 可以挂载成文件,如果你的应用需要读取配置文件(如 application.properties),把整个文件内容塞进 ConfigMap 就行。
如果配置项不多,用环境变量最简单。
现在 Pod 能跑、配置能抽离,就剩最后一步:让用户通过域名访问。
6. Ingress:商场门口的 “总导览牌”
你遇到的第六个问题
你的 Flask 应用已经在集群内部跑起来了,Service 也给了固定 IP(ClusterIP)。但 ClusterIP 只能在集群内部访问,用户怎么从外面访问呢?
你可以把 Service 的类型改成 NodePort 或 LoadBalancer,但它们都有缺点:
NodePort:端口号很大(如
http://node-ip:31234),不好记,也不安全。LoadBalancer:每个 Service 都要买一个云负载均衡器,很贵。
如果以后你有多个应用(比如 api.myapp.com、web.myapp.com),难道每个都配一个 LoadBalancer?
你需要一个统一入口,根据域名把请求转发到不同的 Service。
Ingress 是什么?
定义: Ingress 是 Kubernetes 中管理外部访问集群内服务的 API 对象,通常提供 HTTP/HTTPS 路由。
核心
Ingress 只定义规则:写清楚 “域名 flask.mycompany.com → 转发到 flask-service”。
Ingress Controller 才是干活的:集群里必须有一个 Ingress Controller(如 Nginx Ingress、Traefik)监听这些规则,并实际转发流量。k3s(低配版k8s) 自带 Traefik,minikube 需要手动启用。
支持 HTTPS:可以配置 TLS 证书(存在 Secret 里),自动处理 HTTPS。
节省成本:一个 Ingress + 一个 LoadBalancer 就能服务几十个 Service。
YAML 核心字段
apiVersion:networking.k8s.io/v1kind:Ingressmetadata:name:flask-ingressspec:rules:-host:flask.mycompany.com# 用户访问这个域名http:paths:-path:/pathType:Prefixbackend:service:name:flask-service# 转发给这个 Serviceport:number:80生产环境须知
Ingress 本身不干活,要确认集群里有 Ingress Controller(
kubectl get pods -n kube-system | grep ingress)。本地测试可以用 [nip.io](nip.io) 魔法域名,比如
flask.127.0.0.1.nip.io自动解析到 [127.0.0.1](127.0.0.1),不用配 DNS。如果你只有一两个服务,直接用
type: LoadBalancer或kubectl port-forward可能更简单。Ingress 适合中大型项目。
7. 完整串联:从用户请求到容器处理
用户访问 https://flask.mycompany.com │ ▼ DNS 解析到云厂商的 LoadBalancer 公网 IP(Ingress Controller 暴露的) │ ▼ Ingress Controller(Nginx/Traefik)接收请求 │ ├── 匹配 Ingress 规则:host = flask.mycompany.com │ ▼ 转发给 flask-service(ClusterIP,地址 10.96.100.1:80) │ ▼ flask-service 通过标签 `app=flask` 找到所有 Pod(比如 3 个) │ 轮询选中其中一个 Pod IP(如 10.244.1.5:5000) ▼ Pod 里的 Flask 容器处理请求 ├── 从环境变量读取配置(来自 ConfigMap 和 Secret) └── 返回响应资源清单(按顺序准备)
| 资源 | 必须吗 | 作用 |
|---|---|---|
| Deployment | 必须 | 管理 Pod 副本数、更新 |
| Service | 必须 | 给 Pod 固定入口 |
| Secret | 按需 | 存密码、镜像仓库认证 |
| ConfigMap | 按需 | 存普通配置 |
| Ingress | 按需 | 域名路由 + HTTPS |
学习重点
刚开始练习,只需 Deployment + Service(ClusterIP),先在集群内部用kubectl port-forward测试。熟练后再加 Secret、ConfigMap、Ingress。
8. 总结
Pod:最小的工人,里面跑容器(通常是 1 个)。
Deployment:店长,负责维持 Pod 数量、滚动更新、自愈。
Service:前台总机,给动态变化的 Pod 一个固定 IP,并负载均衡。
Secret:保险柜,存密码。
ConfigMap:便利贴,存普通配置。
Ingress:总导览牌,根据域名路由到不同 Service。