windows k8s准备

下载启动minikube

https://minikube.sigs.k8s.io/docs/start/?arch=%2Fwindows%2Fx86-64%2Fstable%2F.exe+download

校验

minikube version

安装 kubectl

minikube kubectl

启动

minikube start

输出:
😄  Microsoft Windows 11 Pro For Workstations 25H2 上的 minikube v1.38.1
E0419 15:07:39.079696   17276 start.go:846] api.Load failed for minikube: filestore "minikube": Docker machine "minikube" does not exist. Use "docker-machine ls" to list machines. Use "docker-machine create" to add a new one.
✨  根据现有的配置文件使用 docker 驱动程序
👍  在集群中 "minikube" 启动节点 "minikube" primary control-plane
🚜  正在拉取基础镜像 v0.0.50 ...
    > gcr.io/k8s-minikube/kicbase...:  519.58 MiB / 519.58 MiB  100.00% 6.23 Mi
🔥  创建 docker container(CPU=2,内存=6000MB)...
🐳  正在 Docker 29.2.1 中准备 Kubernetes v1.35.1…
🔗  配置 bridge CNI (Container Networking Interface) ...
🔎  正在验证 Kubernetes 组件...
    ▪ 正在使用镜像 gcr.io/k8s-minikube/storage-provisioner:v5
🌟  启用插件: storage-provisioner, default-storageclass
🏄  完成!kubectl 现在已配置,默认使用"minikube"集群和"default"命名空间

0

架构:集群里有什么

# 确认 minikube 状态
minikube status

# 确认 kubectl 能连上集群
kubectl cluster-info

# 看节点(minikube 是单节点)
kubectl get nodes

# 看节点详情,架构/OS/容器运行时都在这里
kubectl get nodes -o wide
kubectl get pods -n kube-system
---
PS C:\Users\20881> kubectl get pods -n kube-system
NAME                               READY   STATUS    RESTARTS   AGE
coredns-7d764666f9-sssrx           1/1     Running   0          20m
etcd-minikube                      1/1     Running   0          20m
kube-apiserver-minikube            1/1     Running   0          20m
kube-controller-manager-minikube   1/1     Running   0          20m
kube-proxy-ljkbz                   1/1     Running   0          20m
kube-scheduler-minikube            1/1     Running   0          20m
storage-provisioner                1/1     Running   0          20m
Pod 名 是什么 干什么
etcd-minikube 数据库 存储集群所有状态(唯一持久化的地方)
kube-apiserver-minikube API Server 所有操作的入口,kubectl 就是在跟它说话
kube-scheduler-minikube 调度器 决定新 Pod 去哪个节点跑
kube-controller-manager-minikube 控制器管理器 维持期望状态(比如 Pod 少了就补)
kube-proxy-xxx 网络代理 每个节点上的,维护 Service 的网络规则
coredns-xxx DNS 集群内部域名解析
storage-provisioner 存储供给 minikube 专属,自动创建 PV
你 kubectl → API Server 收到 → 写入 etcd → Controller Manager 发现差异 → Scheduler 决定节点 → kubelet 拉起容器

kubectl 基本命令

创建资源

# 命令式(快速试验用)
kubectl run nginx-test --image=nginx:alpine --port=80

# 看 Pod 状态(-w 是 watch,实时刷新)
kubectl get pods -w

---
PS C:\Users\20881> kubectl run nginx-test --image=nginx:alpine --port=80
pod/nginx-test created
PS C:\Users\20881>
PS C:\Users\20881> kubectl get pods -w
NAME         READY   STATUS              RESTARTS   AGE
nginx-test   0/1     ContainerCreating   0          7s
---

# 等 Running 之后 Ctrl+C 退出 watch
---
PS C:\Users\20881> kubectl get pods -w
NAME         READY   STATUS              RESTARTS   AGE
nginx-test   0/1     ContainerCreating   0          7s
nginx-test   1/1     Running             0          21s
---

apply 声明式管理(生产方式)

# API 版本,v1 是 Pod 的稳定版本
apiVersion: v1
# 资源类型为 Pod(Kubernetes 中最小的可部署单元)
kind: Pod
metadata:
  # Pod 的名称,集群内唯一标识
  name: nginx-declarative
  # 标签,用于分类和选择,可被 Service、Deployment 等资源引用
  labels:
    app: nginx      # 应用名称标签
    env: learning   # 环境标签,表示这是学习/测试环境
spec:
  # 容器列表,一个 Pod 可以包含多个容器(共享网络和存储)
  containers:
  # 第一个容器配置
  - name: nginx             # 容器名称,Pod 内唯一
    image: nginx:alpine     # 容器镜像,使用轻量级的 Alpine 版本
    ports:
    # 容器暴露的端口,仅作声明,不实际开放(需配合 Service)
    - containerPort: 80     # Nginx 默认 HTTP 端口
    resources:
      # 资源请求:调度时保证预留这些资源,决定 Pod 能运行在哪个节点
      requests:
        memory: "64Mi"      # 请求 64 MB 内存(调度依据)
        cpu: "100m"         # 请求 0.1 核 CPU(100 milli-cores)
      # 资源限制:硬性上限,容器实际使用不能超过
      limits:
        memory: "128Mi"     # 内存上限 128MB,超过会被系统 OOMKilled
        cpu: "200m"         # CPU 上限 0.2 核,可短暂突增但会被节流限制
# 应用(创建或更新,幂等)
kubectl apply -f nginx-pod.yaml

# 修改 yaml 里的 image 为 nginx:1.25,再 apply,看到 configured
# kubectl apply -f nginx-pod.yaml

# 用标签筛选查询
kubectl get pods -l app=nginx
kubectl get pods -l env=learning

看资源详情

# describe 看事件和配置,Pod 起不来时第一个看这里
kubectl describe pod nginx-test

---
Name:             nginx-test
Namespace:        default
Priority:         0
Service Account:  default
Node:             minikube/192.168.49.2
Start Time:       Sun, 19 Apr 2026 15:40:35 +0800
Labels:           run=nginx-test
Annotations:      <none>
Status:           Running
IP:               10.244.0.3
IPs:
  IP:  10.244.0.3
Containers:
  nginx-test:
    Container ID:   docker://c4bc198ac3abb8d21a9bf7d3ee458f774a65ef35c6b0bcdb0ee9edfc6f1adc1d
    Image:          nginx:alpine
    Image ID:       docker-pullable://nginx@sha256:5616878291a2eed594aee8db4dade5878cf7edcb475e59193904b198d9b830de
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Sun, 19 Apr 2026 15:40:55 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-mw48f (ro)
---

# 重点看底部的 Events 区域:
# 能看到 Scheduled → Pulling → Pulled → Created → Started 整个流程
---
Events:
  Type    Reason     Age    From               Message
  ----    ------     ----   ----               -------
  Normal  Scheduled  2m19s  default-scheduler  Successfully assigned default/nginx-test to minikube
  Normal  Pulling    2m19s  kubelet            Pulling image "nginx:alpine"
  Normal  Pulled     2m     kubelet            Successfully pulled image "nginx:alpine" in 19.296s (19.297s including waiting). Image size: 62181394 bytes.
  Normal  Created    119s   kubelet            Container created
  Normal  Started    119s   kubelet            Container started
---

查看日志

# 看当前日志
kubectl logs nginx-test

# 实时跟踪(类似 tail -f)
kubectl logs nginx-test -f

# 如果 Pod 崩溃重启过,看上一个容器的日志
kubectl logs nginx-test --previous

进入容器

# 进入容器执行交互式 shell
kubectl exec -it nginx-test -- /bin/sh

---
kubectl exec -it nginx-test -- /bin/sh
/ # ls
bin                   docker-entrypoint.sh  lib                   opt                   run                   sys                   var
dev                   etc                   media                 proc                  sbin                  tmp
docker-entrypoint.d   home                  mnt                   root                  srv                   usr
/ #号 
---

# 进去之后可以:
ls /etc/nginx/          # 看配置
curl localhost          # 测试服务
env                     # 看环境变量
exit                    # 出来

端口转发(本地访问服务集群)

# 把本地 8080 映射到 Pod 的 80
kubectl port-forward pod/nginx-test 8080:80
---
PS C:\Users\20881> kubectl port-forward pod/nginx-test 8080:80
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080
---

# 然后浏览器打开 http://localhost:8080 能看到 nginx 欢迎页
# Ctrl+C 断开

导出 YAML(理解资源结构)

# 看这个 Pod 的完整 YAML 定义
kubectl get pod nginx-test -o yaml

---
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2026-04-19T07:40:35Z"
  generation: 1
spec:
  containers:
  - image: nginx:alpine
    imagePullPolicy: IfNotPresent
    name: nginx-test
    ports:
    - containerPort: 80
      protocol: TCP
    resources: {}
status:
  conditions:
  - lastProbeTime: null
    lastTransitionTime: "2026-04-19T07:40:56Z"
    observedGeneration: 1
---

# 只看你关心的字段(用 jsonpath)
kubectl get pod nginx-test -o jsonpath='{.status.podIP}'

删除资源

# 删除单个 Pod
kubectl delete pod nginx-test

# 用文件删除(推荐)
kubectl delete -f nginx-pod.yaml

# 查看所有 namespace 的 Pod
kubectl get pods -A

k9s

scoop install k9s

:pod 看 Pod,l 看日志,e 编辑,d describe,ctrl+k 删除,/ 搜索,? 看所有快捷键。

1

Pod → ReplicaSet → Deployment 三层关系

Deployment        # 你操作的层,管"怎么更新"
    └── ReplicaSet    # 管"维持几个副本"
            └── Pod   # 实际跑容器的单元
直接用 Pod 的问题: Pod 死了没人拉起来,没法扩容,没法滚动更新。所以实际上你几乎永远不直接创建 Pod,而是创建 Deployment。

创建第一个 Deployment

deployment-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-demo
  labels:
    app: web-demo
spec:
  replicas: 3                          # 维持 3 个 Pod 副本
  selector:                            # 找到它管的pod的方法
    matchLabels:
      app: web-demo                    # 通过这个标签找到它管的 Pod
  strategy:                            # 更新策略,更新时能多出来几个,多少不可用
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                      # 更新时最多多出 1 个 Pod
      maxUnavailable: 1                # 更新时最多有 1 个 Pod 不可用
  template:                            # Pod 模板,下面和写 Pod yaml 一样
    metadata:                          # pod元数据
      labels:
        app: web-demo                  # 必须和 selector.matchLabels 一致
    spec:
      containers:
      - name: web
        image: nginx:1.24-alpine
        ports:
        - containerPort: 80
        resources:                     # 保证预留的资源(调度依据)
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:                      # 超过这些会被kill掉
            memory: "128Mi"
            cpu: "200m"
kubectl apply -f deployment-demo.yaml

# 看三层结构同时存在
kubectl get deployment
kubectl get replicaset
kubectl get pods

# 看完整关系(rs 是 replicaset 缩写)
kubectl get all -l app=web-demo
PS C:\Users\20881\Documents> kubectl apply -f deployment-demo.yaml
deployment.apps/web-demo created

PS C:\Users\20881\Documents> kubectl get deployment
NAME       READY   UP-TO-DATE   AVAILABLE   AGE
web-demo   0/3     3            0           9s
PS C:\Users\20881\Documents> kubectl get replicaset
NAME                  DESIRED   CURRENT   READY   AGE
web-demo-79766649db   3         3         1       20s

PS C:\Users\20881\Documents> kubectl get pods
NAME                        READY   STATUS              RESTARTS   AGE
nginx-test                  1/1     Running             0          31m
web-demo-79766649db-4g6v2   0/1     ContainerCreating   0          27s
web-demo-79766649db-bmp8k   1/1     Running             0          27s
web-demo-79766649db-vcb88   1/1     Running             0          27s
PS C:\Users\20881\Documents> kubectl get all -l app=web-demo
NAME                            READY   STATUS    RESTARTS   AGE
pod/web-demo-79766649db-4g6v2   1/1     Running   0          45s
pod/web-demo-79766649db-bmp8k   1/1     Running   0          45s
pod/web-demo-79766649db-vcb88   1/1     Running   0          45s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/web-demo   3/3     3            3           45s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/web-demo-79766649db   3         3         3       45s
# 手动删一个 Pod,看它自动重建
kubectl get pods   # 记住一个 Pod 名字,比如 web-demo-xxx-yyy
kubectl delete pod web-demo-xxx-yyy

# 立刻 watch,会看到新 Pod 被创建
kubectl get pods -w

结果

PS C:\Users\20881\Documents> kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
nginx-test                  1/1     Running   0          33m
web-demo-79766649db-4g6v2   1/1     Running   0          2m4s
web-demo-79766649db-bmp8k   1/1     Running   0          2m4s
web-demo-79766649db-vcb88   1/1     Running   0          2m4s
PS C:\Users\20881\Documents> kubectl delete pod web-demo-79766649db-vcb88
pod "web-demo-79766649db-vcb88" deleted from default namespace
PS C:\Users\20881\Documents> kubectl get pods -w
NAME                        READY   STATUS    RESTARTS   AGE
nginx-test                  1/1     Running   0          34m
web-demo-79766649db-4g6v2   1/1     Running   0          3m7s
web-demo-79766649db-bmp8k   1/1     Running   0          3m7s
web-demo-79766649db-h6rrr   1/1     Running   0          3s

ReplicaSet (deployment-demo.yaml中的replica参数)发现实际数量 < 期望数量,立刻补一个,这就是控制器模式。

滚动更新(Rolling Update)

直接改 yaml(推荐,可追踪)

把 deployment-demo.yaml 里的镜像改为 nginx:1.25-alpine,

kubectl apply -f deployment-demo.yaml

# 立刻 watch 更新过程
kubectl rollout status deployment/web-demo

# 同时另开一个终端看 Pod 变化
kubectl get pods -w
PS C:\Users\20881\Documents> kubectl get pods -w
NAME                        READY   STATUS              RESTARTS   AGE
nginx-test                  1/1     Running             0          38m
web-demo-79766649db-4g6v2   1/1     Running             0          7m35s
web-demo-79766649db-bmp8k   1/1     Running             0          7m35s
web-demo-f7b789497-bwvr7    0/1     ContainerCreating   0          1s
web-demo-f7b789497-xhklk    0/1     ContainerCreating   0          1s

你会看到新 Pod 起来、旧 Pod 销毁,交替进行,始终保证有 Pod 在跑——这就是滚动更新。

命令行直接改镜像

kubectl set image deployment/web-demo web=nginx:1.26-alpine

# 查看更新过程
kubectl rollout status deployment/web-demo

查看更新历史

# 查看版本历史(默认保留 10 个)
kubectl rollout history deployment/web-demo

# 看某个版本的详情
kubectl rollout history deployment/web-demo --revision=2
PS C:\Users\20881\Documents> kubectl rollout history deployment/web-demo
deployment.apps/web-demo
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>

回滚

# 回滚到上一个版本
kubectl rollout undo deployment/web-demo

# 回滚到指定版本
kubectl rollout undo deployment/web-demo --to-revision=1

# 验证当前镜像版本
kubectl describe deployment web-demo | grep Image

扩容/缩容

# 手动扩到 5 个副本
kubectl scale deployment web-demo --replicas=5
kubectl get pods -w

# 缩回 2 个
kubectl scale deployment web-demo --replicas=2

三种探针

探针解决的问题:

  • Liveness:容器还活着吗?不活了就重启
  • Readiness:容器准备好接流量了吗?没准备好就从 Service 摘掉
  • Startup:容器启动完了吗?启动期间不跑 Liveness 检测(慢启动应用专用) deployment-probes.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-probes
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web-probes
  template:
    metadata:
      labels:
        app: web-probes
    spec:
      containers:
      - name: web
        image: nginx:alpine
        ports:
        - containerPort: 80
        
        # 启动探针:给容器最多 30s 启动时间
        startupProbe:
          httpGet:
            path: /
            port: 80
          failureThreshold: 6       # 失败 6 次
          periodSeconds: 5          # 每 5s 检查一次,共 30s

        # 存活探针:检测容器是否需要重启
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5    # 启动后 5s 开始检测
          periodSeconds: 10         # 每 10s 检查一次
          failureThreshold: 3       # 连续失败 3 次才重启

        # 就绪探针:检测是否能接收流量
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 3
          periodSeconds: 5
          failureThreshold: 2       # 连续失败 2 次从 Service 摘掉
        
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"
kubectl apply -f deployment-probes.yaml
kubectl get pods -w

# 用 describe 看探针配置是否生效
kubectl describe pod -l app=web-probes | grep -A 10 "Liveness\|Readiness\|Startup"

模拟探针失败

# 进入一个 Pod,手动删掉 nginx 的 index.html,让探针返回 404
kubectl exec -it $(kubectl get pod -l app=web-probes -o name | head -1) -- rm /usr/share/nginx/html/index.html

# 观察 Pod 状态变化:先 Running,后 Restart
kubectl get pods -w

# 看 describe 里的 Events,能看到探针失败和重启记录
kubectl describe pod -l app=web-probes
PS C:\Users\20881\Documents> kubectl exec -it web-probes-7c7444d948-fnw4m -- rm /usr/share/nginx/html/index.html
PS C:\Users\20881\Documents> kubectl get pods -w
NAME                          READY   STATUS    RESTARTS   AGE
nginx-test                    1/1     Running   0          50m
web-demo-55d8f64484-2qvr9     1/1     Running   0          9m15s
web-demo-55d8f64484-7hwrb     1/1     Running   0          8m51s
web-demo-55d8f64484-f2dwr     1/1     Running   0          9m15s
web-probes-7c7444d948-4zrkn   1/1     Running   0          2m58s
web-probes-7c7444d948-fnw4m   0/1     Running   0          2m58s
web-probes-7c7444d948-fnw4m   0/1     Running   1 (0s ago)   3m11s
web-probes-7c7444d948-fnw4m   0/1     Running   1 (4s ago)   3m15s
web-probes-7c7444d948-fnw4m   0/1     Running   1 (9s ago)   3m20s
web-probes-7c7444d948-fnw4m   1/1     Running   1 (9s ago)   3m20s
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  4m11s                default-scheduler  Successfully assigned default/web-probes-7c7444d948-fnw4m to minikube
  Warning  Unhealthy  61s (x3 over 81s)    kubelet            Liveness probe failed: HTTP probe failed with statuscode: 403
  Warning  Unhealthy  61s (x6 over 80s)    kubelet            Readiness probe failed: HTTP probe failed with statuscode: 403
  Normal   Killing    61s                  kubelet            Container web failed liveness probe, will be restarted
  Normal   Pulled     60s (x2 over 4m10s)  kubelet            Container image "nginx:alpine" already present on machine and can be accessed by the pod
  Normal   Created    60s (x2 over 4m10s)  kubelet            Container created
  Normal   Started    60s (x2 over 4m10s)  kubelet            Container started

Init Container

Init Container 在主容器启动前按顺序执行,常见用途:

  • 等待依赖服务就绪
  • 初始化配置文件
  • 做数据库 migration

deployment-initcontainer.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-init
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web-init
  template:
    metadata:
      labels:
        app: web-init
    spec:
      # Init 容器:按顺序执行,全部成功主容器才启动
      initContainers:
      - name: init-content
        image: busybox:1.35
        command:
        - sh
        - -c
        - |
          echo "Init: 开始准备内容..."
          echo "<h1>由 Init Container 生成于 $(date)</h1>" > /work/index.html
          echo "Init: 内容准备完毕"
        volumeMounts:
        - name: web-content
          mountPath: /work

      - name: init-wait          # 第二个 init,模拟等待依赖
        image: busybox:1.35
        command:
        - sh
        - -c
        - |
          echo "Init: 检查依赖..."
          sleep 3
          echo "Init: 依赖就绪,主容器可以启动"

      # 主容器:使用 init 容器生成的内容
      containers:
      - name: web
        image: nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: web-content
          mountPath: /usr/share/nginx/html   # 挂到 nginx 默认目录
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"

      volumes:
      - name: web-content
        emptyDir: {}              # 临时卷,Init 和主容器共享
kubectl apply -f deployment-initcontainer.yaml

# watch 观察:先看到 Init:0/2 → Init:1/2 → Init:2/2 → Running
kubectl get pods -w

# 验证 init 生成的内容被主容器使用
kubectl port-forward deployment/web-init 8080:80
# 浏览器打开 http://localhost:8080,看到 init 写的 html
PS C:\Users\20881\Documents> kubectl get pods -w
NAME                          READY   STATUS     RESTARTS        AGE
nginx-test                    1/1     Running    0               55m
web-demo-55d8f64484-2qvr9     1/1     Running    0               13m
web-demo-55d8f64484-7hwrb     1/1     Running    0               13m
web-demo-55d8f64484-f2dwr     1/1     Running    0               13m
web-init-557c779d7f-p5qxf     0/1     Init:0/2   0               2s
web-probes-7c7444d948-4zrkn   1/1     Running    0               7m31s
web-probes-7c7444d948-fnw4m   1/1     Running    1 (4m20s ago)   7m31s

Sidecar 模式

Sidecar 是和主容器同时运行的辅助容器,常见用途:日志采集、流量代理(Istio)、配置热更新。

创建 deployment-sidecar.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-sidecar
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web-sidecar
  template:
    metadata:
      labels:
        app: web-sidecar
    spec:
      containers:
      # 主容器:nginx 持续写访问日志
      - name: web
        image: nginx:alpine
        ports:
        - containerPort: 80
        volumeMounts:
        - name: log-vol
          mountPath: /var/log/nginx
        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"

      # Sidecar:实时读取主容器的日志
      - name: log-collector
        image: busybox:1.35
        command:
        - sh
        - -c
        - |
          echo "Sidecar 启动,等待日志..."
          tail -F /logs/access.log 2>/dev/null || \
          while true; do
            echo "[$(date)] 等待日志文件生成..."
            sleep 5
          done
        volumeMounts:
        - name: log-vol
          mountPath: /logs            # 和主容器共享同一个卷
        resources:
          requests:
            memory: "32Mi"
            cpu: "50m"
          limits:
            memory: "64Mi"
            cpu: "100m"

      volumes:
      - name: log-vol
        emptyDir: {}
kubectl apply -f deployment-sidecar.yaml
kubectl get pods   # 注意 READY 列显示 2/2,表示两个容器都在跑

# 给主容器发几个请求产生日志
kubectl port-forward deployment/web-sidecar 8080:80 &
curl http://localhost:8080
curl http://localhost:8080/about

# 看 sidecar 收集到的日志(-c 指定容器名)
kubectl logs -f deployment/web-sidecar -c log-collector

# 也可以看主容器日志
kubectl logs -f deployment/web-sidecar -c web
PS C:\Users\20881\Documents> kubectl logs -f deployment/web-sidecar -c log-collector
Sidecar 启动等待日志...
127.0.0.1 - - [19/Apr/2026:08:40:55 +0000] "GET / HTTP/1.1" 200 896 "-" "Mozilla/5.0 (Windows NT; Windows NT 10.0; zh-CN) WindowsPowerShell/5.1.26100.8115" "-"
127.0.0.1 - - [19/Apr/2026:08:41:07 +0000] "GET / HTTP/1.1" 200 896 "-" "Mozilla/5.0 (Windows NT; Windows NT 10.0; zh-CN) WindowsPowerShell/5.1.26100.8115" "-"
127.0.0.1 - - [19/Apr/2026:08:41:27 +0000] "GET /about HTTP/1.1" 404 153 "-" "Mozilla/5.0 (Windows NT; Windows NT 10.0; zh-CN) WindowsPowerShell/5.1.26100.8115" "-"

2

为什么需要 ConfigMap / Secret

没有它们之前,配置要么硬编码在镜像里(改配置要重建镜像),要么写死在 yaml 里(不同环境要维护多份 yaml)。

有了 ConfigMap / Secret:
镜像  ←──────────────────────  不变
yaml  ←──────────────────────  不变
ConfigMap/Secret  ←──────────  按环境替换

ConfigMap

从 yaml 文件创建(推荐,可版本控制)

configmap-demo.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  # 单行键值,适合简单配置
  APP_ENV: "production"
  APP_PORT: "8080"
  LOG_LEVEL: "info"
  MAX_CONNECTIONS: "100"

  # 多行值,适合配置文件
  nginx.conf: |
    server {
        listen 80;
        server_name localhost;
        location / {
            root /usr/share/nginx/html;
            index index.html;
        }
        location /health {
            return 200 'ok';
            add_header Content-Type text/plain;
        }
    }

  app-settings.json: |
    {
      "environment": "production",
      "logLevel": "info",
      "features": {
        "darkMode": true,
        "betaApi": false
      }
    }
# 启动一个configmap,其中的值就是上面的data
kubectl apply -f configmap-demo.yaml 

# 查看
kubectl get configmap
kubectl get cm          # cm 是缩写

# 看内容
kubectl describe cm app-config

# 看完整 yaml(data 字段里都有)
kubectl get cm app-config -o yaml

从文件/命令行直接创建(临时测试用)

# 从字面量创建
kubectl create cm quick-config --from-literal=COLOR=blue --from-literal=SIZE=large

# 从文件创建(key 是文件名,value 是文件内容)
echo "hello from file" > myconfig.txt
kubectl create cm file-config --from-file=myconfig.txt

kubectl describe cm quick-config
kubectl describe cm file-config

ConfigMap 用法一:注入环境变量

cm-env-demo.yaml

# API 版本:apps/v1 用于 Deployment 资源
apiVersion: apps/v1
kind: Deployment                    # 资源类型:Deployment(管理 Pod 的控制器)
metadata:
  name: cm-env-demo                 # Deployment 名称,用于 kubectl 操作识别
spec:
  replicas: 1                       # 只运行 1 个 Pod 副本
  selector:
    matchLabels:
      app: cm-env-demo              # 选择器:通过此标签匹配管理的 Pod
  template:
    metadata:
      labels:
        app: cm-env-demo            # Pod 标签:必须与 selector.matchLabels 一致
    spec:
      containers:
      - name: app                   # 容器名称
        image: busybox:1.35         # 轻量级 Linux 工具镜像(用于演示)
        
        # 容器启动命令:循环打印匹配 APP_/LOG_/MAX_ 开头的环境变量
        command: ["sh", "-c", "while true; do echo '环境变量:'; env | grep -E 'APP_|LOG_|MAX_'; sleep 5; done"]
        
        # ==========================================
        # 环境变量注入方式
        # ==========================================
        
        # 【方式 A】逐个指定:从 ConfigMap 提取特定 key 映射为环境变量
        # 优点:可自定义环境变量名,可挑选需要的 key
        env:
        - name: APP_ENV               # 容器内环境变量名(可自定义)
          valueFrom:
            configMapKeyRef:
              name: app-config        # 来源 ConfigMap 的名称
              key: APP_ENV            # ConfigMap 中的 key 名
              # 可选:指定 key 不存在时的行为
              # optional: true        # true=不存在则忽略,false=不存在则 Pod 启动失败(默认)
        
        - name: LOG_LEVEL             # 另一个环境变量
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: LOG_LEVEL

        # 【方式 B】批量注入:一次性导入整个 ConfigMap 的所有 key-value
        # 效果:ConfigMap 中每个 key 都变成同名的环境变量
        # 优点:ConfigMap 增减 key 无需修改 Deployment,自动同步
        envFrom:
        - configMapRef:
            name: app-config          # 导入 app-config 的所有 key 为环境变量
            # optional: true          # 可选:ConfigMap 不存在时是否忽略(默认 false)

        resources:
          requests:
            memory: "32Mi"            # 请求 32MB 内存(调度依据)
            cpu: "50m"                  # 请求 0.05 核 CPU
          limits:
            memory: "64Mi"            # 内存上限 64MB
            cpu: "100m"                 # CPU 上限 0.1 核
kubectl apply -f cm-env-demo.yaml

# 看日志,确认环境变量注入成功
kubectl logs -l app=cm-env-demo -f

# 或者直接进去验证
kubectl exec -it deployment/cm-env-demo -- env | grep -E 'APP_|LOG_|MAX_'
PS C:\Users\20881\Documents> kubectl logs -l app=cm-env-demo -f
环境变量:
LOG_LEVEL=info
APP_PORT=8080
MAX_CONNECTIONS=100
APP_ENV=production
环境变量:
LOG_LEVEL=info
APP_PORT=8080

ConfigMap 用法二:挂载为文件

cm-volume-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: cm-volume-demo          # Deployment 名称
spec:
  replicas: 1                   # 只运行 1 个副本
  selector:
    matchLabels:
      app: cm-volume-demo       # 选择器,关联 Pod 标签
  template:
    metadata:
      labels:
        app: cm-volume-demo     # 必须与 selector 匹配
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80
        
        # ==========================================
        # 卷挂载声明:把 volumes 定义的数据挂到容器内
        # ==========================================
        volumeMounts:
        # 【方式一】挂载整个 ConfigMap 目录
        # 效果:/etc/nginx/conf.d/ 目录下会出现 ConfigMap 中指定的文件
        - name: config-vol              # 引用下面 volumes 中定义的卷名
          mountPath: /etc/nginx/conf.d  # 容器内的挂载点(Nginx 会读取此目录的 .conf)
          readOnly: true                # 只读挂载(ConfigMap 本身不可修改)

        # 【方式二】挂载单个文件(使用 subPath)
        # 效果:只把 ConfigMap 中的 app-settings.json 这一个 key 挂载为单独文件
        # 优点:不会覆盖 /app/config/ 目录下原有的其他文件
        - name: settings-vol
          mountPath: /app/config/settings.json  # 挂载为具体文件路径,而非目录
          subPath: app-settings.json            # 只提取 ConfigMap 中的这个 key
          readOnly: true

        resources:
          requests:
            memory: "64Mi"
            cpu: "100m"
          limits:
            memory: "128Mi"
            cpu: "200m"

      # ==========================================
      # 卷定义:声明数据来源(这里是 ConfigMap)
      # ==========================================
      volumes:
      # 卷 1:整目录挂载的数据源
      - name: config-vol              # 卷名称,对应 volumeMounts.name
        configMap:
          name: app-config            # 引用名为 "app-config" 的 ConfigMap 资源
          items:                      # 可选:只挂载指定的 key,并可重命名文件
          - key: nginx.conf           # ConfigMap 中的 key
            path: default.conf        # 挂载到容器后的文件名

      # 卷 2:单文件挂载的数据源
      - name: settings-vol            # 卷名称,对应第二个 volumeMounts
        configMap:
          name: app-config            # 同样引用 "app-config" 这个 ConfigMap
          # 这里没有 items,表示挂载该 ConfigMap 的所有 key
          # 但因为 volumeMounts 用了 subPath: app-settings.json
          # 所以最终只把 app-settings.json 这一个文件挂进去
kubectl apply -f cm-volume-demo.yaml

# 验证配置文件已经挂进去
kubectl exec -it deployment/cm-volume-demo -- cat /etc/nginx/conf.d/default.conf
kubectl exec -it deployment/cm-volume-demo -- cat /app/config/settings.json

# 测试自定义 nginx 配置生效(访问 /health 路由)
kubectl port-forward deployment/cm-volume-demo 8080:80
# 新开终端
curl http://localhost:8080/health    # 应该返回 ok

ConfigMap 热更新

# 修改 ConfigMap 的值
kubectl edit cm app-config
# 把 LOG_LEVEL 从 info 改成 debug,保存退出

# 等约 60s(kubelet 同步周期),再检查挂载文件
kubectl exec -it deployment/cm-volume-demo -- cat /etc/nginx/conf.d/default.conf

关键区别:

  • 文件挂载:ConfigMap 更新后,挂载的文件会自动更新(约 60s)
  • 环境变量:ConfigMap 更新后,不会自动更新,必须重启 Pod 才生效

所以需要热更新的配置,优先用文件挂载。

Secret

Secret 和 ConfigMap 结构几乎一样,区别是:

  • 数据 Base64 编码存储(不是加密!只是编码)
  • kubectl get secret 不会直接显示值
  • 挂载到 Pod 后是明文(运行时解码)
  • 配合 RBAC 限制访问权限才真正安全 secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:
  name: app-secret
type: Opaque            # 通用类型,还有 kubernetes.io/tls、kubernetes.io/dockerconfigjson 等
stringData:             # 直接写明文,k8s 自动 base64 编码(推荐)
  DB_PASSWORD: "super-secret-password-123"
  DB_USERNAME: "admin"
  API_KEY: "sk-abcdef1234567890"
  
  db-config.env: |
    host=postgres-service
    port=5432
    database=myapp
    sslmode=require

stringData 写明文更方便,data 字段需要手动 base64 编码。实际项目里 Secret 的 yaml 不应该提交 git,应该用 Vault、SOPS 等工具管理。

kubectl apply -f secret-demo.yaml

# 查看(值是 base64,不直接显示明文)
kubectl get secret app-secret
kubectl describe secret app-secret    # 只显示字节数,不显示值

# 解码某个 key(排查用)
kubectl get secret app-secret -o jsonpath='{.data.DB_PASSWORD}' | base64 -d

# 或者看全部
kubectl get secret app-secret -o yaml
PS C:\Users\20881\Documents> kubectl describe secret app-secret
Name:         app-secret
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
API_KEY:        19 bytes
DB_PASSWORD:    25 bytes
DB_USERNAME:    5 bytes
db-config.env:  63 bytes

Secret 用法一:注入环境变量

secret-env-demo.yaml

# API 版本:Deployment 使用 apps/v1
apiVersion: apps/v1
kind: Deployment                    # 资源类型:Deployment 控制器
metadata:
  name: secret-env-demo             # Deployment 名称
spec:
  replicas: 1                       # 运行 1 个 Pod 副本
  selector:
    matchLabels:
      app: secret-env-demo          # 选择器:匹配带有此标签的 Pod
  template:
    metadata:
      labels:
        app: secret-env-demo        # Pod 标签:必须与 selector 匹配
    spec:
      containers:
      - name: app                   # 容器名称
        image: busybox:1.35         # 轻量级镜像,仅用于演示
        
        # 启动命令:打印从 Secret 注入的敏感环境变量
        # 注意:生产环境不要这样打印密码,仅用于学习验证!
        command:
        - sh
        - -c
        - |
          echo "=== 从 Secret 注入的环境变量 ==="
          echo "DB_USER: $DB_USERNAME"
          echo "DB_PASS: $DB_PASSWORD"
          echo "API_KEY: $API_KEY"
          sleep 3600                 # 休眠 1 小时保持 Pod 运行
        
        # ==========================================
        # 从 Secret 注入环境变量(敏感数据专用)
        # ==========================================
        env:
        # 变量 1:数据库用户名
        - name: DB_USERNAME           # 容器内环境变量名(可按需命名)
          valueFrom:
            secretKeyRef:             # 从 Secret 中取值(区别于 configMapKeyRef)
              name: app-secret        # Secret 资源名称
              key: DB_USERNAME        # Secret 中的 key 名
        
        # 变量 2:数据库密码
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: DB_PASSWORD
        
        # 变量 3:API 密钥
        - name: API_KEY
          valueFrom:
            secretKeyRef:
              name: app-secret
              key: API_KEY
              # optional: false       # 默认行为:Secret 或 key 不存在时 Pod 无法启动
        
        resources:
          requests:
            memory: "32Mi"
            cpu: "50m"
          limits:
            memory: "64Mi"
            cpu: "100m"
kubectl apply -f secret-env-demo.yaml
kubectl logs -l app=secret-env-demo

Secret 用法二:挂载为文件

secret-volume-demo.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: secret-volume-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: secret-volume-demo
  template:
    metadata:
      labels:
        app: secret-volume-demo
    spec:
      containers:
      - name: app
        image: busybox:1.35
        command:
        - sh
        - -c
        - |
          echo "=== Secret 文件挂载 ==="
          echo "文件列表:"
          ls -la /app/secrets/
          echo ""
          echo "db-config.env 内容:"
          cat /app/secrets/db-config.env
          echo ""
          echo "文件权限(注意是 0400):"
          stat /app/secrets/DB_PASSWORD
          sleep 3600
        volumeMounts:
        - name: secret-vol
          mountPath: /app/secrets
          readOnly: true            # Secret 挂载一定要加 readOnly
        resources:
          requests:
            memory: "32Mi"
            cpu: "50m"
          limits:
            memory: "64Mi"
            cpu: "100m"

      volumes:
      - name: secret-vol
        secret:
          secretName: app-secret
          defaultMode: 0400         # 文件权限:只有属主可读
kubectl apply -f secret-volume-demo.yaml
kubectl logs -l app=secret-volume-demo

3

Pod 是短暂的。它会因为更新、崩溃、扩缩容而不断被销毁和重建,每次重建 IP 都会变。如果服务 A 硬编码了服务 B 某个 Pod 的 IP,Pod 一重建连接就断了。

Service 解决的就是这个问题——它提供一个稳定的虚拟 IP(ClusterIP)和 DNS 名,背后通过标签选择器动态跟踪哪些 Pod 是健康的,自动做负载均衡。你可以把 Service 理解成一个"稳定的门牌号",Pod 来来去去,门牌号永远不变。

外部请求
    │
    ▼
[ Service ]  ←── 稳定的 ClusterIP + DNS 名
    │
    ├──▶ Pod A  (可能随时被替换)
    ├──▶ Pod B
    └──▶ Pod C

backend.yaml

# API 版本:apps/v1 用于 Deployment
apiVersion: apps/v1
kind: Deployment                    # 资源类型:Deployment 控制器
metadata:
  name: backend                     # Deployment 名称
spec:
  replicas: 3                       # 运行 3 个 Pod 副本(用于演示负载均衡)
  selector:
    matchLabels:
      app: backend                  # 选择器:匹配标签 app=backend 的 Pod
  template:
    metadata:
      labels:
        app: backend                # 必须与 selector.matchLabels 匹配
        tier: api                   # 额外标签,可用于 Service 选择或监控筛选
    spec:
      containers:
      - name: app                   # 容器名称
        # 镜像:HashiCorp 的 http-echo,一个简单的 HTTP 回显服务
        # 访问时会返回设置的 -text 内容,非常适合测试负载均衡
        image: hashicorp/http-echo:alpine
        
        # 启动参数:设置响应文本,$(MY_POD_NAME) 会被替换为实际 Pod 名
        # 效果:每个 Pod 返回 "Hello from backend-xxx-yyy",便于区分请求到了哪个 Pod
        args:
        - "-text=Hello from $(MY_POD_NAME)"  # 通过环境变量动态设置响应内容
        
        ports:
        - containerPort: 5678       # 容器暴露的端口(http-echo 默认监听 5678)
        
        # ==========================================
        # Downward API:将 Pod 自身信息注入环境变量
        # ==========================================
        env:
        - name: MY_POD_NAME         # 自定义环境变量名
          valueFrom:
            fieldRef:               # 从 Pod 字段引用值(区别于 configMapKeyRef/secretKeyRef)
              fieldPath: metadata.name    # 引用 Pod 的 metadata.name 字段(即 Pod 实际名称)
              # 其他可用字段:
              # - metadata.namespace      # Pod 所在命名空间
              # - metadata.uid            # Pod 的唯一 ID
              # - spec.nodeName          # Pod 调度到的节点名称
              # - status.podIP           # Pod 的 IP 地址
              # - status.hostIP          # 节点 IP 地址
        
        resources:
          requests:
            memory: "32Mi"
            cpu: "50m"
          limits:
            memory: "64Mi"
            cpu: "100m"
kubectl apply -f backend.yaml
kubectl get pods -l app=backend
# 等三个 Pod 都 Running 后继续

ClusterIP Service(集群内部通信)

ClusterIP 是最基础的 Service 类型,只在集群内部可达。适用于服务间通信,比如后端调数据库。 service-clusterip.yaml

apiVersion: v1
kind: Service
metadata:
  name: backend-svc          # 这个名字就是 DNS 名,其他 Pod 用它访问
spec:
  type: ClusterIP            # 默认类型,可以不写
  selector:
    app: backend             # 找所有带这个标签的 Pod,自动做负载均衡
  ports:
  - name: http
    port: 80                 # Service 对外暴露的端口
    targetPort: 5678         # 转发到 Pod 的这个端口 上面backend端口是5678
kubectl apply -f service-clusterip.yaml

# 查看 Service,注意 CLUSTER-IP 那列,这是虚拟 IP
kubectl get service backend-svc

# 看详情,注意 Endpoints 那行,列出了所有 Pod 的 IP
kubectl describe service backend-svc
PS C:\Users\20881\Documents> kubectl apply -f service-clusterip.yaml
service/backend-svc created
PS C:\Users\20881\Documents> kubectl get service backend-svc
NAME          TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
backend-svc   ClusterIP   10.103.135.109   <none>        80/TCP    8s
PS C:\Users\20881\Documents> kubectl describe service backend-svc
Name:                     backend-svc
Namespace:                default
Labels:                   <none>
Annotations:              <none>
Selector:                 app=backend
Type:                     ClusterIP
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.103.135.109
IPs:                      10.103.135.109
Port:                     http  80/TCP
TargetPort:               5678/TCP
Endpoints:                10.244.0.29:5678,10.244.0.30:5678,10.244.0.31:5678
Session Affinity:         None
Internal Traffic Policy:  Cluster
Events:                   <none>

现在验证负载均衡——我们起一个临时 Pod,在集群内部用 DNS 名访问 Service:

# 起一个临时的 curl Pod 进去测试
kubectl run curl-test --image=curlimages/curl:latest --rm -it --restart=Never -- sh

# 进去之后,多次访问 Service 的 DNS 名,观察 hostname 在变化
curl http://backend-svc
curl http://backend-svc
curl http://backend-svc
curl http://backend-svc

# 完整的 DNS 格式是:<service-name>.<namespace>.svc.cluster.local
curl http://backend-svc.default.svc.cluster.local

exit
PS C:\Users\20881\Documents> kubectl run curl-test --image=curlimages/curl:latest --rm -it --restart=Never -- sh
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
~ $ curl http://backend-svc
Hello from backend-b46484d4b-59jnc
~ $ curl http://backend-svc
Hello from backend-b46484d4b-8lvpb
~ $ curl http://backend-svc
Hello from backend-b46484d4b-59jnc

你应该能看到每次返回不同的 Pod 名字,这就是 kube-proxy 在做的轮询负载均衡。

理解 k8s DNS 规则: 每个 Service 创建后,CoreDNS 自动注册一条记录:<service-name>.<namespace>.svc.cluster.local。同一 namespace 内可以直接用短名 backend-svc,跨 namespace 就要用完整名。这是服务发现的基础。

NodePort Service(从集群外访问)

ClusterIP 只能集群内用。如果想从本机浏览器直接访问,需要 NodePort——它在每个节点上开一个端口(30000~32767 范围),外部流量打进来后转发给对应的 Pod。

创建 service-nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: backend-nodeport
spec:
  type: NodePort
  selector:
    app: backend
  ports:
  - name: http
    port: 80           # ClusterIP 端口(集群内访问用)
    targetPort: 5678   # Pod 端口
    nodePort: 30080    # 节点端口(不指定则随机分配 30000-32767)
kubectl apply -f service-nodeport.yaml
kubectl get service backend-nodeport

# minikube 需要用这个命令获取可访问的 URL
minikube service backend-nodeport --url

# 用返回的 URL 访问(多刷新几次看负载均衡)
curl <上面返回的 URL>

NodePort 的缺点很明显:端口范围受限,每个服务占一个端口,管理麻烦,生产环境很少直接用。它更多是个过渡,真正对外暴露用 Ingress。

Headless Service(StatefulSet 的好搭档)

Headless Service 是一个特殊变体,把 clusterIP 设为 None。这样 DNS 查询不会返回虚拟 IP,而是直接返回所有 Pod 的 IP 列表。适合需要直连特定 Pod 的场景,比如数据库集群、消息队列。

创建 service-headless.yaml

apiVersion: v1
kind: Service
metadata:
  name: backend-headless
spec:
  clusterIP: None        # 关键:没有虚拟 IP
  selector:
    app: backend
  ports:
  - port: 5678
kubectl apply -f service-headless.yaml

# 进临时 Pod 做 DNS 查询,看区别
kubectl run dns-test --image=busybox:1.35 --rm -it --restart=Never -- sh

# 查普通 Service:返回一个 ClusterIP
nslookup backend-svc

# 查 Headless Service:直接返回所有 Pod 的 IP
nslookup backend-headless

exit
PS C:\Users\20881\Documents> kubectl apply -f service-headless.yaml
service/backend-headless created
PS C:\Users\20881\Documents> kubectl run dns-test --image=busybox:1.35 --rm -it --restart=Never -- sh
All commands and output from this session will be recorded in container logs, including credentials and sensitive information passed through the command prompt.
If you don't see a command prompt, try pressing enter.
/ # nslookup backend-svc
Server:         10.96.0.10
Address:        10.96.0.10:53

** server can't find backend-svc.cluster.local: NXDOMAIN

** server can't find backend-svc.cluster.local: NXDOMAIN


Name:   backend-svc.default.svc.cluster.local
Address: 10.103.135.109

** server can't find backend-svc.svc.cluster.local: NXDOMAIN

** server can't find backend-svc.svc.cluster.local: NXDOMAIN

/ # nslookup backend-headless
Server:         10.96.0.10
Address:        10.96.0.10:53

** server can't find backend-headless.cluster.local: NXDOMAIN

** server can't find backend-headless.cluster.local: NXDOMAIN


** server can't find backend-headless.svc.cluster.local: NXDOMAIN

** server can't find backend-headless.svc.cluster.local: NXDOMAIN

Name:   backend-headless.default.svc.cluster.local
Address: 10.244.0.30
Name:   backend-headless.default.svc.cluster.local
Address: 10.244.0.29
Name:   backend-headless.default.svc.cluster.local
Address: 10.244.0.31

Ingress(生产级 HTTP 路由)

Ingress 本质上是一个七层(HTTP)反向代理的配置规则,它自己不做任何事,需要配合一个 Ingress Controller(实际干活的进程,通常是 nginx)才能工作。可以把它们的关系理解成:Ingress 是"路由规则配置文件",Ingress Controller 是"读取这份配置并执行的 nginx 进程"。

浏览器
  │
  ▼
Ingress Controller (nginx Pod)    ← 真正的流量入口
  │
  ├── /api/*    ──▶  backend-svc:80
  ├── /web/*    ──▶  frontend-svc:80
  └── /admin/*  ──▶  admin-svc:80
  • 安装ingress
# minikube 内置了 ingress addon,一行搞定
minikube addons enable ingress

# 等 ingress controller Pod 变成 Running(可能需要 1~2 分钟)
kubectl get pods -n ingress-nginx -w

# 确认 ingress controller 的 Service
kubectl get svc -n ingress-nginx
  • 再部署一个前端服务,方便演示路由分发 frontend.yaml
# ==========================================
# 第一部分:Deployment(部署应用)
# ==========================================
apiVersion: apps/v1
kind: Deployment                    # 资源类型:Deployment 控制器
metadata:
  name: frontend                    # Deployment 名称
spec:
  replicas: 2                       # 运行 2 个 Pod 副本(提供高可用)
  selector:
    matchLabels:
      app: frontend                 # 选择器:管理带有 app=frontend 标签的 Pod
  template:
    metadata:
      labels:
        app: frontend               # Pod 标签:必须与 selector 匹配
    spec:
      containers:
      - name: app                   # 容器名称
        # 使用 http-echo 镜像,一个简单的 HTTP 回显服务
        image: hashicorp/http-echo:alpine
        # 启动参数:设置响应文本,所有 Pod 统一返回 "I am the FRONTEND"
        args: ["-text=I am the FRONTEND"]
        ports:
        - containerPort: 5678       # 容器监听端口(http-echo 默认端口)
        resources:
          requests:
            memory: "32Mi"          # 请求 32MB 内存
            cpu: "50m"                # 请求 0.05 核 CPU
          limits:
            memory: "64Mi"          # 内存上限 64MB
            cpu: "100m"               # CPU 上限 0.1 核

---
# YAML 分隔符:上面和下面是两个独立的资源定义

# ==========================================
# 第二部分:Service(暴露服务)
# ==========================================
apiVersion: v1
kind: Service                       # 资源类型:Service(四层负载均衡)
metadata:
  name: frontend-svc                # Service 名称,集群内 DNS 可解析
spec:
  # 选择器:将流量转发给带有 app=frontend 标签的 Pod
  # 必须与 Deployment 的 selector.matchLabels 一致才能关联
  selector:
    app: frontend
  
  ports:
  - port: 80                        # Service 暴露的端口(集群内访问用)
    targetPort: 5678                # 转发到 Pod 的哪个端口(与 containerPort 对应)
    # protocol: TCP                 # 默认 TCP,可省略
    # name: http                    # 端口命名,可选
  
  # type 字段省略,默认为 ClusterIP(仅集群内部访问)
  # 其他可选值:NodePort(节点端口)、LoadBalancer(云负载均衡)、ExternalName(外部域名)
kubectl apply -f frontend.yaml
  • 创建 Ingress 规则
# API 版本:networking.k8s.io/v1 是 Ingress 的稳定版本
apiVersion: networking.k8s.io/v1
kind: Ingress                       # 资源类型:Ingress(七层 HTTP/HTTPS 路由)
metadata:
  name: app-ingress                 # Ingress 资源名称
  annotations:
    # Nginx Ingress Controller 专属注解:URL 重写规则
    # 作用:去掉匹配的路径前缀,再转发给后端
    # 示例:请求 /api/users  →  后端收到 /users(去掉了 /api)
    nginx.ingress.kubernetes.io/rewrite-target: /
    
    # 其他常用注解(本例未使用,供参考):
    # nginx.ingress.kubernetes.io/ssl-redirect: "true"      # 强制 HTTPS
    # nginx.ingress.kubernetes.io/rate-limit: "100"          # 限流
    # nginx.ingress.kubernetes.io/enable-cors: "true"      # 跨域支持

spec:
  # 指定 Ingress Controller 类型(必须匹配集群中实际安装的)
  # 常见值:nginx、haproxy、traefik、alb(AWS)、gce(GCP)
  ingressClassName: nginx           # 使用 Nginx Ingress Controller
  
  # 路由规则列表
  rules:
  - host: myapp.local               # 虚拟域名,浏览器/请求必须带这个 Host 头
                                    # 本地测试时需配置 /etc/hosts 指向集群入口 IP
    
    http:
      paths:
      # ==========================================
      # 路由规则 1:API 请求 → 后端服务
      # ==========================================
      - path: /api                  # 路径前缀匹配
        pathType: Prefix            # 匹配类型:前缀匹配(/api、/api/v1、/api/users 都匹配)
                                    # 其他类型:Exact(精确匹配)、ImplementationSpecific(依赖实现)
        backend:                    # 后端目标
          service:
            name: backend-svc       # 目标 Service 名称(对应之前的 backend Deployment)
            port:
              number: 80            # Service 的端口号(对应 Service.spec.ports.port)
      
      # ==========================================
      # 路由规则 2:Web 请求 → 前端服务
      # ==========================================
      - path: /web
        pathType: Prefix
        backend:
          service:
            name: frontend-svc      # 目标 Service 名称(对应之前的 frontend Deployment)
            port:
              number: 80
      
      # ==========================================
      # 路由规则 3:兜底规则 → 前端服务
      # ==========================================
      - path: /                     # 根路径,匹配所有未被上面规则匹配的请求
        pathType: Prefix
        backend:
          service:
            name: frontend-svc
            port:
              number: 80
        # 注意:Nginx Controller 中,/ 会匹配所有路径,但由于规则按顺序匹配,
        # 实际 /api 和 /web 会先被上面规则捕获,不会走到这里
kubectl apply -f ingress-demo.yaml

# 查看 Ingress,注意 ADDRESS 列(可能需要等几秒才有 IP)
kubectl get ingress
kubectl describe ingress app-ingress
# 访问根路径 → 前端
curl http://myapp.local/

# 访问 /api → 后端(多执行几次看负载均衡)
curl http://myapp.local/api
curl http://myapp.local/api
curl http://myapp.local/api

# 访问 /web → 前端
curl http://myapp.local/web
用户浏览器/客户端
       │
       ▼
┌─────────────────┐     ┌─────────────────────┐     ┌─────────────────┐
│   Ingress 入口   │────▶│  Nginx Controller   │────▶│   集群内部 Service │
│  (NodeIP/LoadIP)│     │   (Pod 形式运行)     │     │                 │
│   myapp.local   │     │                     │     │  ┌─────────────┐  │
└─────────────────┘     │  路由规则:           │     │  │ /api 请求   │──▶ backend-svc ──▶ backend Pod (3个)
                        │  /api/* → backend    │     │  │ 重写为 /*   │     (负载均衡)
                        │  /web/* → frontend   │     │  └─────────────┘
                        │  /*     → frontend   │     │  ┌─────────────┐
                        │  (去掉前缀后转发)       │────▶│  │ /web/* 请求 │──▶ frontend-svc ──▶ frontend Pod (2个)
                        │                     │     │  │ 重写为 /*   │     (负载均衡)
                        └─────────────────────┘     │  └─────────────┘
                                                    │  ┌─────────────┐
                                                    │  │ /* 兜底请求  │──▶ frontend-svc ──▶ frontend Pod
                                                    │  │ 直接转发    │
                                                    │  └─────────────┘
                                                    └─────────────────┘