Cloud Native Architecture · 图解走查 · 10 figures

现代互联网服务部署是怎么跑起来的:10 张图

没有轻量虚拟机,只有受限的普通进程。从 Linux 内核 Cgroups 隔离,到 K8s 声明式控制循环自愈,再到 Ingress 流量穿透与 GitOps 对齐——看机制与数据流,不读枯燥定义。

读者
后端工程师 / 架构师 / SRE
视角
内核隔离 / 状态机自愈 / 网络拓扑
读法
架构图 → 边注 → 下一图
图例
控制循环 / 异常 · 流量路径 · ─ 挂载 · ┄ 期望状态
01

容器本质:没有轻量虚拟机,只有受限的普通进程

容器不是虚机,没有 Guest OS。它是通过 Linux Namespaces 隔开视野、Cgroups 戴上资源紧箍咒、Seccomp 限制系统调用的普通宿主机进程。

Fig 01Namespaces (视图隔离) + Cgroups (资源配额) + Seccomp (系统调用白名单)
宿主机物理机 / 节点操作系统 (Linux Kernel 6.x · 单一共享内核) 容器 A 边界 (普通进程 PID 1402, 容器内视角 PID 1) [Namespaces 视图隔离] pid: 独立进程树 · net: 独立网卡/路由 · mnt: 独立挂载根 ipc: 独立信号量 · uts: 独立主机名 · user: 独立UID [Cgroups 资源配额限制] cpu.max: 200000 100000 (限配 2 核, 超出则 CFS 强行节流) memory.max: 512MiB (达到阈值触发 OOM Killer 强杀) [Seccomp BPF 系统调用白名单] 仅放行 read/write/epoll 等 60 种调用,严禁 reboot/ptrace 宿主机 Linux 内核进程调度器全局视野 PID 1: systemd (宿主机 init) 拥有所有硬件真实控制权 PID 1402: /app/server (即容器 A 主进程) 内核调度视角下就是一个普通 fork() 出来的 task_struct PID 1590: nginx-worker (另一容器或宿主机进程) 共享同一 CPU L3 Cache 与内核页缓存 内核结论: 容器启动 = 毫秒级进程创建,零虚拟化开销 ■ STATUS 宿主机执行 clone(CLONE_NEWPID | CLONE_NEWNET),毫秒级拉起受限隔离进程 Namespace: 隔离完成 | Cgroups: 512MB限制生效 | 状态: 正常
启动速度容器启动仅需调用一次系统调用 clone(),毫秒级就绪;而虚机需要载入完整操作系统内核。
性能损耗CPU 与内存指令直接裸跑在物理 CPU 上,损耗接近 0%;开销仅体现在跨虚拟网桥的网络报文路由。
致命风险所有容器共享同一个 Linux 宿主机内核!一旦内核存在提权漏洞(如脏牛、Dirty Pipe),容器即被彻底击穿。
02

多阶段构建:将编译器关在门外,只留 16MB 运行时

生产镜像绝不能包含 gcc、npm、源码与构建缓存。利用 Docker 多阶段构建,只把编译完成的二进制文件带入 Distroless 超轻镜像。

Fig 02Stage 1 (Builder: 1.2GB) ➔ COPY --from=builder ➔ Stage 2 (Distroless: 16MB)
Stage 1: 构建编译环境 (Builder) FROM golang:1.24-alpine AS builder (体积: 1.25 GB) 1. 安装依赖: apk add gcc git make 2. 复制源码: COPY go.mod main.go . 3. 静态编译: CGO_ENABLED=0 go build ➔ 生成单一静态纯二进制: /app/server (16 MB) 剥离全部符号表与调试信息 (ldflags="-s -w") 构建阶段临时产物(将在交付后被全盘丢弃) COPY --from Stage 2: 生产精炼发布镜像 (Production) FROM gcr.io/distroless/static:nonroot (极简 16 MB) COPY --from=builder /app/server /server 唯一的交付实体:仅携带编译后的二进制代码 [安全基线攻防对照表] ✔ 无 Shell (/bin/sh, /bin/bash): 阻断 99% 命令注入反弹 ✔ 无包管理器 (apt, apk): 攻击者无法下载渗透工具 ✔ 非 root 运行 (USER nonroot:nonroot): 杜绝内核越权 ✔ 零依赖攻击面: CVE 漏洞暴露面暴跌 98% 镜像最终体积: 16.4 MB (原镜像 1.25 GB, 精简 98.7%) ■ STATUS Stage 1 完成二进制编译,提取极简产物跨阶段注入 Distroless 运行时 交付体积: 16.4 MB | 安全基线: Distroless (零Shell)
交付物纯度生产镜像只包含二进制和必要的 CA 证书;把整个 Go/Node 编译套件留在生产镜像里是重大安全过失。
攻击面归零黑客即使通过代码漏洞触发命令执行,因容器内没有 sh,反弹 Shell 脚本将直接报错退出。
排障法则Distroless 容器无法 kubectl exec 进入?利用 Kubernetes 临时调试容器(Ephemeral Containers)挂载调试!
03

控制循环:声明式状态机的无限趋近闭环

Kubernetes 不是命令式执行器。你声明期望状态(Desired),Controller Manager 不断比对实际状态(Actual),无限趋近、故障自愈。

Fig 03Observe (观测) ➔ Diff (对比) ➔ Act (调和) ➔ Desired == Actual
期望状态 (Desired State) YAML 规范存储于 Etcd kind: Deployment replicas: 3 K8s 控制器循环 (Reconciliation Loop) 无限运行: for { observe(); diff(); act(); } 1. Observe: 观测实际 2. Diff: 发现偏离 Δ 3. Act: 调和追平 实际状态 (Actual State) 当前运行健康 Pod 副本数 Pods = 2 发生偏离: 发生节点硬件故障 物理节点运行拓扑演练场 (Worker Nodes) Pod-web-01 (Running) IP: 10.244.1.12 · 节点 Node-A Pod-web-02 (Running) IP: 10.244.2.34 · 节点 Node-B Pod-web-03 (Terminating) 节点故障驱逐 ➔ 控制器触发自动重建 ■ STATUS 观测到实际副本 (2) 小于期望副本 (3),控制器触发扩容事件自愈补齐 Desired: 3 | Actual: 2 | 偏离: -1 | 自愈中
声明式魔力我们从不对系统下达“请启动一台机器”的命令,只提交“无论何时集群必须有 3 个副本”的契约。
自愈闭环无论是节点宕机、内核崩溃还是运维手抖误删,控制循环在秒级内发现 Diff 并自动调度拉起新实例。
脑裂危险如果外部应用有状态(如主从数据库),盲目让控制器拉起新副本可能导致双写脑裂,必须依赖 StatefulSet。
04

Pod 拓扑:共享网络与生命周期绑定的容器共生体

Pod 是最小调度单位,不是单个容器。Pause 基础容器率先启动创建网络栈,主业务容器与 Sidecar 共享 localhost 与存储卷。

Fig 04Pause 基础容器 ➔ 共享 Network Namespace (veth0/lo) ➔ Sidecar 共生通信
Pod 沙箱实体模型 (Pod IP: 10.244.1.65 · 宿主机调度最小原子单位) 1. Pause 基础容器 (C语言) 镜像: registry.k8s.io/pause (仅 300KB) 职责: 调用 clone() 率先占住 Network / IPC Namespace 拥有虚拟网卡: veth0 拥有本地回环: lo (127.0.0.1) 2. 主业务应用容器 (App) 监听端口: 0.0.0.0:8080 运行 Java / Go 核心服务 加入 Pause 的网络栈: --net=container:pause 直接读写本地共享存储卷: /logs 无须配置复杂跨主机路由! 3. Sidecar 辅助容器 (Agent) Fluentbit 日志采集 / Envoy 代理 监听端口: 127.0.0.1:15001 同属同一网络栈: 与主容器无缝回环通信 实时 tail 监听共享卷: /logs/app.log 业务解耦: 运维逻辑彻底外置 Pod 内部高性能 localhost 零损耗通信链路 localhost:8080 内存协议栈直通 (零网络封包损耗) 两容器生命周期绑定 · 共享 IPC 信号量 · 共享 EmptyDir 存储卷 ■ STATUS Pause 容器占位网络命名空间,业务容器与 Sidecar 通过 localhost 内存回环极速通信 Netns: 共享 | 存储卷: /logs 共享绑定 | 状态: 稳态运行
设计哲学不要在一个容器里塞进进程守护和日志采集器;利用 Pod 多容器共生,实现关注点分离。
网络栈共享主业务容器访问 Sidecar,直接调 127.0.0.1,就像访问同一个操作系统的两个本地端口。
端口冲突同一个 Pod 内部的多个容器严禁监听同一端口,否则直接报错 address already in use
05

流量穿透:从 Ingress 域名解析到 Pod 容器网络

外部请求如何精准找到不断漂移的 Pod?Ingress 负责七层域名路由,Service 负责四层 VIP 负载均衡,IPVS 保持百万连接不掉线。

Fig 05Ingress (七层路由) ➔ Service VIP (四层 IPVS) ➔ Pod (Overlay 容器网络)
外部客户端 HTTP Request learn.yishen.uk Ingress Controller Envoy / Nginx 七层路由 Host: learn.yishen.uk Path: /api/* ➔ Svc TLS 证书卸载 · WAF 过滤 Service 虚拟 IP ClusterIP: 10.96.0.10:80 IPVS 内核哈希表 O(1) 复杂度极速寻址 Endpoints 动态保活探活 Pod 1 (10.244.1.20) Port: 8080 · 节点 A 健康健康 Pod 2 (10.244.2.35) Port: 8080 · 节点 B 健康健康 Pod 3 (10.244.3.48) Port: 8080 · 节点 C 健康健康 网络穿透时序: Ingress (L7 TLS/URL) ➔ Service (L4 IPVS DNAT) ➔ CNI (Geneve/VXLAN Overlay 封包) ➔ veth0 实测吞吐: IPVS 模式下支持单节点挂载 100,000+ Services,消除 iptables 线性链表性能雪崩 ■ STATUS 外部请求进入 Ingress 七层网关,解析域名并将流量转发至后端 Service VIP 协议阶段: L7 Ingress ➔ L4 IPVS 负载均衡 ➔ Pod-2
VIP 假象Service IP(ClusterIP)根本不是绑在某块物理网卡上的真实 IP,它只是 IPVS 内核模块里的 NAT 转发规则。
IPVS 优势比起老旧的 iptables 规则链,IPVS 基于 Linux 内核哈希表,即便集群有一万个服务,路由延迟依然维持在常数级 $O(1)$。
Session 黏性如需客户端 IP 保持长连接会话,必须配置 sessionAffinity: ClientIP,否则请求将在多个 Pod 间随机跳跃。
06

存储解耦:声明式 PVC、动态供给与底层 CSI 挂载

业务容器不能把状态存在本地镜像。通过 PersistentVolumeClaim 声明存储需求,StorageClass 驱动底层 CSI 动态供给云盘并精准挂载。

Fig 06Pod ➔ PVC (需求申请) ➔ StorageClass ➔ PV (实际存储卷) ➔ 物理云盘
应用 Pod 挂载点: /data volumes: - name: data-vol claimName: db-pvc 应用只需知晓 PVC 名 零感知底层存储硬件型号 PVC 存储申请书 PersistentVolumeClaim accessModes: - ReadWriteOnce resources: 100Gi storageClass: ssd StorageClass 供给器 CSI 自动化接口标准 provisioner: ebs.csi.aws.com ➔ 自动化创建 PV 调用云 API 分配磁盘 Attach 到当前 Worker 节点 PV 物理持久卷 PersistentVolume volId: vol-08f321 capacity: 100Gi status: Bound 底层物理介质: SSD NVMe 云存储池 存储生命周期解耦: Pod 即使因故障被漂移调度到新节点,CSI 自动完成磁盘卸载 (Detach) 与新节点重新挂载 (Attach) 开发只需提交 PVC 声明,运维定义 StorageClass 策略,实现开发与基础设施彻底解耦 ■ STATUS Pod 启动声明 100Gi PVC 申请,StorageClass 调用 CSI 驱动动态供给云磁盘 存储状态: Bound (已绑定) | 挂载点: /data | 容量: 100Gi
解耦契约应用开发者只需在 yaml 里写“我要 100G 磁盘”,根本不用关心底层是 Ceph、AWS EBS 还是阿里云 NAS。
自动漂移当 Pod 所在节点宕机,调度器在 Node 2 启动 Pod,CSI 自动把磁盘从老节点拔下并插到新节点,数据完好无损。
访问模式ReadWriteOnce(RWO)只允许单节点独占挂载;若多 Pod 需并发读写,必须选用支持 ReadWriteMany(RWX)的文件存储。
07

Helm 参数化:用一套模板消灭多环境的配置漂移

拒绝为 dev/staging/prod 各维护一套几千行的 YAML。Helm 用 Go 模板将配置与架构骨架分离,一套 Chart 参数化渲染全场景。

Fig 07Chart Template (架构蓝图) + values-prod.yaml (环境变量) ➔ Helm Render ➔ K8s Manifest
Chart 架构模板 (deployment.yaml) apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }} spec: replicas: {{ .Values.replicaCount }} containers: image: {{ .Values.image.tag }} 生产环境入参: values-prod.yaml replicaCount: 10 image: tag: "v2.4.0" resources: limits.cpu: 4000m limits.memory: 8Gi 环境差异完全收敛在单文件 Helm 引擎渲染交付结果 (Manifest) metadata.name: order-prod replicas: 10 image: myapp:v2.4.0 resources: cpu: 4000m memory: 8Gi ✔ 准备注入 APIServer 版本可回滚控制: helm upgrade / helm rollback · 一键回退至任意历史发布版本 revision 消灭 Copy-Paste 陷阱: 研发团队只维护一个 Chart,开发、测试、预发、生产四套环境参数化按需渲染 ■ STATUS 注入 values-prod.yaml 参数,动态替换模板占位符,渲染生产发布清单 环境: PROD | 副本: 10 | 镜像版本: v2.4.0
版本控制Helm 将每次发布记录为一个 Revision,如果上线故障,执行 helm rollback 2 秒级回滚。
依赖编排通过 Chart.lock 声明外部依赖(如 Redis、PostgreSQL),一键拉起整套微服务拓扑。
反模式切忌在模板里写过度复杂的 if/else 流程控制,这会把 Kubernetes YAML 变成谁也看不懂的天书。
08

GitOps 持续交付:单向真理源与声明式自动同步

严禁工程师直接 kubectl apply 生产集群。Git 仓库是集群状态的唯一真理源,ArgoCD 自动探测 Git 与集群的漂移并平滑对齐。

Fig 08Git Commit (单一真理源) ➔ ArgoCD 对比 ➔ 自动同步 ➔ 集群健康
Git 仓库 (Single Source of Truth) github.com/myorg/k8s-manifests Commit: 8f419c feat: bump image to v2.4.0 期望规范定义: image: v2.4.0 replicas: 4 ArgoCD 驱动控制器 (Pull Model) 集群内运行 · 周期比对 Git 差异 Diff 检测: OutOfSync (偏离) Git(v2.4.0) != Cluster(v2.3.9) 同步动作: 触发声明式滚动升级 自动对齐 · 禁止任何人直接修改集群 如有非法运维篡改,立即自动覆写还原 生产 Kubernetes 集群 实际运行工作负载 (Live State) Running: v2.3.9 正在执行零宕机滚动对齐 审计记录: 变更完全由 Git PR 审查驱动 杜绝不可审计的黑盒命令操作 GitOps 核心戒律: 集群凭据永远不需要暴露给外部 CI 管道,由集群内部控制器主动向 Git 拉取 (Pull 模型) 灾难恢复: 若整个集群机房损毁,新开空集群指向 Git 仓库,10 分钟内全量原地满血复活! ■ STATUS 开发者合并代码提交至 Git,ArgoCD 检测到 OutOfSync 偏离并触发全自动平滑同步 状态: Syncing ➔ Healthy | Commit: 8f419c
安全防线再也不用把生产集群的 kubeconfig 秘钥上传给 GitHub Actions 等第三方 CI 平台。
防止漂移如果有人在生产环境用 kubectl edit 偷改副本数,ArgoCD 秒级检测并强制刷回 Git 里的版本。
灾难恢复集群全炸了?重新申请一个空集群,只要 argocd app create 指向 Git,所有服务一键自愈。
09

可观测三支柱:指标、日志与分布式追踪的黄金交汇

系统变慢时,日志是大海捞针。通过 Trace ID 将 Prometheus 聚合指标、Loki 结构化日志与 Jaeger 跨服务调用链彻底串联。

Fig 09Metrics (指标警报) ➔ Traces (调用拓扑定位) ➔ Logs (精准取证)
1. Metrics 聚合指标 (Prometheus) 宏观感知: 系统正在发生什么异常? P99 延迟暴增: 2.85s (报警!) PromQL: histogram_quantile(0.99...) 特点: 存储极轻、支持秒级多维报警 2. Traces 分布式链路追踪 (Jaeger) 拓扑定位: 慢在哪个微服务瓶颈点? Ingress: 2850ms order-service: 2800ms pay-service: 2400ms (阻塞!) 精准锁定瓶颈: pay-service 阻塞 TraceID: 8f4a10c9e781b2 3. Logs 结构化精准日志 (Loki) 病因根因取证: 代码究竟因何报错? {level: "ERROR"} service: "pay-service" trace_id: "8f4a10c9e..." Bank Gateway Timeout (504) 下游银行接口连接超时 通过 TraceID 直击崩溃现场 无需在数十亿行日志中盲目 grep 可观测黄金闭环: Prometheus 触发告警 ➔ 1 秒点入 Jaeger 找到最长耗时红色 Span ➔ 顺藤摸瓜点出该 Span 的具体错误日志 OpenTelemetry 时代终极准则: 全链路打透 traceparent 报文头,消灭微服务内部信息孤岛 ■ STATUS Prometheus 触发 P99 告警,Jaeger 锁定 pay-service 耗时 2400ms,Loki 精准打印下游银行接口超时 TraceID: 8f4a10c9e | 瓶颈: pay-service (2.4s) | 根因: Gateway 504
指标告警不要拿日志做实时告警(太昂贵太迟缓);让 Prometheus 盯住 RED 黄金指标(Rate, Errors, Duration)。
Trace 串联全链路服务间调用必须在 HTTP 请求头透传 traceparent,否则调用链在网关直接断裂。
采样成本高并发场景千万不要 100% 全量采样 Trace!配置动态概率采样(如 1%~5%),否则存储账单将超过计算集群本身。
10

生产排障:SRE 黄金决策树与四类致命故障速查

线上告警时慌乱乱改只会酿成二次灾难。遵循 SRE 标准处置流:区分 CrashLoopBackOff、OOMKilled、Pending 与网络丢包,按部就班快速止血。

Fig 10生产高频致命故障 SRE 黄金处置路径
1. CrashLoopBackOff Exit Code: 1 或 2 [典型根因] 配置缺失 / 环境变量为空 数据库连接串打错无法建连 探针 livenessProbe 误杀 [SRE 黄金命令] kubectl logs --previous kubectl describe pod 处置: 修正 ConfigMap 立即止血 2. OOMKilled 内存强杀 Exit Code: 137 (128+9) [典型根因] 内存泄漏 (Memory Leak) JVM 未感知 Cgroup 限制 突发大请求撑爆本地缓冲区 [SRE 黄金命令] dmesg -T | grep oom 提升 limits.memory 配额 处置: 临时上调配额,抓取堆转储 3. Pending 无法调度 Status: 0/N nodes available [典型根因] 集群 CPU/内存资源超卖耗尽 节点污点未容忍 (Taints) PVC 所在可用区与机器不匹配 [SRE 黄金命令] kubectl get events kubectl top nodes 处置: 触发 Cluster Autoscaler 扩机 4. 502 / DNS 解析超时 i/o timeout · 502 Bad Gateway [典型根因] CoreDNS 副本不足被打满 Linux conntrack 连接表被刷爆 应用优雅关闭未做,粗暴退役 [SRE 黄金命令] kubectl get endpoints 扩容 CoreDNS 副本 处置: 开启 NodeLocal DNSCache SRE 铁律: 优先止血(降级、扩容、回滚),严禁在线上直接打断点调试!一切排障动作以恢复业务为第一准则。 ■ STATUS 生产排障快速定位中: 遇到 Exit Code 137,立即判定为 Cgroup 内存强杀 OOMKilled 当前场景: OOMKilled (Exit 137) ➔ 止血方案: 临时提权配额并抓取堆快照
Exit 137137 = 128 + 9(SIGKILL),说明进程是被操作系统内核因 OOM 强行拍死的,不要去查应用逻辑。
Exit 143143 = 128 + 15(SIGTERM),说明是 Kubernetes 主动通知优雅退出,多半是因为驱逐或滚动更新。
探针误区如果 livenessProbe 设置的超时时间比垃圾回收(GC)停顿时间还短,容器会被探针无限循环误杀!
云原生部署架构图解 · 10 figures · 离线单文件 空白键 = 暂停/继续当前图 · 每图可单步与重播