说实话,刚接触 Kubernetes (K8s) 的时候,我和你一样,看着那一堆 YAML 文件头都大了。很多人问我:“Agnes,到底怎么从 Docker 平滑过渡到 K8s?微服务治理又是个什么鬼?” 别急,今天咱们就坐下来,像聊家常一样,把这条从容器化到云原生架构落地的完整路径,掰开了、揉碎了讲清楚。
为什么我们要从 Docker 走向 K8s?
首先,得承认 Docker 真的很香。写个 Dockerfile,构建镜像,然后 docker run,搞定。但在生产环境里,事情就开始变复杂了。
假设你的电商系统有十几个微服务:用户中心、订单服务、支付服务、库存服务……如果只用 Docker Compose,你能管理单机上的十几个容器。但一旦你需要水平扩展,比如双11来了,订单服务流量暴涨,你需要10个副本,这时候 Docker 原生的管理方式就显得力不从心了。
K8s 解决的正是这个问题:自动化运维。你不需要手动去服务器上重启容器、监控状态、调整副本数。你只需要告诉 K8s “我想要5个订单服务实例”,它会自己去实现这个目标。
第一步:夯实基础——理解容器与镜像的本质
很多新手跳进 K8s 之前,对 Docker 的理解停留在“打包工具”层面。其实,镜像和容器的关系,就像类和对象的关系。
镜像(Image) 是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量等。 容器(Container) 是镜像的运行实例,是轻量的、隔离的环境。
避坑指南 1:不要忽略基础镜像的选择
初学者最喜欢用 FROM ubuntu 或者 FROM python 这种大镜像。这会导致镜像体积臃肿,启动慢,且安全风险高。
正确做法:使用多阶段构建(Multi-stage Builds)和精简镜像。
# 阶段一:构建
FROM golang:1.21-alpine AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
# 阶段二:运行
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/main .
CMD ["./main"]
这段代码先把 Go 程序编译好,然后只把编译好的二进制文件复制到最小的 Alpine 镜像中。镜像大小从几百 MB 降到了 10 MB 左右,这就是云原生该有的样子。
第二步:K8s 核心概念——不只是 Pod
K8s 的抽象层级有很多,新手容易晕。但其实核心就这几个:
- Pod:K8s 调度的最小单元。一个 Pod 可以包含一个或多个容器,它们共享网络命名空间和存储卷。
- Deployment:管理无状态应用(如 Web 服务器)。它负责创建、更新、扩缩容 Pod。
- Service:暴露 Pod 给外部访问,或者让其他服务访问 Pod。这是服务发现的关键。
- ConfigMap:管理配置信息,将配置与镜像解耦。
- Secret:管理敏感信息(密码、密钥)。
服务发现:Service 是怎么工作的?
在微服务架构中,服务A需要调用服务B。在 Docker 时代,我们可能靠 IP 地址和端口。但在 K8s 里,Pod 的 IP 是动态变化的,重启后就变了,怎么找?
答案就是 Service。
Service 定义了一个稳定的接入点(虚拟 IP,即 ClusterIP),背后代理着一组标签相同的 Pod。当服务B的 Pod 重启或扩缩容时,Service 会自动更新后端列表,对服务A透明。
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service # 选择标签为 order-service 的 Pod
ports:
- protocol: TCP
port: 80 # Service 端口
targetPort: 8080 # Pod 端口
type: ClusterIP # 集群内部访问
服务A只需要访问 http://order-service:80,K8s 就会自动把请求转发到后端的某个健康 Pod 上。这就是服务发现的核心机制。
第三步:配置管理与故障自愈
配置管理:ConfigMap 的最佳实践
不要把配置写死在环境变量里,也不要写进镜像。使用 ConfigMap。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: mysql-service
LOG_LEVEL: "info"
然后在 Deployment 中引用:
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: DATABASE_HOST
这样,修改配置只需要更新 ConfigMap,然后重启 Pod 即可生效(或者通过 Volume 挂载实现热加载,但要注意应用对热加载的支持)。
故障自愈:Health Checks 是基石
K8s 能做到“故障自愈”,前提是你得告诉它什么是“健康”。这依赖两个探针:
- livenessProbe:容器是否活着?如果失败,K8s 会重启容器。
- readinessProbe:容器是否准备好接收流量?如果失败,K8s 会从 Service 的后端列表中移除这个 Pod,但不会重启它。
新手常见错误:只配 liveness,不配 readiness。
想象一下,你的应用启动需要加载大量数据,耗时 30 秒。如果没有 readinessProbe,K8s 可能在应用还没初始化好、还没准备好处理请求时,就把流量转发过去了,导致用户请求失败。
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 10 # 给应用更多时间初始化
periodSeconds: 5
第四步:微服务治理——超越 K8s 原生能力
K8s 的 Service 是 4 层负载均衡,而真正的微服务治理需要 7 层能力:限流、熔断、灰度发布、服务网格等。这时候,我们通常会引入 Istio 或 Linkerd 这样的 Service Mesh(服务网格)方案,或者使用 Ingress Controller(如 Nginx Ingress、Traefik)来处理外部流量。
Ingress:流量的入口
如果你需要基于域名、路径进行路由,或者配置 HTTPS,Ingress 是标准选择。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: shop.example.com
http:
paths:
- path: /orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
- path: /users
pathType: Prefix
backend:
service:
name: user-service
port:
number: 80
这段配置告诉 Ingress Controller:“凡是从 shop.example.com/orders 进来的流量,转给 order-service;凡是从 /users 进来的,转给 user-service。”
灰度发布:逐步切流量
在生产环境,我们不敢一下子把所有流量切到新版本的微服务。这时候,需要 Canary Release(金丝雀发布)。
在 Istio 中,可以通过 VirtualService 和 DestinationRule 配置流量权重:
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: myapp
spec:
hosts:
- myapp.com
http:
- match:
- headers:
end-user:
exact: wangwu # 只有 wangwu 用户去新版本
route:
- destination:
host: myapp.com
subset: v2 # 新版本
- route:
- destination:
host: myapp.com
subset: v1 # 大部分流量去旧版本
weight: 90
- destination:
host: myapp.com
subset: v2
weight: 10 # 10% 流量去新版本
通过这种方式,你可以先让 10% 的流量进入新版本,观察日志和监控指标,如果没有问题,再逐步增加到 50%、100%。这是微服务治理中降低发布风险的核心手段。
第五步:生产环境常见问题与解决方案
即使你学会了上述所有概念,在生产环境中,还是会遇到各种坑。以下是新手最常遇到的几个问题:
问题 1:Pod 一直 Pending,起不来
原因:通常是资源不足或持久卷(PV)未绑定。
排查步骤:
kubectl describe pod <pod-name>查看 Events。- 检查节点是否有足够的 CPU/内存。
- 检查 PVC(持久卷声明)是否绑定到 PV。
解决:
如果是资源不足,可以扩缩容集群节点,或者优化 Pod 的资源请求(requests)和限制(limits)。注意:requests 是调度的依据,limits 是资源使用的上限。设置不当会导致调度失败或容器被 OOM Kill。
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
问题 2:Service 无法访问后端 Pod
原因:Selector 不匹配,或者 Pod 的 readinessProbe 失败。
排查步骤:
- 确认 Service 的
selector和 Pod 的labels完全一致(包括大小写)。 - 检查 Pod 的
Ready状态。如果 Readiness Probe 失败,Pod 虽然运行中,但不会被 Service 路由。
解决:
修正 Label 或调整探针配置。有时候,应用启动慢,探针的 initialDelaySeconds 设置太短,导致应用还没就绪就被判定为不健康。
问题 3:配置更新后,Pod 没有生效
原因:ConfigMap 更新后,挂载的 Pod 不会自动重启。
解决:
- 如果通过环境变量引用 ConfigMap,必须滚动重启 Deployment:
kubectl rollout restart deployment/<name>。 - 如果通过 Volume 挂载 ConfigMap,可以在应用层面支持热加载(如 Nginx 的
reload命令),或者修改subPath挂载方式,并配合脚本监听文件变化。
问题 4:网络策略(NetworkPolicy)导致连接被拒
原因:默认情况下,K8s 集群内 Pod 之间是可以相互通信的。但一旦启用了 NetworkPolicy(安全加固常见做法),如果没有明确允许,所有通信都会被拒绝。
排查步骤: 检查集群是否启用了 NetworkPolicy,以及你的 Pod 是否有对应的 Ingress/Egress 规则。
解决: 编写合适的 NetworkPolicy,允许必要的流量。例如,允许前端 Pod 访问后端 Pod:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-backend
spec:
podSelector:
matchLabels:
app: backend
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- port: 8080
给新手的最后建议:别急,一步一步来
- 本地环境先行:先安装 Minikube 或 Kind,在一个本地集群里折腾,不要直接上生产。
- 理解 YAML,但不要死记:学会用
kubectl explain来理解每个字段的作用,比背文档管用得多。 - 监控和日志是生命线:部署 Prometheus + Grafana 监控资源,部署 ELK 或 Loki 收集日志。没有可观测性,云原生就是瞎子摸象。
- 拥抱 GitOps:使用 ArgoCD 或 Flux,将 K8s 的声明式配置版本化管理。代码即配置,变更即发布。
云原生架构的学习曲线确实陡峭,但一旦你理解了“声明式 API”和“控制回路”的核心思想,你会发现,K8s 其实是在帮你做你原本需要手动做的所有运维工作。它不会让事情变得更简单,但会让事情变得更可控、更自动化、更可靠。
希望这篇指南能帮你理清思路。记住,实践出真知,多动手,多报错,多排查,你很快就能从“K8s 小白”变成“云原生专家”。加油!
