一、起步:为什么我们要折腾这些?
想象一下,你写了一个很棒的“待办事项”应用。最初,它运行在一台服务器上,所有代码都在一个文件夹里,数据库也在旁边。这叫单体应用。
有一天,应用火了,用户多了,问题来了:
- 修一个Bug可能要部署整个应用,其他功能也得陪着重启。
- 想加个“支付”功能,就得拉上整个团队改代码,协调成本极高。
- 服务器突然扛不住,你得整台机器扩容,哪怕只是“待办事项”模块需要更多资源。
这时候,你开始思考:能不能把大应用拆成小服务,各自独立开发、部署、扩展?
这就是微服务的雏形。而容器化(比如Docker)和Kubernetes(K8s),就是实现和驾驭这种架构的利器。
别担心,我们从头聊起,一步步构建你的云原生知识体系。
二、容器化:给你的应用一个“标准化包裹”
在容器出现之前,应用部署是个坑。
- 开发说:“在我机器上是好的!”
- 运维说:“生产环境配置跟你不一样。”
什么是容器?
容器就像是一个轻量级的“虚拟环境”。它把应用代码、运行依赖、系统工具、库文件统统打包在一起。不管在谁的机器上跑,表现都一致。
比喻:以前你搬家,家具、衣服、书分开运,容易丢。现在你用一个个标准集装箱,把 everything 封好,整箱搬运,直接吊装到船/卡车/火车上,开箱即用。
Docker:容器的代表
Docker 是最流行的容器化工具。核心概念有三个:
- 镜像(Image):只读的模板,包含运行应用所需的一切。比如
nginx:latest。 - 容器(Container):镜像的运行实例。可以有多个容器跑同一个镜像。
- 仓库(Registry):存放镜像的地方,比如 Docker Hub。
实战:Dockerfile 怎么写?
假设你的“待办事项”应用是用 Python 写的,依赖 flask 和 redis。
创建一个文件 Dockerfile(无后缀):
# 1. 基础镜像:选择官方 Python 镜像,指定版本
FROM python:3.9-slim
# 2. 设置工作目录
WORKDIR /app
# 3. 复制依赖文件并安装依赖
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 4. 复制应用代码
COPY . .
# 5. 暴露端口(只是声明,不实际转发)
EXPOSE 5000
# 6. 启动命令
CMD ["python", "app.py"]
构建并运行:
# 构建镜像
docker build -t todo-app:v1 .
# 运行容器
docker run -d -p 5000:5000 --name my-todo todo-app:v1
看,你的应用就被“包裹”起来了,在任何支持 Docker 的机器上都能以相同方式运行。
三、微服务架构:拆而不散,合而有序
微服务是什么?
微服务架构是一种设计风格,它将单一应用程序划分为一组小型服务:
- 每个服务运行在独立的进程中,轻量级通信(通常是 HTTP/REST 或 gRPC)。
- 服务围绕业务能力组织,而不是技术分层。
- 每个服务可以独立部署、独立扩展、独立数据库。
以“待办事项”为例拆分
| 服务 | 职责 | 可能技术 |
|---|---|---|
user-service |
用户注册、登录、权限 | Node.js, MySQL |
todo-service |
待办事项的增删改查 | Python, Redis |
notification-service |
发送邮件/短信提醒 | Go, RabbitMQ |
api-gateway |
统一入口,路由请求,鉴权 | Nginx, Kong |
微服务带来的挑战
拆得越好,管理越难:
- 服务多了,怎么发现彼此?
- 调用链断了,怎么排查?
- 一个服务挂了,怎么容错?
- 几百个服务,怎么部署、升级、回滚?
这时候,Kubernetes 登场了。
四、Kubernetes(K8s):微服务的“自动驾驶仪”
K8s 是一个开源的容器编排平台,由 Google 开源,现在由 CNCF 托管。它的核心任务是:让部署、扩展和管理容器化应用变得简单而高效。
K8s 核心概念速览
1. Pod:最小的调度单元
Pod 是 K8s 里你可以创建和管理的最小单位。一个 Pod 可以包含一个或多个容器(通常是业务容器 + 侧车容器,如日志采集)。
比喻:Pod 是一个小家庭,容器是家庭成员。他们共享网络命名空间和存储卷,紧密协作。
2. Deployment:管理无状态服务
如果你想运行一个“待办事项”服务,有多个副本(比如 3 个),用 Deployment 管理。
# todo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-deployment
spec:
replicas: 3
selector:
matchLabels:
app: todo
template:
metadata:
labels:
app: todo
spec:
containers:
- name: todo-container
image: todo-app:v1
ports:
- containerPort: 5000
应用这个配置:
kubectl apply -f todo-deployment.yaml
K8s 会自动:
- 创建 3 个 Pod,每个跑一个
todo-app:v1容器。 - 如果某个 Pod 挂了,K8s 会自动重启或重建。
- 如果你需要扩容到 5 个副本,只需
kubectl scale deployment todo-deployment --replicas=5。
3. Service:让 Pod 可被发现
Pod 的 IP 是动态变化的,怎么访问?用 Service。
Service 提供稳定的网络入口,将流量转发到后端的多个 Pod。
# todo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: todo-service
spec:
selector:
app: todo
ports:
- protocol: TCP
port: 80 # Service 端口
targetPort: 5000 # Pod 容器内端口
type: ClusterIP # 集群内访问
其他服务(如 notification-service)只需访问 http://todo-service:80,不用关心背后是哪个 Pod。
4. ConfigMap 和 Secret:配置分离
不要把密码、API Key 硬编码在镜像里!用 ConfigMap 存配置,Secret 存敏感信息。
# todo-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: todo-config
data:
REDIS_HOST: "redis-service"
LOG_LEVEL: "info"
# todo-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: todo-secret
type: Opaque
data:
DB_PASSWORD: cGFzc3dvcmQxMjM= # base64 编码的 "password123"
在 Deployment 中使用:
envFrom:
- configMapRef:
name: todo-config
- secretRef:
name: todo-secret
5. Ingress:对外暴露服务
上面的 Service 是 ClusterIP,只在集群内部可访问。要让外网能访问,需要 Ingress。
Ingress 是 K8s 的 HTTP/HTTPS 路由层,配合 Ingress Controller(如 Nginx Ingress Controller)实现。
# todo-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: todo-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: todo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: todo-service
port:
number: 80
用户访问 http://todo.example.com 时,流量被路由到 todo-service。
五、实战:构建一个完整的云原生微服务应用
让我们把以上知识串联起来,构建一个简化的“待办事项系统”。
架构设计
用户
|
v
Ingress (todo.example.com)
|
v
API Gateway (Kong / Nginx)
|
+---> todo-service (Deployment: 3 replicas, Service: ClusterIP)
| |
| +---> redis-service (Deployment: 1 replica, Service: ClusterIP)
|
+---> user-service (Deployment: 2 replicas, Service: ClusterIP)
|
+---> mysql-service (Deployment: 1 replica, Service: ClusterIP)
步骤 1:创建命名空间(可选,但推荐)
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: todo-app
kubectl apply -f namespace.yaml
步骤 2:部署 Redis
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: todo-app
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "200m"
---
# redis-service.yaml
apiVersion: v1
kind: Service
metadata:
name: redis-service
namespace: todo-app
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
kubectl apply -f redis-deployment.yaml
步骤 3:部署 Todo Service
# todo-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: todo-config
namespace: todo-app
data:
REDIS_HOST: "redis-service"
REDIS_PORT: "6379"
LOG_LEVEL: "info"
---
# todo-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: todo-secret
namespace: todo-app
type: Opaque
data:
API_KEY: Ym9yZWNoeQ== # base64("borechee")
---
# todo-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-deployment
namespace: todo-app
spec:
replicas: 3
selector:
matchLabels:
app: todo
template:
metadata:
labels:
app: todo
spec:
containers:
- name: todo-container
image: todo-app:v1
ports:
- containerPort: 5000
envFrom:
- configMapRef:
name: todo-config
- secretRef:
name: todo-secret
resources:
requests:
memory: "128Mi"
cpu: "200m"
limits:
memory: "256Mi"
cpu: "500m"
readinessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 5000
initialDelaySeconds: 15
periodSeconds: 20
---
# todo-service.yaml
apiVersion: v1
kind: Service
metadata:
name: todo-service
namespace: todo-app
spec:
selector:
app: todo
ports:
- port: 80
targetPort: 5000
kubectl apply -f todo-configmap.yaml
kubectl apply -f todo-secret.yaml
kubectl apply -f todo-deployment.yaml
kubectl apply -f todo-service.yaml
步骤 4:配置 Ingress
假设你已经部署了 Nginx Ingress Controller(通常云厂商提供,或手动安装)。
# todo-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: todo-ingress
namespace: todo-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: todo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: todo-service
port:
number: 80
kubectl apply -f todo-ingress.yaml
步骤 5:验证
# 查看所有资源
kubectl get all -n todo-app
# 查看 Pod 状态(确保 Running)
kubectl get pods -n todo-app
# 查看 Service 端点
kubectl get endpoints -n todo-app
# 测试 API(如果你有 Ingress 的公网 IP)
curl http://todo.example.com/api/todos
六、进阶:可观测性与弹性
1. 日志:谁在干什么?
K8s 本身不管理日志,但你可以用:
- sidecar 模式:在同一个 Pod 里跑一个日志采集容器(如 Fluentd)。
- 集中式日志系统:如 EFK(Elasticsearch + Fluentd + Kibana)或 Loki + Grafana。
2. 监控:健康吗?负载高吗?
- Prometheus:指标采集和存储。
- Grafana:可视化展示。
- K8s Metrics Server:提供 Pod 和节点的资源使用情况。
安装 Prometheus + Grafana 的常用方法是使用 Helm Chart。
3. 弹性:自动扩缩容
HPA(Horizontal Pod Autoscaler)根据 CPU/内存或自定义指标自动调整副本数。
# hpa.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: todo-hpa
namespace: todo-app
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: todo-deployment
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 50 # CPU 使用率超过 50% 时扩容
kubectl apply -f hpa.yaml
当负载增加,K8s 自动将 todo-deployment 的副本数从 3 增加到 5、6……负载降低时又缩回去。
4. 服务网格:更细粒度的控制
当服务越来越多,服务间调用变得复杂。服务网格(如 Istio、Linkerd)提供:
- 流量管理(灰度发布、A/B 测试)
- 安全(mTLS 加密)
- 可观测性(调用链追踪)
- 熔断、重试、超时控制
Istio 的工作方式是在每个 Pod 里注入一个 Envoy 代理(sidecar),所有流量都经过它。
七、设计原则:如何正确设计云原生应用
1. 12-Factor App
这是云原生应用设计的经典原则,核心包括:
- 代码库:一份代码,多环境部署。
- 依赖:显式声明依赖。
- 配置:配置存入环境变量,不存入代码。
- 后端服务:将后端服务视为附加资源。
- 构建、发布、运行:严格分离。
- 进程:无状态,易伸缩。
- 端口绑定:应用通过端口提供服务。
- 并发:通过进程模型扩展。
- 易处理:快速启动、优雅终止。
- 开发/生产等价:最大程度保持一致。
- 日志:视为事件流。
- 管理进程:一次性任务作为独立进程运行。
2. 无状态设计
每个服务实例应该是无状态的(不保存本地状态)。状态交给外部存储(Redis、数据库)。这样任何实例都可以随时被替换或扩容。
3. 故障隔离
单个服务故障不应拖垮整个系统。使用:
- 熔断器:调用失败时快速失败,避免级联故障。
- 超时控制:不要无限等待。
- 重试策略:有退避的重试。
4. 渐进式交付
不要一次性全量发布。使用:
- 蓝绿部署:同时运行两个环境,切换流量。
- 金丝雀发布:先让一部分用户试用新版本,没问题再全量。
K8s 本身不直接支持这些,但配合 Argo Rollouts、Flagger 等工具可以实现。
