下载启动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
│ │ 直接转发 │
│ └─────────────┘
└─────────────────┘