6.2 Kubernetes 容器编排入门
预计阅读时间:14 分钟
📖 目录
学习目标
- 理解 Kubernetes 集群架构:Control Plane 各组件职责、Worker Node 角色
- 掌握核心工作负载资源:Pod、Deployment、Service、Ingress 的编写与操作
- 学会使用 ConfigMap / Secret 管理配置,用 Helm 部署复杂应用
- 能够在单机用 K3s 搭建 K8s 环境并部署应用
- 了解常见错误模式与生产最佳实践
核心知识
本章建立在 6.1:容器底层原理 容器底层原理 之上——K8s 本质是 container runtime(containerd)的上层编排引擎。后续可衔接 6.7:GitOps 入门 GitOps 入门,用 ArgoCD/Flux 实现 Git 驱动的自动化部署。
| 概念 | 一句话 | 关联资源 |
|---|---|---|
| Pod | 最小调度单元,一个或多个共享网络的容器 | Deployment / StatefulSet / DaemonSet |
| Deployment | 声明式管理 Pod 副本数与滚动更新 | ReplicaSet(自动管理) |
| Service | Pod 的稳定网络入口 + 负载均衡 | ClusterIP / NodePort / LoadBalancer |
| Ingress | 七层 HTTP/HTTPS 路由到 Service | Ingress Controller(nginx / traefik) |
| ConfigMap / Secret | 将配置从镜像中解耦 | Pod 通过 env / volume 注入 |
| Helm | K8s 包管理器,Chart = YAML 模板 + 参数 | values.yaml + 模板渲染 |
| K3s | 轻量 K8s(<100MB),适合单机/边缘 | 默认 SQLite 存储(可换 etcd)+ containerd |
知识关联
- 前置知识:6.1:容器底层原理 容器底层原理(Pod 依赖 namespace/cgroups)、3.1:Docker 容器入门 Docker 容器入门(容器镜像与运行时基础)
- 后续影响:6.7:GitOps 入门 GitOps 入门(ArgoCD/Flux 在 K8s 上实现 Git 驱动部署)、4.9:CI/CD 持续部署 CI/CD 持续部署(K8s 是 CI/CD 的常见目标平台)
- 配套技术:K3s 轻量 K8s 适合单机学习,Helm 包管理器简化应用部署,Prometheus 是 K8s 生态标准监控方案
原理讲解
为什么 K8s 要用声明式 API 而不是命令式
K8s 采用声明式 API(你声明期望状态,系统自动收敛)而非命令式(你告诉系统每一步做什么),这个设计决策解决了分布式系统的核心难题:并发控制和状态一致性。在命令式模型中,如果你告诉系统"创建 3 个 Pod",系统执行后某个 Pod 崩溃,系统不知道应该重建——因为"创建 3 个 Pod"是一个已完成的动作。在声明式模型中,你声明"始终运行 3 个 Pod",控制器持续监控实际状态与期望状态的差异,Pod 崩溃后自动重建。这种"Reconcile Loop(调谐循环)"让 K8s 具备了自愈能力——你只需声明意图,系统负责实现。
声明式 API 的另一个优势是幂等性:多次 apply 同一个 YAML 文件,结果相同。这简化了 GitOps 工作流——Git 中的 YAML 文件是唯一的真相来源,任何偏离都会被自动修正。命令式操作(如 kubectl run)则不具备幂等性,重复执行会创建多个资源。
控制器循环(Reconcile Loop)如何工作
K8s 的核心架构是"控制循环":控制器持续观察实际状态,将其与 etcd 中存储的期望状态对比,执行操作缩小差距。例如 Deployment Controller 观察到期望 3 个 Pod 但实际只有 2 个(某 Pod 崩溃),立即创建新 Pod;Node Controller 观察到某节点心跳超时,驱逐该节点上的所有 Pod。这个循环永不停止,确保系统始终收敛到期望状态。控制器之间通过 API Server 通信,从不直接读写 etcd——这简化了访问控制和审计,但也意味着 API Server 是集群的单点瓶颈。
为什么推荐用 etcd 而不是 Consul
etcd 和 Consul 都是分布式 KV 存储,但设计目标不同。etcd 专为 Kubernetes 设计:强一致性(Raft 算法)、高可用(奇数节点集群)、简单的 API(PUT/GET/DELETE + Watch)。Consul 功能更丰富(内置服务发现、健康检查、多数据中心),但复杂度更高。K8s 选择 etcd 的原因:① etcd 的 Watch 机制天然适合 K8s 的事件驱动架构(API Server 通过 Watch 推送变更给控制器);② etcd 的 Raft 实现简单可靠,适合存储集群核心状态;③ etcd 的性能在小数据量(K8s 集群状态通常 <1GB)下足够好。对于纯服务发现场景(非 K8s),Consul 的内置功能更完整。
K8s 集群分为控制面(Control Plane)和工作节点(Worker Node)。控制面决定"做什么",工作节点负责"执行"。
Control Plane 核心组件:
- API Server(kube-apiserver)——集群唯一入口,提供 REST API 和认证授权。etcd 是它的后端存储,所有组件通过 API Server 通信,从不直接读写 etcd。
- etcd——分布式键值存储,保存集群全部状态(Pod 调度信息、Secret、ConfigMap 等)。唯一有状态组件,必须定期备份。
- Scheduler(kube-scheduler)——为新 Pod 选择合适的节点。调度算法考虑资源请求、亲和性、污点容忍、数据本地性等。
- Controller Manager——运行一系列控制循环(Deployment Controller、Node Controller、Endpoint Controller 等),确保实际状态收敛到期望状态。
Worker Node 核心组件:
- kubelet——节点上的代理,接收 API Server 指令并管理 Pod 生命周期(启停、健康检查)。
- kube-proxy——维护节点 iptables/IPVS 规则,实现 Service 的负载均衡和网络路由。
- Container Runtime——实际运行容器的底层引擎(containerd / CRI-O)。
# 控制面与工作节点交互流程
# 1. kubectl apply -f deployment.yaml → API Server
# 2. API Server 写入 etcd
# 3. Controller Manager 检测到期望状态,创建 ReplicaSet
# 4. Scheduler 为新 Pod 选择合适的 Worker Node
# 5. 目标节点的 kubelet 收到指令 → 调用 Container Runtime 启动容器
# 6. kube-proxy 更新 Service 规则,新 Pod 开始接收流量
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d).db。无备份=无灾备。调度原理与 Pod 生命周期
理解"一个 Pod 从提交到运行经历了什么",是排查一切 K8s 问题的根基:
- Pod 创建:
kubectl apply→ API Server 校验并写入 etcd(Pod 状态Pending) - 调度(Scheduling):Scheduler 从队列中取 Pod,执行"过滤(Feasibility)→ 打分(Scoring)"两阶段。过滤阶段排除资源不足、taint 不匹配的节点;打分阶段按资源利用率、亲和性、反亲和性等策略排序,选出最优节点。节点亲和性(nodeAffinity)、Pod 反亲和性(podAntiAffinity)和污点(taint)是三个最常用的调度控制手段
- kubelet 执行:节点 kubelet 观察到 Pod 绑定到本节点 → 调用 CRI(containerd)创建容器 → 执行 initContainer → 启动主容器(状态
Running) - 探针与就绪:livenessProbe 失败 → 重启容器;readinessProbe 失败 → 从 Service Endpoints 摘除(不接流量)
- 终态:任务完成(
Completed)或节点故障被驱逐(Evicted)→ 控制器重建(Deployment 保证副本数)
# 观察调度决策(Events 中能看到 Scheduler 的选择)
kubectl describe pod nginx-deploy-xxxxx | sed -n '/Events:/,$p'
# 输出:
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal Scheduled 12s default-scheduler Successfully assigned ... to node1
# Normal Pulling 11s kubelet Pulling image "nginx:alpine"
# Normal Started 9s kubelet Started container nginx
# 反亲和性示例:同一应用的多个副本尽量分散到不同节点
# affinity:
# podAntiAffinity:
# preferredDuringSchedulingIgnoredDuringExecution:
# - weight: 100
# podAffinityTerm:
# labelSelector:
# matchLabels:
# app: nginx
# topologyKey: kubernetes.io/hostname
示例代码
kubectl 基础操作
kubectl version --client
# 输出: Client Version: v1.30.0
kubectl get nodes
# 输出: NAME STATUS ROLES AGE VERSION
# node1 Ready control-plane 10d v1.30.0
kubectl get pods -A
# 输出: NAMESPACE NAME READY STATUS RESTARTS AGE
# kube-system coredns-5d78c9869d-7j9k2 1/1 Running 0 10d
Deployment YAML
# nginx-deploy.yaml — Deployment 定义了 Pod 的期望状态
apiVersion: apps/v1 # Deployment 属于 apps 组 v1 版本
kind: Deployment # 资源类型:Deployment(管理 ReplicaSet + 滚动更新)
metadata:
name: nginx-deploy
spec:
replicas: 3 # 期望运行 3 个 Pod 副本
selector:
matchLabels:
app: nginx # 必须与 template.metadata.labels 一致
template:
metadata:
labels:
app: nginx # Pod 标签,Service 通过此标签选择 Pod
spec:
containers:
- name: nginx
image: nginx:alpine # 使用 alpine 精简镜像(约 40MB vs 标准 180MB)
ports:
- containerPort: 80
resources:
requests: # 调度器据此预留资源(保证能调度到节点)
cpu: "100m" # 100m = 0.1 核 CPU
memory: "128Mi"
limits: # 运行时上限,超出则 OOMKilled
cpu: "500m"
memory: "256Mi"
terminationGracePeriodSeconds: 30 # 给 Pod 30 秒完成清理后强制终止
strategy:
type: RollingUpdate # 滚动更新策略(默认)
rollingUpdate:
maxUnavailable: 1 # 更新时最多 1 个 Pod 不可用
maxSurge: 1 # 更新时最多多出 1 个 Pod
kubectl apply -f nginx-deploy.yaml
kubectl rollout status deployment/nginx-deploy
# 输出: deployment "nginx-deploy" successfully rolled out
kubectl scale deployment nginx-deploy --replicas=5
kubectl set image deployment/nginx-deploy nginx=nginx:1.25-alpine
kubectl rollout history deployment/nginx-deploy
# 输出: REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
kubectl rollout undo deployment/nginx-deploy --to-revision=1
Service 暴露
# nginx-svc.yaml — Service 为 Pod 提供稳定的网络入口
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
type: ClusterIP # 集群内部访问(默认);其他类型:NodePort(节点端口)、LoadBalancer(云 LB)
selector:
app: nginx # 匹配 Pod 标签,将流量转发到匹配的 Pod
ports:
- port: 80 # Service 暴露端口(客户端访问端口)
targetPort: 80 # Pod 容器监听端口(必须与 containerPort 一致)
kubectl apply -f nginx-svc.yaml
kubectl get svc nginx-svc
# 输出: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# nginx-svc ClusterIP 10.96.123.45 <none> 80/TCP 5s
kubectl expose deployment nginx-deploy --port=80 --type=NodePort --name=nginx-nodeport
kubectl get svc nginx-nodeport
# 输出: NAME TYPE CLUSTER-IP PORT(S) AGE
# nginx-nodeport NodePort 10.96.66.77 80:30123/TCP 5s
Ingress
# example-ingress.yaml — Ingress 定义七层 HTTP/HTTPS 路由规则
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: / # 重写请求路径
spec:
ingressClassName: nginx # 指定 Ingress Controller(必须已安装)
rules:
- host: app.example.com # 域名匹配(HTTP Host 头)
http:
paths:
- path: /api # 路径前缀匹配(pathType: Prefix)
pathType: Prefix
backend:
service:
name: api-svc # 后端 Service 名称
port:
number: 8080 # Service 端口
- path: / # 兜底路由(优先级低于 /api)
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
tls: # HTTPS 配置(需提前创建 TLS Secret)
- hosts:
- app.example.com
secretName: app-tls # 包含 tls.crt 和 tls.key 的 Secret
kubectl get ingress
# 输出: NAME CLASS HOSTS ADDRESS PORTS AGE
# example-ingress nginx app.example.com 80, 443 10s
ConfigMap
# app-config.yaml — ConfigMap 将配置从镜像中解耦(不敏感数据用 ConfigMap,敏感数据用 Secret)
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
# 键值对形式:注入为环境变量
DB_HOST: "db-service" # 数据库 Service 名称(同命名空间内 DNS 自动解析)
DB_PORT: "3306"
LOG_LEVEL: "INFO"
# 文件形式:通过 Volume 挂载为配置文件
nginx.conf: |
server {
listen 80;
root /usr/share/nginx/html;
}
# 注入方式一:环境变量(在 Pod spec.containers 中添加)
# env:
# - name: DB_HOST
# valueFrom:
# configMapKeyRef:
# name: app-config # ConfigMap 名称
# key: DB_HOST # 键名
# 注入方式二:Volume 挂载(适合配置文件)
# volumes:
# - name: nginx-config
# configMap:
# name: app-config
# items:
# - key: nginx.conf
# path: nginx.conf
kubectl get configmap app-config -o yaml
# 输出: (展开所有 data 字段)
Helm 安装
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo nginx
# 输出: NAME CHART VERSION APP VERSION DESCRIPTION
# bitnami/nginx 15.0.0 1.25.0 Chart for the nginx server
helm install my-nginx bitnami/nginx --namespace web --create-namespace --set service.type=LoadBalancer
# 输出: NAME: my-nginx
# LAST DEPLOYED: ...
# NAMESPACE: web
# STATUS: deployed
helm list -n web
# 输出: NAME NAMESPACE REVISION STATUS CHART APP VERSION
# my-nginx web 1 deployed nginx-15.0.0 1.25.0
K3s 搭建
# 安装 K3s(单行命令)
curl -sfL https://get.k3s.io | sh -
sudo systemctl status k3s
# 输出: ● k3s.service - Lightweight Kubernetes
# Active: active (running)
sudo k3s kubectl get nodes
# 输出: NAME STATUS ROLES AGE VERSION
# node1 Ready control-plane,master 1m v1.30.1+k3s1
# 部署测试应用
kubectl create deployment hello --image=nginx:alpine
kubectl expose deployment hello --port=80 --type=NodePort
kubectl get svc hello
# 输出: NAME TYPE CLUSTER-IP PORT(S) AGE
# hello NodePort 10.43.xxx.xxx 80:3xxxx/TCP 5s
# 清理
# /usr/local/bin/k3s-uninstall.sh
kubectl 高频命令速查
# 查看与诊断
kubectl get pods -o wide # 附带节点/IP 信息
kubectl get all -n myns # 查看命名空间内所有资源
kubectl describe pod # Events + 配置详情(排错第一步)
kubectl logs -f --previous # 查看上一个容器实例的日志
kubectl exec -it -- sh # 进入容器调试
kubectl top node / kubectl top pod # 资源用量(需 metrics-server)
# 资源操作
kubectl apply -f deploy.yaml # 声明式创建/更新
kubectl delete -f deploy.yaml # 按清单删除
kubectl rollout restart deployment/nginx # 滚动重启(改配置后常用)
kubectl rollout status deployment/nginx # 等待滚动完成
kubectl scale deployment/nginx --replicas=5
kubectl port-forward svc/nginx-svc 8080:80 # 本地端口转发到集群内服务
# 上下文与命名空间
kubectl config get-contexts # 查看所有集群上下文
kubectl config use-context prod-cluster
kubectl get ns && kubectl create ns staging
故障排查五步法
# 第一步:定位问题资源
kubectl get pods -A | grep -v Running | grep -v Completed
# 第二步:查看事件(80% 的问题在这里一眼可见)
kubectl describe pod | tail -20
# 第三步:看日志(应用层错误)
kubectl logs --tail=100
# 第四步:验证 Service 链路
kubectl get endpoints # Endpoints 为空 = selector 没匹配上
kubectl exec -it -- wget -qO- http://:80
# 第五步:检查 DNS 与网络
kubectl exec -it -- nslookup ..svc
get(什么状态)→ 再 describe(发生了什么)→ 后 logs(应用为什么)→ 最后 exec(进容器验证)。大多数"Service 不通"的案例,90% 是 selector 标签写错——先查 Endpoints,别急着查网络。常见错误
| 错误现象 | 原因 | 解决方案 |
|---|---|---|
Pod 一直 Pending | 节点资源不足(CPU/内存)或 PVC 未绑定 | kubectl describe pod 看 Events 段;检查节点 kubectl top nodes |
Pod 反复 CrashLoopBackOff | 容器启动后立即退出(配置错误/app bug) | kubectl logs 看退出原因;检查 readinessProbe 配置 |
| Service 无法访问 | selector 不匹配 Pod 标签,或 targetPort 错误 | 检查 kubectl describe svc 的 Endpoints 字段;确保 endpoints 非空 |
| Ingress 返回 404 | Ingress Controller 未安装,或 path 前缀不匹配 | 确认有 Ingress Controller Pod 在运行;检查 annotation 配置 |
ImagePullBackOff | 镜像名拼错、私有仓库未配置 imagePullSecrets | kubectl describe pod 看具体错误;配置 imagePullSecrets |
| Helm 安装失败 | values.yaml 参数不合法,或 Chart 依赖未下载 | helm lint 检查 Chart;helm dependency build 下载依赖 |
最佳实践
| 领域 | 实践 | 说明 |
|---|---|---|
| 资源限制 | 始终设置 requests + limits | 防止单 Pod 耗尽节点资源,影响其他服务 |
| 健康检查 | 配置 livenessProbe + readinessProbe | liveness 决定是否重启,readiness 决定是否接流量 |
| 优雅关闭 | 设置 terminationGracePeriodSeconds + preStop hook | 给容器足够时间完成清理、断连、刷盘 |
| 标签规范 | 使用有意义的 labels(app/tier/env) | 便于 Service selector、监控、成本分摊 |
| 配置管理 | ConfigMap 存配置,Secret 存敏感数据 | 不硬编码在镜像或 Deployment 中 |
| 权限最小化 | RBAC 不分配 cluster-admin | 每个角色只有必需的 resources+verbs |
| Secret 加密 | 启用 etcd 加密 + Sealed Secrets | base64 不是加密,谁有 etcd 权限谁就能读 |
| 版本管理 | 所有 YAML 入 Git,用 GitOps 工具同步 | 审计追踪、回滚即时、漂移自动修正 |
练习题
- 排错实战:创建一个 Deployment,故意设置错误的 image 名称(如
nginx:nonexistent),观察 Pod 状态。用kubectl describe pod找到错误原因并修复。 - Service 联通:创建两个 Deployment(nginx + busybox),通过 ClusterIP Service 使 busybox 用
wget访问 nginx。验证 Endpoints 正确性。 - Ingress 路由:部署一个 nginx Ingress Controller,配置一个 Ingress 将
/app1路由到 svc1、/app2路由到 svc2。用curl -H "Host: example.com"测试。 - ConfigMap 注入:创建一个 ConfigMap 包含
NGINX_HOST=myapp.local,通过 env 注入到 Pod。验证容器内echo $NGINX_HOST输出正确。 - K3s 实战:在一台 Ubuntu 虚拟机或树莓派上用 K3s 搭建单节点集群,部署 WordPress(Helm Chart bitnami/wordpress),通过 NodePort 访问。
点击查看答案
- 创建 Deployment 设置
image: nginx:nonexistent→ Pod 状态ImagePullBackOff→kubectl describe pod的 Events 段显示 "Failed to pull image" + 具体 404 错误 → 修正镜像名为正确 tag 后kubectl apply -f自动拉取并启动。 - 创建 nginx Deployment + 创建 ClusterIP Service → 创建 busybox Deployment 并在其 Pod 内执行
wget -O- http://nginx-svc应拿到 nginx 页面。kubectl describe svc nginx-svc的 Endpoints 字段应列出 nginx Pod 的 IP。 - 安装 nginx Ingress Controller → 创建两个 Service(svc1/svc2)→ Ingress YAML 中配置两条 path 规则 →
curl -H "Host: example.com" http://localhost/app1路由到 svc1,/app2路由到 svc2。 - ConfigMap 中
data: NGINX_HOST: myapp.local→ Deployment 中env: - name: NGINX_HOST valueFrom: configMapKeyRef: name: my-config key: NGINX_HOST→ Pod 内echo $NGINX_HOST输出myapp.local。 - 安装 K3s →
helm repo add bitnami→helm install wordpress bitnami/wordpress --set service.type=NodePort→kubectl get svc wordpress获取 NodePort → 浏览器访问 http://node-ip:NodePort 完成 WordPress 安装。
学习检查点
学完本章后,请检验自己是否掌握以下内容:
| 检查项 | 自测问题 | 验证方法 |
|---|---|---|
| 概念理解 | 能用自己的话解释 K8s 的 Pod、Service、Deployment 核心概念 | 尝试向他人讲解 |
| 命令操作 | 能不查文档完成 kubectl 常用命令和 YAML 资源定义编写 | 在终端实际执行 |
| 原理掌握 | 能说出 K8s 的控制平面组件和调度器工作原理 | 画出流程图 |
| 故障排查 | 能独立排查 Pod 处于 Pending 或 CrashLoopBackOff 状态 | 模拟故障并修复 |
| 最佳实践 | 能说明为什么需要配置资源限制和健康检查探针 | 对比不同方案 |
本章总结
速查表
| 资源 | API 版本 | 一句话 | kubectl 前缀 |
|---|---|---|---|
| Pod | v1 | 最小调度单元(1+N 容器) | kubectl get pods |
| Deployment | apps/v1 | 管理 ReplicaSet + 滚动更新 | kubectl get deployments |
| Service | v1 | 稳定的 Pod 网络入口 | kubectl get svc |
| Ingress | networking.k8s.io/v1 | 七层 HTTP 路由 | kubectl get ingress |
| ConfigMap | v1 | 非敏感配置 | kubectl get configmap |
| Secret | v1 | 敏感数据(base64 编码) | kubectl get secrets |
| Helm Release | Helm v3 | Chart 部署的实例 | helm list |
学习路径:
- 掌握本章 → 6.7:GitOps 入门 GitOps 入门(ArgoCD/Flux 自动化同步)
- 深入了解容器网络 → k8s05 CNI 网络插件(Calico / Cilium / Flannel)
- 生产集群运维 → k8s01-k8s04(etcd 备份、集群升级、HPA/VPA、NetworkPolicy)
- 进阶主题 → k8s06 Service Mesh 入门(Istio)、Operator 模式(Kubebuilder)、eBPF(Cilium)
排错四步曲:kubectl get → describe → logs → exec。善用 -o wide 和 -o yaml 获取详细信息。
延伸阅读
- Kubernetes 官方文档 — 概念 + 任务 + 教程,最权威的参考
- Helm 官方文档 — Chart 开发指南、最佳实践
- K3s 官网 — 轻量 K8s 安装与配置文档
- Kubernetes in Action (Marko Lukša) — K8s 实战经典教材
- The Kubernetes Book (Nigel Poulton) — 入门友好,配图丰富
- KubeWiz — 交互式 kubectl 练习平台