前言
单机 Docker 跑十几个容器还行,成百上千个跨几十台机器呢?谁挂了谁来重启?怎么滚动升级不中断服务?Kubernetes(K8s)就是容器世界的"操作系统",负责调度、自愈、扩缩容。
一、为什么需要 K8s
| 痛点 | K8s 的答案 |
|---|---|
| 容器挂了没人管 | 自愈:自动重新拉起(重启/换节点) |
| 手动挑机器部署 | 调度:按资源请求自动选择节点 |
| 发布中断服务 | 滚动更新:新旧副本渐进切换,随时回滚 |
| 流量打到哪个容器 | 服务发现与负载均衡(Service) |
| 配置改了要重启 | 配置中心(ConfigMap/Secret 热更新) |
| 高峰扩容、低谷缩容 | HPA 自动扩缩容 |
二、架构:控制面 + 工作节点
┌─────────────── 控制平面 (Control Plane) ───────────────┐
│ API Server ── 唯一入口, 所有增删改查走 REST/kubectl │
│ etcd ──────── 集群大脑: 存储全部期望状态(键值库) │
│ Scheduler ─── 决定新 Pod 落在哪个节点 │
│ Controller ── 控制循环: 不断对齐"期望状态 vs 实际状态" │
└───────────────────────────────────────────────────────┘
│ 监听/上报
┌───────────▼─────────────── 工作节点 (Node) ──────────────┐
│ kubelet ──── 节点管家: 管理本节点容器生命周期 │
│ kube-proxy ─ 网络规则: 让 Service 的 VIP 能转发到 Pod │
│ 容器运行时 ── containerd(实际跑容器的进程) │
└────────────────────────────────────────────────────────┘
核心思想:声明式 API + 控制循环
你只声明"我要 3 个副本的 nginx"(写 YAML),控制器持续对比现状并修复差距——挂一个补一个。这与传统"一步步执行命令"的命令式运维是范式级差异。
kubectl get pods # 万物皆可 get
kubectl describe pod xxx # 排障第一命令
kubectl logs -f xxx # 看日志
kubectl exec -it xxx -- sh # 进容器
三、核心对象(由小到大)
Pod:最小调度单元
apiVersion: v1
kind: Pod
metadata:
name: web
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests: {cpu: 100m, memory: 128Mi} # 调度依据
limits: {cpu: 500m, memory: 256Mi} # 上限(超限可能被 OOM 杀)
- 一个 Pod = 一组同生共死、共享网络/存储的容器(多数 Pod 只有一个主容器)
- Pod 是易逝的(牛,不是宠物)——IP 会变,所以不能直接对外
Deployment:管理 Pod 的副本与升级
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3 # 期望副本数
selector:
matchLabels: {app: web}
template: # Pod 模板
metadata:
labels: {app: web} # 打标签, selector 靠它认领
spec:
containers:
- name: web
image: reg.local/myapp:v1.2.0
ports: [{containerPort: 8080}]
livenessProbe: # 存活检查: 失败则重启容器
httpGet: {path: /healthz, port: 8080}
readinessProbe: # 就绪检查: 未就绪不接流量
httpGet: {path: /ready, port: 8080}
Deployment 能力:副本控制、滚动更新、回滚、暂停发布。
kubectl rollout status deploy/web
kubectl rollout undo deploy/web # 一键回滚
kubectl scale deploy/web --replicas=5 # 手动扩容
kubectl set image deploy/web web=reg.local/myapp:v1.3.0
Service:稳定的访问入口
Pod IP 随时会变,Service 提供固定虚拟 IP(VIP)+ 域名,把流量负载均衡到后端 Pod:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector: {app: web} # 认领带此标签的 Pod
ports:
- port: 80 # Service 端口
targetPort: 8080 # 转发到容器的端口
三种类型:
ClusterIP 集群内访问(默认): http://web.default.svc.cluster.local
NodePort 每个节点开一个高位端口: 节点IP:30080
LoadBalancer 云厂商分配公网 LB(付费但省心)
Ingress:七层入口(域名/路由/TLS)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blog
spec:
rules:
- host: antidebug.cn
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port: {number: 80}
集群只需一个 LoadBalancer 暴露 Ingress Controller,内部靠域名路由到不同 Service。
ConfigMap / Secret:配置与敏感信息
apiVersion: v1
kind: ConfigMap
metadata:
name: app-conf
data:
APP_ENV: "production"
nginx.conf: |
server { listen 80; }
---
# Secret 同理, 值是 base64(编码≠加密, 生产用外部密钥管理)
apiVersion: v1
kind: Secret
metadata:
name: db-cred
stringData:
password: "s3cret"
# 挂进 Pod: 环境变量 或 文件
env:
- name: APP_ENV
valueFrom: {configMapKeyRef: {name: app-conf, key: APP_ENV}}
volumeMounts: [{name: conf, mountPath: /etc/app}]
volumes:
- name: conf
configMap: {name: app-conf}
命名空间:逻辑分区
kubectl get ns
kubectl -n prod get pods # 生产命名空间下的 Pod
按环境或团队划分,配 ResourceQuota 限额。
四、一个应用的完整上云之路
# 1. 镜像先行(上篇的 Dockerfile 产物)
docker build -t reg.local/myapp:v1.0 . && docker push reg.local/myapp:v1.0
# 2. 写 YAML(Deployment + Service + ConfigMap)
kubectl apply -f app.yaml
# 3. 观察
kubectl get pods -w
kubectl rollout status deploy/web
# 4. 升级
kubectl set image deploy/web web=reg.local/myapp:v1.1
kubectl rollout undo deploy/web # 有问题秒回滚
# 5. 对外暴露
kubectl apply -f ingress.yaml
curl http://antidebug.cn/
五、学习建议
- 先用
kind/minikube起个单机集群练手:kind create cluster - 把上面每个对象各写一遍 YAML,
describe看事件 - 别背概念,盯住一条流量路径:Ingress → Service → Pod → 容器端口
- 排障三板斧:
describe(Events 段)、logs、exec
小结
| 对象 | 一句话 |
|---|---|
| Pod | 最小调度单元,易逝 |
| Deployment | 副本管理 + 滚动升级 |
| Service | 稳定 VIP + 负载均衡 |
| Ingress | 域名/路由/TLS 的七层入口 |
| ConfigMap/Secret | 配置与密钥 |
| 声明式 | 说"要什么",控制器负责"怎么做" |
本文是「云原生」系列第 4 篇,下篇动手把应用部署进 K8s。