搜 索

☸️ Kubernetes从入门到放弃

  • 17阅读
  • 2023年09月30日
  • 0评论
首页 / 编程 / 正文

一、Kubernetes 是什么?——名字都念不对,怎么学?

1.1 先把名字念对

Kubernetes(读作 /kuːbərˈnɛtɪs/),源自希腊语,意为"舵手"或"领航员"。

简称 K8s——因为 K 和 s 之间有 8 个字母。程序员的偷懒精神,连名字都不放过。

也有人叫它 Kube,但请不要叫它 "Ku-ber-NET-eez",会被鄙视的。

1.2 K8s 的前世今生

timeline title 容器编排的进化史 2003-2004 : Google 内部开发 Borg : 管理数十亿容器 2013 : Docker 发布 : 容器技术走向大众 2014 : Google 开源 Kubernetes : 基于 Borg 15年经验 2015 : CNCF 成立 : K8s 成为首个项目 2017 : Docker Swarm 认输 : K8s 赢得编排大战 2018 : K8s 统治市场 : 各大云厂商全面支持 2020+ : 云原生成为标配 : 不会 K8s 等于不会呼吸

1.3 为什么需要 K8s?——Docker Compose 不够吗?

graph TB subgraph "Docker Compose 能做的 ✅" DC1["单机部署多个容器"] DC2["容器间网络通信"] DC3["定义依赖关系"] DC4["一键启动/停止"] end subgraph "Docker Compose 做不到的 ❌" K1["跨多台机器部署"] K2["自动扩缩容"] K3["容器挂了自动重启"] K4["滚动更新/回滚"] K5["服务发现与负载均衡"] K6["Secret/Config 管理"] K7["存储编排"] K8["资源调度优化"] end DC1 -.->|"K8s 全都能做
而且做得更好"| K1 style K1 fill:#c8e6c9 style K2 fill:#c8e6c9 style K3 fill:#c8e6c9 style K4 fill:#c8e6c9 style K5 fill:#c8e6c9 style K6 fill:#c8e6c9 style K7 fill:#c8e6c9 style K8 fill:#c8e6c9

用人话说:Docker Compose 是单机版"开心消消乐",K8s 是分布式"星际争霸"。


二、K8s 架构——看完就想放弃的那种

2.1 整体架构

graph TB USER["👤 用户 / CI/CD"] -->|kubectl| API["API Server
☸️ 集群大脑的前台"] subgraph CP["Control Plane(控制平面)🧠"] API --> ETCD["etcd
📦 分布式键值存储
集群的唯一数据源"] API --> SCHED["Scheduler
📋 调度器
决定 Pod 跑在哪个节点"] API --> CM["Controller Manager
🔄 控制器管理器
维护集群期望状态"] API --> CCM["Cloud Controller
☁️ 云控制器
对接云厂商 API"] end subgraph N1["Worker Node 1 🖥️"] KL1["Kubelet
🤖 节点代理
管理本机 Pod"] KP1["Kube-proxy
🔀 网络代理
维护网络规则"] CR1["Container Runtime
🐳 containerd"] subgraph PODS1["Pods"] P1["Pod A"] P2["Pod B"] end KL1 --> CR1 --> PODS1 end subgraph N2["Worker Node 2 🖥️"] KL2["Kubelet"] KP2["Kube-proxy"] CR2["Container Runtime"] subgraph PODS2["Pods"] P3["Pod C"] P4["Pod D"] end KL2 --> CR2 --> PODS2 end subgraph N3["Worker Node 3 🖥️"] KL3["Kubelet"] KP3["Kube-proxy"] CR3["Container Runtime"] subgraph PODS3["Pods"] P5["Pod E"] end KL3 --> CR3 --> PODS3 end API --> KL1 API --> KL2 API --> KL3
🧠 记忆法:Control Plane 是公司总部(CEO、财务、HR),Worker Node 是各地分公司(干活的)。API Server 是前台,所有命令都要通过它。etcd 是公司保险柜,存着所有重要数据。

2.2 组件职责一张图

graph LR subgraph "Control Plane 组件" A["API Server
🚪 唯一入口
所有通信的中枢"] B["etcd
💾 数据库
集群状态持久化"] C["Scheduler
🎯 调度器
为 Pod 选择节点"] D["Controller Manager
♻️ 控制循环
确保实际=期望"] end subgraph "Node 组件" E["Kubelet
👷 工头
执行 Pod 生命周期"] F["Kube-proxy
🔌 网络管家
Service → Pod 路由"] G["Container Runtime
🏭 容器工厂
创建/销毁容器"] end A <-->|"读写状态"| B A -->|"Pod 调度决策"| C A -->|"状态协调"| D A -->|"Pod 操作指令"| E E -->|"管理容器"| G

2.3 K8s 的"声明式"哲学

sequenceDiagram participant U as 你 (kubectl) participant A as API Server participant E as etcd participant C as Controller participant S as Scheduler participant K as Kubelet U->>A: 我要 3 个 nginx Pod A->>E: 记下来:期望状态 = 3 pods A->>C: 嘿,检查一下状态 C->>A: 当前 0 个,需要创建 3 个 A->>S: 这 3 个 Pod 放哪? S->>A: Node1 放 1 个,Node2 放 2 个 A->>K: Node1: 创建 1 个 Pod A->>K: Node2: 创建 2 个 Pod K->>A: 报告!Pod 已运行 A->>E: 更新:当前状态 = 3 pods ✅ Note over C: 之后持续监控...
如果有 Pod 挂了
自动拉起新的

🔑 核心理念:你不是告诉 K8s "怎么做",而是告诉它 "我要什么样的结果"。它会自己想办法达到那个状态,并且持续维护。

就像你对一个靠谱的管家说"家里永远要有 3 盒牛奶",而不是"去超市买 3 盒牛奶"。牛奶喝完了?管家自己去买。过期了?管家自己换。


三、K8s 核心概念——名词轰炸开始

3.1 概念关系全景图

graph TB subgraph "工作负载(Workloads)" POD["Pod
🫛 最小调度单元
一个或多个容器"] RS["ReplicaSet
🔢 副本控制器
维护 Pod 数量"] DEPLOY["Deployment
🚀 部署控制器
滚动更新/回滚"] STS["StatefulSet
📊 有状态部署
稳定网络标识+存储"] DS["DaemonSet
👹 守护进程集
每个节点跑一个"] JOB["Job / CronJob
⏰ 任务/定时任务
跑完就结束"] DEPLOY --> RS --> POD end subgraph "服务发现与负载均衡" SVC["Service
🔀 服务
稳定的访问入口"] ING["Ingress
🌐 入口网关
HTTP/HTTPS 路由"] EP["Endpoints
📍 端点
Service 背后的 Pod IP"] ING --> SVC --> EP --> POD end subgraph "配置与存储" CM["ConfigMap
📝 配置映射"] SEC["Secret
🔐 敏感信息"] PV["PersistentVolume
💾 持久化卷"] PVC["PersistentVolumeClaim
📋 存储申请单"] SC["StorageClass
🏭 存储类"] PVC -->|"绑定"| PV SC -->|"动态创建"| PV end subgraph "命名空间与权限" NS["Namespace
📂 命名空间
逻辑隔离"] SA["ServiceAccount
🪪 服务账号"] RBAC["RBAC
🔑 角色权限控制"] end

3.2 用一家餐厅来理解 K8s

graph TB subgraph "K8s = 一家连锁餐厅 🍜" CLUSTER["Cluster 集群
= 整个连锁品牌"] subgraph "Control Plane = 总部" CEO["API Server = 前台经理"] DB["etcd = 账本"] HR["Scheduler = HR
(安排员工去哪个分店)"] QA["Controller = 品控
(确保每家店品质一致)"] end subgraph "Node = 分店" CHEF["Kubelet = 店长"] WAITER["Kube-proxy = 服务员
(把客人引到对的桌子)"] end subgraph "核心概念对照" P["Pod = 一道菜
(可能有主菜+配菜)"] D["Deployment = 菜谱
(规定怎么做这道菜)"] S["Service = 菜单上的菜名
(客人不关心谁做的)"] I["Ingress = 餐厅大门
(外部客人从这进来)"] NS2["Namespace = 楼层
(中餐在2楼,西餐在3楼)"] CM2["ConfigMap = 调料架"] SEC2["Secret = 秘方
(锁在保险柜里)"] PV2["PV = 冰箱"] PVC2["PVC = 食材申请单"] end end
客人(请求)从大门(Ingress)进来,看菜单(Service)点菜,服务员(Kube-proxy)把订单送到后厨,店长(Kubelet)安排厨师用菜谱(Deployment)做菜(Pod)。如果某道菜做砸了,品控(Controller)会让重新做一道。

四、安装 K8s——第一个 Boss 关

4.1 安装方式选择

graph TD START["我要安装 K8s!"] --> Q1{"你是来学习的
还是上生产的?"} Q1 -->|"学习"| Q2{"你的电脑
内存够吗?"} Q2 -->|"≥ 8GB"| MINIKUBE["Minikube
✅ 最推荐新手"] Q2 -->|"≥ 4GB"| K3S["K3s
✅ 轻量级 K8s"] Q2 -->|"< 4GB"| KIND["Kind
(K8s in Docker)
⚠️ 但你确定要套娃?"] Q1 -->|"上生产"| Q3{"你有云账号吗?"} Q3 -->|"有钱"| MANAGED["托管服务
EKS / AKS / GKE
💰 花钱买省心"] Q3 -->|"没钱"| Q4{"你有几台机器?"} Q4 -->|"≥ 3台"| KUBEADM["kubeadm
🔧 官方安装工具
⚠️ 需要一定经验"] Q4 -->|"1台"| K3S_PROD["K3s
单节点也能跑"] MINIKUBE --> NOTE1["⚠️ 仅限学习
不要上生产!"] MANAGED --> NOTE2["💡 强烈推荐
除非你喜欢加班"] KUBEADM --> NOTE3["🎓 适合想深入理解的人
也适合想掉头发的人"] style MANAGED fill:#c8e6c9 style MINIKUBE fill:#bbdefb style NOTE3 fill:#ffcdd2

4.2 Minikube 快速开始(学习推荐)

# ===== macOS =====
brew install minikube
minikube start --driver=docker --memory=4096 --cpus=2

# ===== Linux =====
curl -LO https://storage.googleapis.com/minikube/releases/latest/minikube-linux-amd64
sudo install minikube-linux-amd64 /usr/local/bin/minikube
minikube start --driver=docker --memory=4096 --cpus=2

# 验证安装
kubectl get nodes
# 输出:
# NAME       STATUS   ROLES           AGE   VERSION
# minikube   Ready    control-plane   30s   v1.28.3

# 打开 Dashboard(可视化界面)
minikube dashboard
# 🎉 浏览器自动打开,看到界面那一刻你会觉得 K8s 也没那么难
# 别高兴太早

4.3 kubeadm 安装(生产级,给勇者)

# ===== 所有节点执行 =====

# 1. 关闭 swap(K8s 的强制要求,别问为什么)
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab

# 2. 加载内核模块
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter

# 3. 内核参数
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables  = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward                 = 1
EOF
sudo sysctl --system

# 4. 安装 containerd
sudo apt-get update && sudo apt-get install -y containerd.io
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml
# 修改 SystemdCgroup = true
sudo systemctl restart containerd

# 5. 安装 kubeadm, kubelet, kubectl
sudo apt-get install -y apt-transport-https ca-certificates curl
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | \
  sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] \
  https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | \
  sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl


# ===== 仅 Master 节点执行 =====

# 6. 初始化集群
sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --apiserver-advertise-address=<MASTER_IP>

# 7. 配置 kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 8. 安装网络插件(Calico)
kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml


# ===== 仅 Worker 节点执行 =====

# 9. 加入集群(使用 kubeadm init 输出的命令)
sudo kubeadm join <MASTER_IP>:6443 \
  --token <TOKEN> \
  --discovery-token-ca-cert-hash sha256:<HASH>
😅 如果你顺利走完了以上步骤没出错,请立刻去买彩票。

五、kubectl——你的新命令行伙伴

5.1 kubectl 命令结构

graph LR CMD["kubectl"] --> VERB["动词
get / create / apply
delete / describe"] VERB --> RESOURCE["资源类型
pod / svc / deploy
node / ns / ing"] RESOURCE --> NAME["资源名称
my-app / nginx"] NAME --> FLAGS["选项
-n namespace
-o yaml/json
--watch"] style CMD fill:#e1f5fe style VERB fill:#fff9c4 style RESOURCE fill:#c8e6c9 style FLAGS fill:#f3e5f5

5.2 必背 kubectl 命令速查表

# ============ 查看类 ============

# 查看所有 Pod
kubectl get pods                          # 当前 namespace
kubectl get pods -A                       # 所有 namespace
kubectl get pods -o wide                  # 显示更多信息(IP、节点等)
kubectl get pods --watch                  # 实时监控变化
kubectl get pods -l app=nginx             # 按标签筛选

# 查看所有资源
kubectl get all                           # Pod, Service, Deployment...
kubectl get nodes                         # 节点状态
kubectl get svc                           # Service 列表
kubectl get deploy                        # Deployment 列表
kubectl get ns                            # Namespace 列表

# 详细信息(排错必用)
kubectl describe pod <pod-name>
kubectl describe node <node-name>
kubectl describe svc <svc-name>

# 查看日志
kubectl logs <pod-name>                   # 当前日志
kubectl logs -f <pod-name>                # 实时跟踪
kubectl logs <pod-name> -c <container>    # 多容器 Pod 指定容器
kubectl logs --previous <pod-name>        # 上一次崩溃的日志(救命)


# ============ 操作类 ============

# 应用 YAML(声明式,推荐)
kubectl apply -f deployment.yaml
kubectl apply -f ./k8s/                   # 应用整个目录
kubectl apply -f https://xxx/xxx.yaml     # 直接从 URL 应用

# 删除资源
kubectl delete -f deployment.yaml
kubectl delete pod <pod-name>
kubectl delete pod <pod-name> --force --grace-period=0  # 强制删除(卡住时用)

# 进入容器(debug 用)
kubectl exec -it <pod-name> -- /bin/sh
kubectl exec -it <pod-name> -c <container> -- /bin/bash

# 端口转发(本地调试用)
kubectl port-forward pod/<pod-name> 8080:80
kubectl port-forward svc/<svc-name> 8080:80

# 扩缩容
kubectl scale deployment <name> --replicas=5

# 查看资源使用
kubectl top nodes
kubectl top pods


# ============ 高级操作 ============

# 滚动更新镜像
kubectl set image deployment/<name> <container>=<image>:<tag>

# 查看滚动更新状态
kubectl rollout status deployment/<name>

# 回滚到上一个版本
kubectl rollout undo deployment/<name>

# 回滚到指定版本
kubectl rollout history deployment/<name>
kubectl rollout undo deployment/<name> --to-revision=2

# 临时运行一个 Pod(debug 神器)
kubectl run debug --rm -it --image=busybox -- /bin/sh
kubectl run debug --rm -it --image=nicolaka/netshoot -- /bin/bash  # 网络调试

5.3 kubectl 效率提升

# 配置别名(加到 ~/.bashrc 或 ~/.zshrc)
alias k='kubectl'
alias kgp='kubectl get pods'
alias kgs='kubectl get svc'
alias kgd='kubectl get deploy'
alias kgn='kubectl get nodes'
alias kdp='kubectl describe pod'
alias kaf='kubectl apply -f'
alias kdf='kubectl delete -f'
alias kl='kubectl logs -f'
alias kei='kubectl exec -it'

# 自动补全
source <(kubectl completion bash)  # bash
source <(kubectl completion zsh)   # zsh

# 快速切换 namespace
kubectl config set-context --current --namespace=production

# 或者安装 kubectx / kubens(强烈推荐)
# kubens production     ← 切换 namespace
# kubectx staging       ← 切换集群

六、用 YAML 定义一切——K8s 的灵魂

6.1 YAML 恐惧症

graph LR A["第 1 天
YAML 挺简洁的"] --> B["第 7 天
缩进错了一格
整个集群炸了"] B --> C["第 30 天
我的 YAML 文件
比代码还多"] C --> D["第 90 天
我开始用 Helm
生成 YAML"] D --> E["第 180 天
Helm template
比原始 YAML 还复杂"] E --> F["第 365 天
用 AI 写 YAML 😌"] style B fill:#ffcdd2 style E fill:#ffcdd2 style F fill:#c8e6c9

6.2 最完整的 Deployment YAML(带注释)

apiVersion: apps/v1              # API 版本
kind: Deployment                 # 资源类型
metadata:
  name: payment-service          # Deployment 名字
  namespace: production          # 命名空间
  labels:
    app: payment                 # 标签(用于选择器匹配)
    team: deposit                # Joey 的 Deposit Team 😎
    version: v2.1.0
  annotations:
    description: "NPSS Aani payment processing service"
spec:
  replicas: 3                    # 副本数
  revisionHistoryLimit: 5        # 保留 5 个历史版本(用于回滚)
  
  strategy:                      # 更新策略
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                # 更新时最多多出 1 个 Pod
      maxUnavailable: 0          # 更新时不允许有 Pod 不可用
  
  selector:                      # 选择器:匹配哪些 Pod
    matchLabels:
      app: payment
  
  template:                      # Pod 模板
    metadata:
      labels:
        app: payment
        version: v2.1.0
    spec:
      # ===== 优雅终止 =====
      terminationGracePeriodSeconds: 60
      
      # ===== 安全上下文 =====
      securityContext:
        runAsNonRoot: true
        runAsUser: 1000
        fsGroup: 1000
      
      # ===== 反亲和性(分散到不同节点)=====
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              podAffinityTerm:
                labelSelector:
                  matchExpressions:
                    - key: app
                      operator: In
                      values:
                        - payment
                topologyKey: kubernetes.io/hostname
      
      # ===== Init 容器 =====
      initContainers:
        - name: wait-for-mysql
          image: busybox:1.36
          command: ['sh', '-c', 'until nc -z mysql-svc 3306; do echo waiting for mysql; sleep 2; done']
      
      # ===== 主容器 =====
      containers:
        - name: payment-app
          image: registry.astratech.ae/payment-service:v2.1.0
          imagePullPolicy: IfNotPresent
          
          ports:
            - name: http
              containerPort: 8080
              protocol: TCP
            - name: actuator
              containerPort: 8081
              protocol: TCP
          
          # ===== 环境变量 =====
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: "production"
            - name: TZ
              value: "Asia/Dubai"
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: payment-secrets
                  key: db-password
            - name: JAVA_OPTS
              value: "-XX:MaxRAMPercentage=75.0 -XX:+UseG1GC"
          
          envFrom:
            - configMapRef:
                name: payment-config
          
          # ===== 资源限制 =====
          resources:
            requests:
              cpu: "250m"        # 0.25 核
              memory: "512Mi"
            limits:
              cpu: "1000m"       # 1 核
              memory: "1Gi"
          
          # ===== 探针 =====
          startupProbe:          # 启动探针(Java 应用启动慢,先用这个)
            httpGet:
              path: /actuator/health
              port: actuator
            failureThreshold: 30
            periodSeconds: 10
          
          livenessProbe:         # 存活探针(挂了就重启)
            httpGet:
              path: /actuator/health/liveness
              port: actuator
            initialDelaySeconds: 0
            periodSeconds: 15
            timeoutSeconds: 5
            failureThreshold: 3
          
          readinessProbe:        # 就绪探针(没准备好就不接流量)
            httpGet:
              path: /actuator/health/readiness
              port: actuator
            initialDelaySeconds: 0
            periodSeconds: 10
            timeoutSeconds: 5
            failureThreshold: 3
          
          # ===== 挂载 =====
          volumeMounts:
            - name: config-vol
              mountPath: /app/config
              readOnly: true
            - name: logs-vol
              mountPath: /app/logs
      
      # ===== 卷定义 =====
      volumes:
        - name: config-vol
          configMap:
            name: payment-config-files
        - name: logs-vol
          emptyDir:
            sizeLimit: 500Mi
      
      # ===== 镜像拉取凭证 =====
      imagePullSecrets:
        - name: registry-credentials
😱 一个 Deployment 就写了 130+ 行 YAML。而这只是一个服务。一个完整的微服务体系可能有几十个这样的文件。现在理解为什么有人发明了 Helm 了吧?

6.3 三大探针对比

sequenceDiagram participant K as Kubelet participant P as Pod Note over K,P: 🟡 阶段1:启动探针 (startupProbe) loop 每10s检查一次,最多30次 K->>P: GET /health P-->>K: 503 还在启动... end K->>P: GET /health P-->>K: 200 OK 启动完成! Note over K,P: 🟢 阶段2:就绪探针 (readinessProbe) loop 每10s K->>P: GET /health/readiness alt 返回 200 P-->>K: ✅ Ready Note over P: 接收流量 else 返回非200 P-->>K: ❌ Not Ready Note over P: 从 Service 摘除
不再接收新请求 end end Note over K,P: 🔴 阶段3:存活探针 (livenessProbe) loop 每15s K->>P: GET /health/liveness alt 返回 200 P-->>K: ✅ Alive else 连续3次失败 P-->>K: ❌ Dead Note over K: 重启 Pod! end end
探针作用失败后果适用场景
startupProbe判断应用是否启动完成继续等待Java/Spring Boot 等启动慢的应用
readinessProbe判断是否准备好接收流量从 Service 摘除依赖外部服务、缓存预热
livenessProbe判断应用是否还活着重启 Pod死锁检测、OOM 恢复
⚠️ 血泪教训:不要把 livenessProbe 指向会因为外部依赖(如数据库)失败的接口。否则数据库一挂,所有 Pod 连环重启,雪崩式灾难。

七、Service 与 Ingress——流量怎么进来的

7.1 Service 四种类型

graph TB subgraph "ClusterIP(默认)" direction TB CIP["Service
ClusterIP: 10.96.0.1
只在集群内部可访问"] CIP --> CP1["Pod 1"] CIP --> CP2["Pod 2"] CIP --> CP3["Pod 3"] end subgraph "NodePort" direction TB NP["Service
NodePort: 30080
通过节点IP:端口访问"] EXT1["外部请求
NodeIP:30080"] --> NP NP --> NP1["Pod 1"] NP --> NP2["Pod 2"] end subgraph "LoadBalancer" direction TB LB["云厂商负载均衡器
公网 IP"] LBSVC["Service
LoadBalancer"] LB --> LBSVC LBSVC --> LP1["Pod 1"] LBSVC --> LP2["Pod 2"] end subgraph "ExternalName" direction TB EN["Service
ExternalName"] EDNS["external-api.example.com
DNS CNAME 映射"] EN --> EDNS end style CIP fill:#bbdefb style NP fill:#fff9c4 style LB fill:#c8e6c9 style EN fill:#f3e5f5
# ClusterIP(集群内部通信)
apiVersion: v1
kind: Service
metadata:
  name: payment-svc
spec:
  type: ClusterIP
  selector:
    app: payment
  ports:
    - port: 80            # Service 端口
      targetPort: 8080     # Pod 端口
      protocol: TCP

---

# NodePort(开发/测试用)
apiVersion: v1
kind: Service
metadata:
  name: payment-svc-nodeport
spec:
  type: NodePort
  selector:
    app: payment
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080      # 范围:30000-32767

7.2 Ingress 路由规则

graph LR INTERNET["🌐 Internet"] --> ING["Ingress Controller
(Nginx / Traefik)"] ING -->|"api.astratech.ae/payment/*"| SVC1["payment-svc
:80"] ING -->|"api.astratech.ae/deposit/*"| SVC2["deposit-svc
:80"] ING -->|"api.astratech.ae/exchange/*"| SVC3["exchange-svc
:80"] ING -->|"dashboard.astratech.ae"| SVC4["dashboard-svc
:80"] SVC1 --> P1["Payment Pods"] SVC2 --> P2["Deposit Pods"] SVC3 --> P3["Exchange Pods"] SVC4 --> P4["Dashboard Pods"]
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: astratech-ingress
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.astratech.ae
      secretName: api-tls-cert
  rules:
    - host: api.astratech.ae
      http:
        paths:
          - path: /payment
            pathType: Prefix
            backend:
              service:
                name: payment-svc
                port:
                  number: 80
          - path: /deposit
            pathType: Prefix
            backend:
              service:
                name: deposit-svc
                port:
                  number: 80
          - path: /exchange
            pathType: Prefix
            backend:
              service:
                name: exchange-svc
                port:
                  number: 80

八、ConfigMap 与 Secret——配置管理

8.1 配置和密钥的正确姿势

graph TB subgraph "❌ 错误做法" BAD1["把密码写在镜像里"] BAD2["环境变量硬编码在 YAML"] BAD3["配置文件 bake 进镜像"] end subgraph "✅ 正确做法" CM["ConfigMap
📝 非敏感配置"] SEC["Secret
🔐 敏感信息"] CM -->|"挂载为文件"| POD1["Pod"] CM -->|"注入为环境变量"| POD1 SEC -->|"挂载为文件"| POD2["Pod"] SEC -->|"注入为环境变量"| POD2 end BAD1 -.->|"别这样"| SEC BAD2 -.->|"别这样"| CM BAD3 -.->|"别这样"| CM
# ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: payment-config
data:
  # 键值对形式
  DB_HOST: "mysql-svc"
  DB_PORT: "3306"
  REDIS_HOST: "redis-svc"
  LOG_LEVEL: "INFO"
  
  # 整个文件形式
  application.yml: |
    spring:
      application:
        name: payment-service
      datasource:
        url: jdbc:mysql://${DB_HOST}:${DB_PORT}/payment_db
        hikari:
          maximum-pool-size: 20

---

# Secret(base64 编码,注意:不是加密!)
apiVersion: v1
kind: Secret
metadata:
  name: payment-secrets
type: Opaque
data:
  db-password: cGFzc3dvcmQxMjM=          # echo -n "password123" | base64
  redis-password: cmVkaXNQQHNz             # echo -n "redisP@ss" | base64
  api-key: c2VjcmV0LWFwaS1rZXktMTIz       # echo -n "secret-api-key-123" | base64

# ⚠️ 严肃提醒:K8s Secret 默认只是 base64 编码,不是加密!
# 生产环境请配合:
# - Sealed Secrets
# - HashiCorp Vault
# - AWS Secrets Manager / Azure Key Vault
# - SOPS (Mozilla)
🔥 Secret 的安全真相:K8s 的 Secret 默认存在 etcd 里,而且只是 base64 编码。echo "cGFzc3dvcmQxMjM=" | base64 -d 就能解码。所以生产环境必须启用 etcd 加密或用外部密钥管理服务。

九、持久化存储——有状态服务的噩梦

9.1 存储架构

graph TB subgraph "存储体系" POD["Pod"] -->|"volumeMounts"| PVC["PVC
PersistentVolumeClaim
📋 我要 10Gi 的 SSD"] PVC -->|"绑定"| PV["PV
PersistentVolume
💾 这里有 10Gi 的 SSD"] SC["StorageClass
🏭 存储工厂"] -->|"动态创建"| PV PV --> BACKEND{"实际存储后端"} BACKEND --> LOCAL["本地磁盘
hostPath"] BACKEND --> NFS["NFS
网络文件系统"] BACKEND --> CLOUD["云存储
EBS / Azure Disk
GCE PD"] BACKEND --> CEPH["Ceph / Rook"] end
# StorageClass(动态存储,推荐)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast-ssd
provisioner: kubernetes.io/aws-ebs   # 或其他存储提供商
parameters:
  type: gp3
  iopsPerGB: "50"
reclaimPolicy: Retain                 # 删除 PVC 后保留数据
allowVolumeExpansion: true            # 允许扩容

---

# PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: fast-ssd
  resources:
    requests:
      storage: 50Gi

9.2 StatefulSet vs Deployment

graph TB subgraph "Deployment(无状态)" D_RS["ReplicaSet"] D_P1["pod-随机hash-abc"] D_P2["pod-随机hash-def"] D_P3["pod-随机hash-ghi"] D_RS --> D_P1 D_RS --> D_P2 D_RS --> D_P3 D_NOTE["Pod 名称随机
可以随意替换
共享存储或无存储
无序启动/终止"] end subgraph "StatefulSet(有状态)" S_SS["StatefulSet"] S_P0["mysql-0 👑
(master)"] S_P1["mysql-1
(slave)"] S_P2["mysql-2
(slave)"] S_PV0["PVC-0
50Gi"] S_PV1["PVC-1
50Gi"] S_PV2["PVC-2
50Gi"] S_SS --> S_P0 --> S_PV0 S_SS --> S_P1 --> S_PV1 S_SS --> S_P2 --> S_PV2 S_NOTE["Pod 名称有序
固定网络标识
各自独立 PVC
有序启动: 0→1→2
有序终止: 2→1→0"] end style D_NOTE fill:#fff9c4 style S_NOTE fill:#bbdefb
🎯 选择指南:无状态服务(Web API, 微服务)→ Deployment。有状态服务(数据库, 消息队列, Zookeeper)→ StatefulSet。

十、Helm——K8s 的包管理器

10.1 为什么需要 Helm

graph TD subgraph "没有 Helm 😰" Y1["deployment.yaml"] Y2["service.yaml"] Y3["configmap.yaml"] Y4["secret.yaml"] Y5["ingress.yaml"] Y6["pvc.yaml"] Y7["hpa.yaml"] Y8["networkpolicy.yaml"] Y1 --> REPEAT["× 3 个环境
(dev, staging, prod)
= 24 个 YAML 文件
内容几乎一样 😱"] end subgraph "有了 Helm 😌" CHART["📦 Helm Chart"] TMPL["模板 (templates/)"] VAL["values.yaml"] VAL_DEV["values-dev.yaml"] VAL_STG["values-staging.yaml"] VAL_PRD["values-prod.yaml"] CHART --> TMPL CHART --> VAL VAL_DEV -->|"helm install -f"| DEPLOY_D["部署到 Dev"] VAL_STG -->|"helm install -f"| DEPLOY_S["部署到 Staging"] VAL_PRD -->|"helm install -f"| DEPLOY_P["部署到 Prod"] end

10.2 Helm 常用命令

# 添加仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

# 搜索 Chart
helm search repo nginx
helm search hub prometheus       # 搜索 Artifact Hub

# 安装应用
helm install my-redis bitnami/redis \
  --namespace redis \
  --create-namespace \
  --set auth.password=myRedisP@ss \
  --set replica.replicaCount=3

# 用自定义 values 文件安装
helm install my-app ./my-chart -f values-prod.yaml

# 查看已安装的 Release
helm list -A

# 升级
helm upgrade my-app ./my-chart -f values-prod.yaml

# 回滚
helm rollback my-app 1           # 回滚到版本 1

# 卸载
helm uninstall my-app

# 查看渲染后的 YAML(debug 用)
helm template my-app ./my-chart -f values-prod.yaml

10.3 Chart 目录结构

my-chart/
├── Chart.yaml              # Chart 元信息(名称、版本)
├── values.yaml             # 默认配置值
├── values-dev.yaml         # 开发环境覆盖值
├── values-prod.yaml        # 生产环境覆盖值
├── templates/              # K8s 资源模板
│   ├── _helpers.tpl        # 模板函数
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── configmap.yaml
│   ├── secret.yaml
│   ├── ingress.yaml
│   ├── hpa.yaml
│   └── NOTES.txt           # 安装后提示信息
└── charts/                 # 子 Chart 依赖

十一、监控与可观测性——出了问题才知道重要

11.1 可观测性三大支柱

graph TB subgraph "可观测性 Observability" METRICS["📊 Metrics 指标
Prometheus + Grafana
———
CPU/内存使用率
请求延迟 P99
错误率"] LOGS["📝 Logs 日志
EFK / Loki
———
应用日志
错误堆栈
审计日志"] TRACES["🔍 Traces 链路追踪
Jaeger / Tempo
———
请求在微服务间
的完整调用链路"] end METRICS --- LOGS --- TRACES subgraph "K8s 监控栈推荐" PM["Prometheus
指标收集"] GF["Grafana
可视化"] LK["Loki
日志聚合"] TM["Tempo
链路追踪"] AM["AlertManager
告警通知"] PM --> GF LK --> GF TM --> GF PM --> AM end

11.2 Prometheus + Grafana 一键部署

# 使用 kube-prometheus-stack(Helm Chart,一键搞定)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install monitoring prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  --create-namespace \
  --set grafana.adminPassword=admin \
  --set prometheus.prometheusSpec.retention=30d \
  --set alertmanager.config.global.slack_api_url="https://hooks.slack.com/services/xxx"

# 访问 Grafana Dashboard
kubectl port-forward -n monitoring svc/monitoring-grafana 3000:80

# 打开 http://localhost:3000
# 用户名:admin,密码:admin
# 🎉 内置了 N 个 Dashboard,开箱即用

十二、HPA 自动扩缩容——让 K8s 替你加班

12.1 HPA 工作原理

sequenceDiagram participant HPA as HPA Controller participant MS as Metrics Server participant D as Deployment participant P as Pods (3个) loop 每 15 秒 HPA->>MS: 查询 Pod CPU/Memory 指标 MS-->>HPA: 当前平均 CPU: 80% alt CPU > 目标值 (70%) HPA->>D: 扩容! replicas: 3 → 5 D->>P: 创建 2 个新 Pod Note over P: 🚀 5 个 Pod else CPU < 目标值 (70%) 且持续 5 分钟 HPA->>D: 缩容! replicas: 5 → 3 D->>P: 终止 2 个 Pod Note over P: 📉 3 个 Pod end end
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: payment-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: payment-service
  
  minReplicas: 2              # 最少 2 个(高可用)
  maxReplicas: 20             # 最多 20 个(防止失控)
  
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70      # CPU 超过 70% 就扩
    
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80      # 内存超过 80% 就扩
  
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60  # 扩容等 60 秒再决定
      policies:
        - type: Percent
          value: 100                   # 每次最多翻倍
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300 # 缩容等 5 分钟再决定(防抖)
      policies:
        - type: Percent
          value: 25                    # 每次最多缩 25%
          periodSeconds: 60
💡 缩容要比扩容保守:扩容要快(用户在等),缩容要慢(防止波动)。就像招人很急,裁人要慎重。

十三、K8s 排错指南——放弃之前的最后一搏

13.1 Pod 状态诊断

graph TD START["Pod 出问题了!"] --> STATUS{"kubectl get pod
Pod 状态是什么?"} STATUS -->|"Pending"| PEND{"一直 Pending?"} PEND -->|"资源不足"| PEND_A["节点 CPU/内存不够
kubectl describe pod 看 Events
→ 增加节点或降低 requests"] PEND -->|"调度失败"| PEND_B["nodeSelector/affinity 不匹配
→ 检查标签和污点"] PEND -->|"PVC 未绑定"| PEND_C["StorageClass 不存在
或没有可用 PV
→ 检查存储配置"] STATUS -->|"CrashLoopBackOff"| CRASH["容器启动后立即崩溃"] CRASH --> CRASH_A["1. kubectl logs pod-name --previous
2. 检查启动命令 CMD
3. 检查配置文件
4. 检查依赖服务是否就绪"] STATUS -->|"ImagePullBackOff"| IMG["镜像拉取失败"] IMG --> IMG_A["1. 镜像名/Tag 拼写错误?
2. 私有仓库需要 imagePullSecret?
3. 网络能访问仓库吗?"] STATUS -->|"OOMKilled"| OOM["内存溢出被杀"] OOM --> OOM_A["1. 增加 resources.limits.memory
2. 检查应用是否有内存泄漏
3. JVM: -XX:MaxRAMPercentage"] STATUS -->|"Running
但不正常"| RUN{"Service 能访问吗?"} RUN -->|"不能"| RUN_A["1. kubectl get endpoints
2. Pod labels 和 Service selector 匹配吗?
3. 端口号对吗?"] RUN -->|"能但响应异常"| RUN_B["1. kubectl logs -f pod-name
2. kubectl exec 进去看看
3. 检查 ConfigMap/Secret"]

13.2 排错命令清单

# ===== 第一步:看状态 =====
kubectl get pods -o wide
kubectl get events --sort-by='.lastTimestamp'     # 按时间排序的事件

# ===== 第二步:看详情 =====
kubectl describe pod <pod-name>                    # 重点看 Events 部分!

# ===== 第三步:看日志 =====
kubectl logs <pod-name>                            # 当前日志
kubectl logs <pod-name> --previous                 # 崩溃前的日志
kubectl logs <pod-name> --all-containers           # 所有容器的日志

# ===== 第四步:进去看 =====
kubectl exec -it <pod-name> -- /bin/sh

# ===== 第五步:网络排查 =====
# 临时启动一个网络调试 Pod
kubectl run netdebug --rm -it --image=nicolaka/netshoot -- /bin/bash

# 在调试 Pod 内:
nslookup payment-svc                               # DNS 解析
curl -v http://payment-svc:80/health               # 服务连通性
traceroute payment-svc                              # 路由跟踪

# ===== 第六步:资源排查 =====
kubectl top nodes                                   # 节点资源
kubectl top pods                                    # Pod 资源
kubectl describe node <node-name> | grep -A 10 "Allocated"  # 已分配资源

# ===== 终极手段 =====
kubectl delete pod <pod-name>                       # 删了重建
kubectl rollout restart deployment <deploy-name>    # 滚动重启

13.3 常见错误速查

症状常见原因解决方案
Pod 一直 Pending资源不足 / 调度约束kubectl describe pod 看 Events
CrashLoopBackOff启动命令错误 / 配置缺失kubectl logs --previous
ImagePullBackOff镜像不存在 / 没有拉取权限检查镜像名和 imagePullSecret
OOMKilled内存不够增加 limits.memory
Evicted节点资源不足被驱逐检查节点资源 / 设置 PDB
Service 不通label 不匹配 / 端口错误kubectl get endpoints
Ingress 404path 不匹配 / backend 配错检查 Ingress 规则
PVC PendingStorageClass 不存在kubectl get sc

十四、生产环境最佳实践——防止半夜被叫醒

14.1 安全清单

graph TD SEC["🔒 K8s 安全加固"] --> S1["RBAC 权限最小化
别给 cluster-admin"] SEC --> S2["Pod Security Standards
禁止特权容器"] SEC --> S3["Network Policies
微分段,零信任"] SEC --> S4["Secret 加密存储
etcd encryption"] SEC --> S5["镜像签名 & 扫描
只允许可信镜像"] SEC --> S6["Resource Quotas
限制 namespace 资源"] SEC --> S7["API Server 审计
记录所有操作"] SEC --> S8["定期更新 K8s 版本
CVE 修复"]

14.2 资源管理黄金法则

# 每个容器都必须设置 resources!
resources:
  requests:        # 调度依据(保证能拿到这么多)
    cpu: "250m"    # 1/4 核
    memory: "256Mi"
  limits:          # 上限(超过会被 throttle/OOMKill)
    cpu: "1000m"   # 1 核
    memory: "512Mi"

# 💡 经验值:
# - requests ≈ 实际平均使用量
# - limits = requests × 2~4
# - 不设 limits → 一个 Pod 吃光整个节点
# - requests = limits → 保证 QoS 为 Guaranteed(最不容易被驱逐)

14.3 高可用部署 Checklist

graph TD HA["🏆 高可用部署"] --> H1["✅ replicas ≥ 2
至少两个副本"] HA --> H2["✅ Pod 反亲和
分散到不同节点"] HA --> H3["✅ PodDisruptionBudget
限制同时不可用数"] HA --> H4["✅ 滚动更新策略
maxUnavailable: 0"] HA --> H5["✅ 健康检查三件套
startup + readiness + liveness"] HA --> H6["✅ Resource requests/limits
防止资源饥饿"] HA --> H7["✅ 优雅终止
preStop + SIGTERM 处理"] HA --> H8["✅ HPA 自动扩缩容
应对流量波动"]
# PodDisruptionBudget - 限制同时中断数
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: payment-pdb
spec:
  minAvailable: 2                 # 至少保持 2 个可用
  # 或者 maxUnavailable: 1       # 最多 1 个不可用
  selector:
    matchLabels:
      app: payment

十五、K8s 生态全景图

mindmap root((Kubernetes 生态)) 包管理 Helm Kustomize Jsonnet CI/CD ArgoCD FluxCD Tekton Jenkins X 监控 Prometheus Grafana Datadog New Relic 日志 EFK Stack Loki Fluentbit 网络 Calico Cilium Istio Service Mesh Linkerd 存储 Rook/Ceph Longhorn OpenEBS 安全 Falco OPA/Gatekeeper Trivy Cert-Manager 开发体验 Telepresence Skaffold Tilt DevSpace 多集群 Rancher KubeFed Crossplane
😵 看到这张图,你可能已经理解了为什么这个系列叫「从入门到放弃」了。

十六、K8s 学习路线图——如果你还没放弃

graph TB L1["🌱 入门阶段
2-4 周"] L1 --> L1_1["理解核心概念
Pod, Deployment, Service"] L1 --> L1_2["Minikube 上手"] L1 --> L1_3["kubectl 基本操作"] L1 --> L1_4["写简单的 YAML"] L2["🌿 进阶阶段
1-2 个月"] L1 --> L2 L2 --> L2_1["ConfigMap, Secret
存储管理"] L2 --> L2_2["Ingress, NetworkPolicy"] L2 --> L2_3["Helm 包管理"] L2 --> L2_4["健康检查, 滚动更新"] L3["🌳 高级阶段
2-3 个月"] L2 --> L3 L3 --> L3_1["HPA, VPA 自动扩缩容"] L3 --> L3_2["RBAC 权限管理"] L3 --> L3_3["Prometheus 监控体系"] L3 --> L3_4["CI/CD 集成 (ArgoCD)"] L4["🏔️ 专家阶段
3-6 个月"] L3 --> L4 L4 --> L4_1["Service Mesh (Istio)"] L4 --> L4_2["Operator 开发"] L4 --> L4_3["多集群管理"] L4 --> L4_4["K8s 源码阅读"] L5["🌌 放弃阶段"] L4 --> L5 L5 --> L5_1["老板说要学 Serverless 了..."] style L1 fill:#e8f5e9 style L2 fill:#c8e6c9 style L3 fill:#a5d6a7 style L4 fill:#81c784 style L5 fill:#ffcdd2

结语:从入门到放弃,再到不得不捡起来

K8s 的学习曲线长这样:

graph LR A["🤩 K8s 好酷!
kubectl get pods"] --> B["📚 概念好多..."] B --> C["📝 YAML 好长..."] C --> D["🔧 为什么 Pod 是 Pending?"] D --> E["😱 CrashLoopBackOff!!!"] E --> F["💀 网络不通 + 存储丢了"] F --> G["🪦 想放弃了..."] G --> H["😤 不行,简历上已经写了"] H --> I["🧠 突然顿悟声明式的美"] I --> J["🏆 真正掌握 K8s"] J --> K["😭 老板说要学 Service Mesh..."] K --> L["🌀 Istio 从入门到放弃(预告)"]

记住:没有人真正"精通" Kubernetes,我们都只是在不同阶段的放弃与坚持之间反复横跳。

而最终你会发现:放弃 K8s 最大的障碍,是你的简历上已经写了"熟练掌握 Kubernetes"。


📝 关于本文

这是「从入门到放弃」系列的又一力作。如果你从 Docker 篇一路读到这里还没放弃,说明你是一个内心极其强大的人。

更多技术分享请访问 underestimated.cn

系列回顾: Docker 从入门到放弃 → Kubernetes 从入门到放弃(本篇) → Istio 从入门到放弃(敬请期待)


最后的最后,保命命令升级版:

# Docker 版
alias yolo-docker='docker system prune -a --volumes -f'

# K8s 版(请勿在生产环境执行)
alias yolo-k8s='kubectl delete all --all -A && echo "☸️ Cluster is clean. Your career might not be."'

# 终极版
alias yolo='yolo-docker && yolo-k8s && echo "🔥 You are now free. And unemployed."'

用之前请三思。用之后请更新简历。

评论区
暂无评论
avatar