)
DevOps 入门系列从 Pod 到 IngressK8s 核心概念你已经会用 Docker 把应用打包成镜像也会用 GitLab CI 自动构建和推送镜像。现在你想把这些镜像放到 Kubernetes 里运行起来。本文用一个真实的 Flask 应用作为例子带你一步步理解 K8s 最核心的几个概念。不执行命令只讲概念和 YAML 的关键部分。你遇到了什么问题假设你写了一个 Flask 应用镜像已经推到了阿里云 ACR 私有仓库你现在想在 K8s 集群里运行它希望一直运行 —— 万一容器挂了能自动重启。跑 3 个副本 —— 分担流量一台机器坏了还有别的。升级版本时不中断服务 —— 从 v1 换到 v2用户无感知。密码不能写死在镜像里 —— 数据库密码要安全地传进去。配置能随时改 —— 比如日志级别、数据库地址不想重新打包镜像。能通过域名从外面访问 —— 用户输入 flask.mycompany.com 就能打开页面。这些问题K8s 里有对应的资源逐个解决。现在我们从最基础的开始怎么让一个容器在 K8s 里跑起来1. PodK8s 里最小的 “工人”你遇到的第一个问题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 是 appvalue 是 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 自动重建全新 PodIP 会变。滚动更新改镜像版本后Deployment 逐步替换旧 Pod不停机。回滚新版有问题可以一键回到旧版本。YAML 核心字段apiVersion:apps/v1kind:Deployment# 声明这是Deployment的配置metadata:name:flask-deployment# 这个 Deployment 的名字spec:replicas:3# 我要 3 个 Podselector:matchLabels:app:flask# 这个 Deployment 只管带 appflask 标签的 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。核心固定 IPService 创建后会分配一个固定的ClusterIP如10.96.100.1不会变。靠标签找人Service 不关心 Pod 的 IP它通过标签比如app: flask自动找到当前所有带有这个标签的 Pod。负载均衡如果有多个匹配的 PodService 会把请求轮流转发给它们默认轮询。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 再建 ServicePod 先跑起来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 也给了固定 IPClusterIP。但 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) 自带 Traefikminikube 需要手动启用。支持 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 Controllerkubectl 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 公网 IPIngress Controller 暴露的 │ ▼ Ingress ControllerNginx/Traefik接收请求 │ ├── 匹配 Ingress 规则host flask.mycompany.com │ ▼ 转发给 flask-serviceClusterIP地址 10.96.100.1:80 │ ▼ flask-service 通过标签 appflask 找到所有 Pod比如 3 个 │ 轮询选中其中一个 Pod IP如 10.244.1.5:5000 ▼ Pod 里的 Flask 容器处理请求 ├── 从环境变量读取配置来自 ConfigMap 和 Secret └── 返回响应资源清单按顺序准备资源必须吗作用Deployment必须管理 Pod 副本数、更新Service必须给 Pod 固定入口Secret按需存密码、镜像仓库认证ConfigMap按需存普通配置Ingress按需域名路由 HTTPS学习重点刚开始练习只需 Deployment ServiceClusterIP先在集群内部用kubectl port-forward测试。熟练后再加 Secret、ConfigMap、Ingress。8. 总结Pod最小的工人里面跑容器通常是 1 个。Deployment店长负责维持 Pod 数量、滚动更新、自愈。Service前台总机给动态变化的 Pod 一个固定 IP并负载均衡。Secret保险柜存密码。ConfigMap便利贴存普通配置。Ingress总导览牌根据域名路由到不同 Service。