k8s 笔记
好的,我们来详细整理一下关于 Kubernetes Endpoints 资源的笔记。这份笔记将严格按照你的大纲,并力求概念通俗易懂、案例详尽、代码完整且包含输出解释。
Kubernetes Endpoints 资源深度解析
一、什么是 Endpoints 资源?
你可以把 Endpoints 资源理解为 Kubernetes 中的“服务地址簿”或“DNS 白名单”。它是一个核心的服务发现组件,充当了 Service (svc) 和 Pod 之间的桥梁。
具体来说:
Endpoints是一个 Kubernetes 资源对象:就像Pod,Service,Deployment一样,它也有自己的 YAML 定义和生命周期。- 核心功能是存储地址:它的主要数据是一个 IP 地址列表和对应的端口号列表。这些 IP 地址正是后端提供服务的
Pod的 IP。 - 由
Service管理和引用:每个Service资源都会自动关联一个同名的Endpoints资源(在大多数情况下)。Service收到请求后,会查看自己关联的Endpoints列表,然后将请求转发到列表中的一个或多个 IP 上。
工作流程简图:
外部请求 -> Service (例如:my-service) -> Endpoints (my-service) -> [Pod IP 1, Pod IP 2, ...]
|
v
后端应用 Pods
通俗比喻:
Service就像是一家公司的总机电话。Endpoints就是这家公司的员工通讯录,上面有各个部门员工的分机号(Pod IP)。- 当客户(外部请求)拨打总机(Service),接线员(kube-proxy)会查看通讯录(Endpoints),然后把电话转接到具体的员工(Pod)那里。
二、核心案例:Endpoints 的自动与手动管理
Endpoints 的创建和维护主要有两种模式:自动模式和手动模式。
案例 1:自动创建 Endpoints (最常见)
当你创建一个 Service 并为其指定 标签选择器(selector) 时,Kubernetes 会自动为你完成以下工作:
- 创建一个与
Service同名的Endpoints资源。 - 持续监控集群中所有
Pod的标签。 - 当发现有
Pod的标签与Service的selector匹配时,就把这个Pod的 IP 地址和端口(从Pod的containerPort获取)添加到Endpoints列表中。 - 当
Pod被删除、更新或标签改变时,Kubernetes 会自动更新Endpoints列表。
详细实现过程:
第 1 步:创建一个 Deployment 来管理 Pod
我们先创建一些带有特定标签的 Pod。这里我们用 Deployment 来批量管理。
my-app-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 3 # 运行 3 个 Pod 副本
selector:
matchLabels:
app: my-app # Deployment 管理带有此标签的 Pod
template:
metadata:
labels:
app: my-app # 给 Pod 打上标签
env: production
spec:
containers:
- name: my-app-container
image: nginx:alpine # 使用一个简单的 nginx 镜像作为示例
ports:
- containerPort: 80 # 容器在 80 端口提供服务
执行命令创建:
kubectl apply -f my-app-deployment.yaml
查看创建的 Pod:
kubectl get pods -o wide --show-labels
输出示例:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES LABELS
my-app-deployment-7f98d7c6b4-2xqzk 1/1 Running 0 10s 10.244.1.10 minikube <none> <none> app=my-app,env=production,pod-template-hash=7f98d7c6b4
my-app-deployment-7f98d7c6b4-5bmd7 1/1 Running 0 10s 10.244.1.11 minikube <none> <none> app=my-app,env=production,pod-template-hash=7f98d7c6b4
my-app-deployment-7f98d7c6b4-8vgrx 1/1 Running 0 10s 10.244.1.12 minikube <none> <none> app=my-app,env=production,pod-template-hash=7f98d7c6b4
你可以看到 3 个 Pod 已经运行,并且都带有 app=my-app 标签,它们的 IP 地址分别是 10.244.1.10, 10.244.1.11, 10.244.1.12。
第 2 步:创建一个带有 selector 的 Service
现在,我们创建一个 Service,让它通过标签选择器找到上面的 Pod。
my-app-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app # 关键!Service 将选择所有带有 app=my-app 标签的 Pod
ports:
- protocol: TCP
port: 80 # Service 暴露的端口
targetPort: 80 # 转发到 Pod 的端口
type: ClusterIP # 这是默认类型,只在集群内部可访问
执行命令创建:
kubectl apply -f my-app-service.yaml
查看 Service:
kubectl get svc my-app-service
输出示例:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-app-service ClusterIP 10.100.235.147 <none> 80/TCP 5s
Service 被分配了一个集群内部的虚拟 IP 地址(CLUSTER-IP)。
第 3 步:验证自动创建的 Endpoints
此时,Kubernetes 已经自动为我们创建了一个名为 my-app-service 的 Endpoints 资源。
查看 Endpoints:
kubectl get endpoints my-app-service -o wide
输出示例:
NAME ENDPOINTS AGE
my-app-service 10.244.1.10:80,10.244.1.11:80,10.244.1.12:80 15s
奇迹发生了! Endpoints 列表中自动包含了我们刚才创建的 3 个 Pod 的 IP 地址和端口 80。
第 4 步:验证服务发现
我们可以在集群内的另一个 Pod 中(比如一个临时的 busybox Pod)来访问这个 Service,以验证它是否能正常工作。
kubectl run -it --rm --image=busybox:1.28 --restart=Never busybox-test sh
这会打开一个 busybox Pod 的 shell 终端。在终端中执行:
# 使用 wget 或 curl 访问 Service 的名字
wget -qO- my-app-service
输出示例(简化):
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</html>
你成功地通过 Service 名称 my-app-service 访问到了后端的 nginx Pod 提供的服务。这背后正是 Endpoints 在默默地工作。
案例 2:手动创建 Endpoints
当 Service 没有 selector 时,Kubernetes 不会自动创建和管理 Endpoints。这种情况通常用于:
- 服务集群外部的应用:例如,你有一个数据库运行在 Kubernetes 集群之外的物理机上,你想让集群内的
Pod也能通过Service的方式访问它。 - 精细控制服务 endpoints:在某些特殊场景下,你需要手动决定哪些 IP 被包含。
详细实现过程:
假设我们有一个数据库运行在集群外部,IP 地址是 192.168.1.100,端口是 5432。
第 1 步:创建一个不带 selector 的 Service
my-external-db-service.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-external-db-service
spec:
ports:
- protocol: TCP
port: 5432 # Service 暴露的端口
targetPort: 5432 # 转发到外部服务的端口
# 注意:这里没有 selector 字段
执行命令创建:
kubectl apply -f my-external-db-service.yaml
查看 Service:
kubectl get svc my-external-db-service
输出示例:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-external-db-service ClusterIP 10.100.156.78 <none> 5432/TCP 10s
Service 创建成功,但此时如果你查看它的 Endpoints,会发现是空的。
kubectl get endpoints my-external-db-service
输出示例:
NAME ENDPOINTS AGE
my-external-db-service <none> 20s
第 2 步:手动创建对应的 Endpoints 资源
现在,我们需要手动创建一个 Endpoints 资源,将 Service 指向外部的数据库 IP。
my-external-db-endpoints.yaml:
apiVersion: v1
kind: Endpoints
metadata:
name: my-external-db-service # 名称必须和 Service 完全一致
subsets:
- addresses:
- ip: 192.168.1.100 # 外部数据库的 IP 地址
ports:
- port: 5432 # 外部数据库的端口
关键点:
metadata.name必须与Service的名字相同,这样Service才能找到并使用它。subsets.addresses.ip可以是任何可访问的 IP,不一定是集群内的PodIP。- 你可以在
addresses列表中添加多个 IP,实现对多个外部服务的负载均衡。
执行命令创建:
kubectl apply -f my-external-db-endpoints.yaml
第 3 步:验证手动创建的 Endpoints
再次查看 Endpoints:
kubectl get endpoints my-external-db-service
输出示例:
NAME ENDPOINTS AGE
my-external-db-service 192.168.1.100:5432 5s
现在 Endpoints 已经有值了!
第 4 步:验证服务访问
现在,集群内的 Pod 就可以通过 my-external-db-service:5432 这个地址来访问外部的数据库了。
# 再次使用 busybox 进行测试
kubectl run -it --rm --image=busybox:1.28 --restart=Never busybox-test sh
在 shell 中:
# 尝试 telnet 到 Service 地址和端口,验证连通性
telnet my-external-db-service 5432
如果外部数据库可达,你应该会看到类似下面的输出,表示连接成功:
Trying 10.100.156.78...
Connected to my-external-db-service.default.svc.cluster.local.
Escape character is '^]'.
(注意:10.100.156.78 是 Service 的 CLUSTER-IP)
三、Endpoints 的特殊行为与配置
默认行为:Service 如何筛选 Pod?
在案例 1 的自动模式下,Service 并不是把所有标签匹配的 Pod 都加入 Endpoints。它有一套默认的健康检查机制:
一个 Pod 必须满足以下所有条件,才会被 Service 选中并加入到 Endpoints 列表中:
- 标签匹配:
Pod的标签必须与Service的selector完全匹配。 - 状态为 "Running":
Pod的status.phase必须是Running。处于Pending,Succeeded,Failed状态的Pod会被忽略。 - 就绪状态为 "Ready":
Pod必须通过所有的就绪探针(Readiness Probe)检查。Pod的status.conditions中必须有一个类型为Ready,且status为True的条件。这表示Pod内部的应用已经启动并准备好接收请求。
示例:一个未就绪的 Pod
假设我们有一个 Pod,它的就绪探针检查一个需要 30 秒才能启动的服务。在启动后的前 30 秒内,kubectl describe pod <pod-name> 会显示:
Conditions:
Type Status
Ready False
...
在这 30 秒内,这个 Pod 虽然标签匹配且状态是 Running,但因为 Ready 状态是 False,所以它不会出现在 Endpoints 列表中。只有当 Ready 变为 True 后,kube-proxy 才会将其加入 Endpoints,并开始转发流量。
特殊配置:publishNotReadyAddresses
有时候,你可能希望 Service 也能将那些未就绪(Not Ready)的 Pod 暴露出来。例如:
- 进行健康检查或监控:你可能有一个监控系统,需要访问所有
Pod(包括未就绪的)来收集启动时的日志或指标。 - 服务网格(Service Mesh)场景:一些服务网格代理需要在
Pod完全就绪前就与之通信,以注入sidecar代理。
要实现这个需求,你可以在 Service 的定义中设置 publishNotReadyAddresses: true。
详细实现过程:
我们修改案例 1 中的 Service YAML。
my-app-service-include-not-ready.yaml:
apiVersion: v1
kind: Service
metadata:
name: my-app-service-include-not-ready
spec:
selector:
app: my-app
ports:
- protocol: TCP
port: 80
targetPort: 80
publishNotReadyAddresses: true # 关键配置!
执行命令创建或更新:
kubectl apply -f my-app-service-include-not-ready.yaml
效果:
现在,假设我们有一个 Pod 因为就绪探针失败而处于 Not Ready 状态。
kubectl get pods
输出示例:
NAME READY STATUS RESTARTS AGE
my-app-deployment-7f98d7c6b4-2xqzk 1/1 Running 0 5m
my-app-deployment-7f98d7c6b4-5bmd7 0/1 Running 0 30s # 这个 Pod 未就绪
my-app-deployment-7f98d7c6b4-8vgrx 1/1 Running 0 5m
查看新 Service 对应的 Endpoints:
kubectl get endpoints my-app-service-include-not-ready
输出示例:
NAME ENDPOINTS AGE
my-app-service-include-not-ready 10.244.1.10:80,10.244.1.11:80,10.244.1.12:80 1m
(假设 10.244.1.11 是那个未就绪 Pod 的 IP)
你会发现,即使 Pod my-app-deployment-7f98d7c6b4-5bmd7 的 READY 状态是 0/1,它的 IP 地址依然被包含在了 Endpoints 列表中。
警告:启用 publishNotReadyAddresses 后,Service 会将流量转发到所有标签匹配且状态为 Running 的 Pod,无论其是否就绪。这意味着客户端可能会访问到一个无法正常提供服务的 Pod,导致请求失败。因此,在生产环境中使用此配置时,必须确保你的应用程序或客户端有足够的容错能力(如重试机制)。
总结
Endpoints是服务发现的基石,它存储了Service背后真实的Pod(或外部服务) 的网络地址。- 自动模式是常态:通过
Service的selector自动管理Endpoints,是 Kubernetes 服务发现的标准用法。 - 手动模式用于特殊场景:当需要将
Service指向集群外部服务或进行精细控制时,可以手动创建Endpoints。 - 默认行为是安全的:
Service只将流量转发到Running且Ready的Pod,确保了服务的可用性。 publishNotReadyAddresses是一个强大的逃逸阀:它允许你打破默认的安全检查,在特定场景下非常有用,但使用时需谨慎。
K8s存储笔记
存储分类
K8s中的存储主要用于解决容器数据持久化、配置管理、敏感信息存储等问题,常见的存储类型包括ConfigMap(配置存储)、Secret(敏感信息存储)、PV/PVC(持久化存储)、Downward API(Pod元数据注入)等。不同存储类型针对不同场景设计,满足多样化的存储需求。
ConfigMap
概念
ConfigMap是K8s专门用来存储非敏感配置信息的资源,比如应用的配置参数、环境变量配置、配置文件内容等。你可以把它理解成一个“配置字典”,里面装着键值对形式的配置数据。
- 它能被挂载到Pod里,要么当成环境变量用,要么当成配置文件用;
- 多个Pod可以共享同一个ConfigMap,实现配置的统一管理和复用;
- 注意:ConfigMap是在Pod第一次启动时注入的,后续如果ConfigMap改了,Pod不会自动更新,除非删掉Pod重新创建(不过用Volume挂载的方式支持热更新,后面会讲)。
创建
1. 通过YAML文件创建
新建一个my-config.yaml文件,内容如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-config # ConfigMap的名字
namespace: default # 所属命名空间,默认default
data:
app.conf: | # 配置文件形式的键值对
server.port=8080
log.level=info
env: "prod" # 简单键值对
max_connections: "1000"
执行创建命令:
kubectl apply -f my-config.yaml
输出:configmap/my-config created
2. 从文件创建
先创建一个本地配置文件app.properties,内容为:
db.url=jdbc:mysql://localhost:3306/mydb
db.username=root
然后执行命令创建ConfigMap:
kubectl create configmap my-config-from-file --from-file=app.properties
输出:configmap/my-config-from-file created
如果有多个文件,可多次指定--from-file,比如--from-file=file1 --from-file=file2。
3. 从字面量创建
直接通过命令行指定键值对创建:
kubectl create configmap my-config-from-literal --from-literal=key1=value1 --from-literal=key2=value2
输出:configmap/my-config-from-literal created
Pod如何使用ConfigMap?
1. 作为环境变量
创建Pod的YAML文件pod-env-config.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-env-config
spec:
containers:
- name: nginx-container
image: nginx:alpine
env:
- name: ENV_CONFIG # 容器内的环境变量名
valueFrom:
configMapKeyRef:
name: my-config # 引用的ConfigMap名称
key: env # 取ConfigMap里的env键
- name: MAX_CONN # 另一个环境变量
valueFrom:
configMapKeyRef:
name: my-config
key: max_connections
创建Pod:
kubectl apply -f pod-env-config.yaml
进入Pod查看环境变量:
kubectl exec -it pod-env-config -- sh
# 执行env命令查看
env | grep -E "ENV_CONFIG|MAX_CONN"
输出:
ENV_CONFIG=prod
MAX_CONN=1000
2. 作为启动参数
创建Pod的YAML文件pod-args-config.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-args-config
spec:
containers:
- name: busybox-container
image: busybox:latest
command: ["/bin/sh", "-c", "echo $(ENV_VAL) $(MAX_VAL)"] # 启动命令中使用环境变量
env:
- name: ENV_VAL
valueFrom:
configMapKeyRef:
name: my-config
key: env
- name: MAX_VAL
valueFrom:
configMapKeyRef:
name: my-config
key: max_connections
创建Pod后查看日志:
kubectl apply -f pod-args-config.yaml
kubectl logs pod-args-config
输出:prod 1000
3. 作为配置文件(Volume挂载)
创建Pod的YAML文件pod-volume-config.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-volume-config
spec:
containers:
- name: nginx-container
image: nginx:alpine
volumeMounts:
- name: config-volume # 卷名,和下面volumes里的name对应
mountPath: /etc/nginx/conf.d # 挂载到容器内的路径
readOnly: true # 只读,ConfigMap挂载默认只读
volumes:
- name: config-volume
configMap:
name: my-config # 引用的ConfigMap名称
items: # 可选,指定要挂载的键,不指定则挂载所有键
- key: app.conf # ConfigMap里的app.conf键
path: app.conf # 挂载到容器内的文件名
创建Pod:
kubectl apply -f pod-volume-config.yaml
进入Pod查看挂载的文件:
kubectl exec -it pod-volume-config -- sh
cat /etc/nginx/conf.d/app.conf
输出:
server.port=8080
log.level=info
再查看目录下的文件:
ls -lh /etc/nginx/conf.d/
输出(类似):
total 0
lrwxrwxrwx 1 root root 13 Mar 20 10:00 app.conf -> ..data/app.conf
可以看到是符号链接,指向..data目录下的实际文件,这是为了支持热更新。
热更新
只有通过Volume挂载为文件的ConfigMap才支持热更新,环境变量方式不支持。下面用Nginx示例演示:
1. 准备Nginx配置文件和ConfigMap
创建nginx.conf文件:
server {
listen 80;
server_name localhost;
location / {
default_type text/plain;
return 200 'Hello from original config!';
}
}
创建ConfigMap:
kubectl create configmap nginx-config --from-file=nginx.conf
2. 创建Deployment挂载ConfigMap
创建nginx-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deploy
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: nginx-config-volume
mountPath: /etc/nginx/conf.d/default.conf # 直接挂载为Nginx默认配置文件
subPath: nginx.conf # 指定挂载的文件,避免覆盖整个目录
volumes:
- name: nginx-config-volume
configMap:
name: nginx-config
创建Deployment:
kubectl apply -f nginx-deployment.yaml
查看Pod:
kubectl get pods
输出(类似):
NAME READY STATUS RESTARTS AGE
nginx-deploy-7f9d6c8b7d-2xqzk 1/1 Running 0 10s
nginx-deploy-7f9d6c8b7d-5m7tl 1/1 Running 0 10s
nginx-deploy-7f9d6c8b7d-8k4zp 1/1 Running 0 10s
3. 测试初始配置
访问其中一个Pod:
kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- curl localhost
输出:Hello from original config!
4. 修改ConfigMap
编辑ConfigMap:
kubectl edit configmap nginx-config
把nginx.conf内容改成:
server {
listen 80;
server_name localhost;
location / {
default_type text/plain;
return 200 'Hello from updated config!';
}
}
保存退出。
5. 查看ConfigMap更新后的Pod文件
等待10秒左右,进入Pod查看配置文件:
kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- cat /etc/nginx/conf.d/default.conf
输出:
server {
listen 80;
server_name localhost;
location / {
default_type text/plain;
return 200 'Hello from updated config!';
}
}
说明ConfigMap已经热更新到Pod的文件里了。
6. 重启Nginx使配置生效
但此时访问Pod还是返回旧内容,因为Nginx没重新加载配置:
kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- curl localhost
输出:Hello from original config!
需要进入Pod重启Nginx:
kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- nginx -s reload
再访问:
kubectl exec -it nginx-deploy-7f9d6c8b7d-2xqzk -- curl localhost
输出:Hello from updated config!
7. 滚动重启Deployment(推荐)
手动重启每个Pod太麻烦,可通过修改Deployment的Annotation触发滚动重启:
kubectl patch deployment nginx-deploy -p "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":{\"kubectl.kubernetes.io/restartedAt\":\"$(date +%Y-%m-%dT%H:%M:%S)\"}}}}}"
查看Pod,会发现旧Pod被删除,新Pod创建:
kubectl get pods
新Pod会自动加载最新的ConfigMap配置,无需手动重启Nginx。
不可改变
1. 配置ConfigMap为不可改变
创建ConfigMap时指定immutable: true,或编辑现有ConfigMap添加该字段:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-immutable-config
data:
key1: value1
immutable: true # 设为不可改变
执行创建:
kubectl apply -f immutable-config.yaml
2. 特点
- 设为不可改变后,无法修改ConfigMap的内容,也不能删除后重建同名的ConfigMap(除非先删除这个不可改变的ConfigMap);
- 这个设置不允许回退,一旦设为
immutable: true,就不能改回false,只能删除重建; - 优点是能提高性能(K8s无需监控其变化),并防止误修改配置。
3. 验证不可改变
尝试编辑:
kubectl edit configmap my-immutable-config
修改key1的值后保存,会报错:
error: configmaps "my-immutable-config" is immutable
Secret
概念
Secret是K8s用来存储敏感信息的资源,比如密码、密钥、证书、Token等。它和ConfigMap结构类似,但会对数据进行Base64编码(注意:Base64不是加密,只是编码,仍需通过权限控制保证安全),且只有Pod内的容器能解密访问。
- 同样支持挂载为环境变量或文件;
- 可被多个Pod共享,统一管理敏感信息;
- 相比ConfigMap,Secret的访问权限更严格,默认不会被非授权用户查看。
类型
| 类型 | 用途 |
|---|---|
| Opaque | 默认类型,存储任意键值对的敏感信息(如密码、密钥) |
| kubernetes.io/dockerconfigjson | 存储Docker镜像仓库的认证信息(拉取私有镜像用) |
| kubernetes.io/service-account-token | 存储服务账号的Token,用于Pod访问K8s API Server |
| kubernetes.io/tls | 存储TLS证书和私钥(如HTTPS证书) |
| bootstrap.kubernetes.io/token | 用于K8s集群节点引导的Token |
Opaque类型Secret
Opaque是最常用的Secret类型,数据以Base64编码存储,下面详细讲创建和使用。
创建
1. 通过YAML文件创建
首先把要存储的内容转成Base64编码:
echo -n "my-password" | base64 # 输出:bXktcGFzc3dvcmQ=
echo -n "admin" | base64 # 输出:YWRtaW4=
创建my-secret.yaml文件:
apiVersion: v1
kind: Secret
metadata:
name: my-secret
type: Opaque
data:
username: YWRtaW4= # Base64编码后的admin
password: bXktcGFzc3dvcmQ= # Base64编码后的my-password
执行创建:
kubectl apply -f my-secret.yaml
输出:secret/my-secret created
2. 从文件创建
创建db-pass.txt文件,内容为db-password-123,然后执行:
kubectl create secret generic my-secret-from-file --from-file=db-pass=db-pass.txt
输出:secret/my-secret-from-file created
这里db-pass是Secret里的键,db-pass.txt是本地文件。
3. 从字面量创建
直接指定键值对(K8s会自动转Base64):
kubectl create secret generic my-secret-from-literal --from-literal=redis-pass=redis-123456
输出:secret/my-secret-from-literal created
查看Secret
1. 查看Secret列表
kubectl get secrets
输出(类似):
NAME TYPE DATA AGE
my-secret Opaque 2 20s
my-secret-from-file Opaque 1 10s
my-secret-from-literal Opaque 1 5s
default-token-xxxx kubernetes.io/service-account-token 3 1d
2. 查看Secret详情
kubectl describe secret my-secret
输出(类似):
Name: my-secret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 10 bytes
username: 5 bytes
注意:describe不会显示具体的Base64编码值,保证敏感信息不泄露。
3. 查看Secret的原始数据
如果需要查看具体内容,可执行:
kubectl get secret my-secret -o jsonpath='{.data}'
输出:{"password":"bXktcGFzc3dvcmQ=","username":"YWRtaW4="}
解码查看:
echo "bXktcGFzc3dvcmQ=" | base64 -d # 输出:my-password
echo "YWRtaW4=" | base64 -d # 输出:admin
使用
1. 作为环境变量
创建pod-secret-env.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-env
spec:
containers:
- name: busybox-container
image: busybox:latest
command: ["/bin/sh", "-c", "env | grep DB_"]
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: my-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: my-secret
key: password
创建Pod并查看日志:
kubectl apply -f pod-secret-env.yaml
kubectl logs pod-secret-env
输出:
DB_USERNAME=admin
DB_PASSWORD=my-password
2. 作为配置文件(Volume挂载)
创建pod-secret-volume.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-secret-volume
spec:
containers:
- name: nginx-container
image: nginx:alpine
volumeMounts:
- name: secret-volume
mountPath: /etc/secrets # 挂载到容器内的目录
readOnly: true
volumes:
- name: secret-volume
secret:
secretName: my-secret # 引用的Secret名称
创建Pod并查看挂载的文件:
kubectl apply -f pod-secret-volume.yaml
kubectl exec -it pod-secret-volume -- sh
ls /etc/secrets/
输出:password username
查看文件内容:
cat /etc/secrets/username # 输出:admin
cat /etc/secrets/password # 输出:my-password
注意:挂载的文件内容是Base64解码后的原始值,容器可直接使用。
3. 自定义文件权限
默认挂载的Secret文件权限是0644,可通过defaultMode修改:
volumes:
- name: secret-volume
secret:
secretName: my-secret
defaultMode: 0400 # 只有所有者可读
创建Pod后查看权限:
kubectl exec -it pod-secret-volume -- ls -l /etc/secrets/
输出(类似):
-r-------- 1 root root 5 Mar 20 12:00 username
-r-------- 1 root root 10 Mar 20 12:00 password
4. 挂载指定的Key
如果只需要挂载Secret中的部分Key,可通过items指定:
volumes:
- name: secret-volume
secret:
secretName: my-secret
items:
- key: username
path: db/user # 挂载到/etc/secrets/db/user
mode: 0600
创建Pod后查看:
kubectl exec -it pod-secret-volume -- ls /etc/secrets/db/
cat /etc/secrets/db/user # 输出:admin
注意:通过items指定Key挂载的方式不支持热更新,Secret修改后Pod不会自动更新,需重启Pod。
不可更改
和ConfigMap一样,Secret也可设置为不可改变:
apiVersion: v1
kind: Secret
metadata:
name: my-immutable-secret
type: Opaque
data:
key: dmFsdWU=
immutable: true
设为不可改变后,无法修改Secret内容,也不能删除重建同名Secret,除非先删除该Secret。
Downward API
概念
Downward API是K8s提供的一种机制,能把Pod自身的元数据(比如Pod名称、IP、标签、资源限制等)注入到容器内部。简单说,就是让容器能“知道”自己所在Pod的信息,无需通过K8s API Server查询(当然也可以通过API Server查,后面会讲)。
- 支持两种注入方式:环境变量和Volume挂载;
- 注入的是Pod启动时的元数据,部分数据(如Pod IP)会在Pod生命周期中保持不变,部分数据(如资源使用)不会实时更新。
使用案例
1. 作为环境变量注入
创建pod-downward-env.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-downward-env
labels:
app: my-app
tier: frontend
annotations:
author: "k8s-user"
version: "1.0"
spec:
containers:
- name: busybox-container
image: busybox:latest
command: ["/bin/sh", "-c", "env | grep POD_"]
resources:
requests:
cpu: "100m" # 0.1核
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
env:
# Pod名称
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
# Pod命名空间
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
# Pod IP
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
# Pod标签(JSON格式)
- name: POD_LABELS
valueFrom:
fieldRef:
fieldPath: metadata.labels
# CPU请求
- name: CPU_REQUEST
valueFrom:
resourceFieldRef:
containerName: busybox-container
resource: requests.cpu
# 内存限制
- name: MEMORY_LIMIT
valueFrom:
resourceFieldRef:
containerName: busybox-container
resource: limits.memory
创建Pod并查看日志:
kubectl apply -f pod-downward-env.yaml
kubectl logs pod-downward-env
输出(类似):
POD_NAME=pod-downward-env
POD_NAMESPACE=default
POD_IP=10.244.1.10
POD_LABELS={"app":"my-app","tier":"frontend"}
CPU_REQUEST=100m
MEMORY_LIMIT=268435456
2. 通过Volume挂载注入
创建pod-downward-volume.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-downward-volume
labels:
app: my-app
tier: frontend
annotations:
author: "k8s-user"
version: "1.0"
spec:
containers:
- name: nginx-container
image: nginx:alpine
volumeMounts:
- name: downward-volume
mountPath: /etc/pod-info
readOnly: true
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "200m"
memory: "256Mi"
volumes:
- name: downward-volume
downwardAPI:
items:
# Pod名称
- path: "pod_name"
fieldRef:
fieldPath: metadata.name
# Pod注解
- path: "pod_annotations"
fieldRef:
fieldPath: metadata.annotations
# CPU限制
- path: "cpu_limit"
resourceFieldRef:
containerName: nginx-container
resource: limits.cpu
# 内存请求
- path: "memory_request"
resourceFieldRef:
containerName: nginx-container
resource: requests.memory
创建Pod并查看挂载的文件:
kubectl apply -f pod-downward-volume.yaml
kubectl exec -it pod-downward-volume -- sh
ls /etc/pod-info/
输出:cpu_limit memory_request pod_annotations pod_name
查看文件内容:
cat /etc/pod-info/pod_name # 输出:pod-downward-volume
cat /etc/pod-info/cpu_limit # 输出:200m
cat /etc/pod-info/memory_request # 输出:134217728
cat /etc/pod-info/pod_annotations # 输出:{"author":"k8s-user","version":"1.0"}
通过API Server获取集群元数据
除了Downward API,也可以在Pod内通过K8s API Server获取集群的元数据(比如其他Pod、Service信息)。需要给Pod绑定Service Account并授权:
1. 创建Service Account和Role
创建sa-role.yaml:
apiVersion: v1
kind: ServiceAccount
metadata:
name: pod-reader-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader-role
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: pod-reader-binding
subjects:
- kind: ServiceAccount
name: pod-reader-sa
roleRef:
kind: Role
name: pod-reader-role
apiGroup: rbac.authorization.k8s.io
执行创建:
kubectl apply -f sa-role.yaml
2. 创建Pod使用Service Account
创建pod-api-server.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-api-server
spec:
serviceAccountName: pod-reader-sa # 使用上面创建的SA
containers:
- name: curl-container
image: curlimages/curl:latest
command: ["/bin/sh", "-c", "while true; do curl -sS --cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT/api/v1/namespaces/default/pods; sleep 10; done"]
创建Pod并查看日志:
kubectl apply -f pod-api-server.yaml
kubectl logs -f pod-api-server
输出会包含default命名空间下所有Pod的详细信息(JSON格式)。
其中:
KUBERNETES_SERVICE_HOST和KUBERNETES_SERVICE_PORT是Pod内默认的环境变量,指向API Server;/var/run/secrets/kubernetes.io/serviceaccount/ca.crt是集群CA证书,用于验证API Server身份;/var/run/secrets/kubernetes.io/serviceaccount/token是Service Account的Token,用于认证(curl会自动使用?或需要在Header里指定:-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)")。
补充:Pod访问Service的方式
- 同一命名空间:直接用Service名称访问(比如
curl my-service:80); - 不同命名空间:用全限定名访问(比如
curl my-service.my-namespace.svc.cluster.local:80)。
K8s存储核心笔记:Volume与PV/PVC
第一章 Volume:容器的基础存储机制
1.1 核心概念
Volume是K8s为容器提供的数据存储抽象,本质是将“存储资源”与“容器生命周期”解耦,解决容器重启/迁移后数据丢失的问题。它不是容器内的临时目录,而是独立于容器的存储空间,可被Pod内的多个容器共享。
存在的核心意义:
-
持久化存储:容器终止后,Volume中的数据不会丢失(部分类型除外,如emptyDir);
-
数据共享:同一Pod内的多个容器可挂载同一个Volume,实现数据互通;
-
存储解耦:支持多种底层存储(本地目录、NFS、云存储等),容器无需关注存储实现细节。
1.2 常见Volume类型
Volume类型按用途可分为“特殊配置存储”和“通用数据存储”,前序学习的Secret、ConfigMap、DownwardAPI均属于特殊Volume,核心用于配置/元数据注入;以下为重点学习的通用Volume类型:
1.2.1 emptyDir:临时空目录(Pod级存储)
定义:Pod启动时自动创建的空目录,绑定于Pod生命周期——Pod删除时,emptyDir中的数据会被彻底删除;但容器重启时,数据会保留。
核心特性:
-
绑定Pod级别,而非容器级别;
-
同一Pod内的多个容器可共享同一个emptyDir;
-
无需配置底层存储,K8s自动在节点上分配临时空间;
-
适用场景:容器间临时数据交换(如日志缓存、临时计算结果)。
1.2.2 hostPath:主机路径挂载(节点级存储)
定义:将Pod调度到的“节点主机”上的目录/文件,直接挂载到容器内部,实现容器与主机的文件系统互通。
核心特性:
-
绑定Pod级别,依赖节点主机的文件系统;
-
Pod迁移到其他节点时,挂载的是新节点的对应路径(数据不共享);
-
适用场景:容器需访问主机文件(如监控主机日志、运行依赖主机设备的应用)。
hostPath的8种类型(控制路径存在性校验):
| 类型 | 含义 | 挂载结果 |
|---|---|---|
| 空字符串 | 默认,无校验 | 路径不存在则挂载失败 |
| DirectoryOrCreate | 目录,不存在则创建 | 自动创建目录(权限0755,属主root) |
| Directory | 目录,必须存在 | 路径不存在或非目录则挂载失败 |
| FileOrCreate | 文件,不存在则创建 | 自动创建文件(权限0644,属主root) |
| File | 文件,必须存在 | 路径不存在或非文件则挂载失败 |
| Socket | UNIX域套接字,必须存在 | 路径非套接字则挂载失败 |
| CharDevice | 字符设备,必须存在 | 路径非字符设备则挂载失败 |
| BlockDevice | 块设备,必须存在 | 路径非块设备则挂载失败 |
hostPath使用注意事项:
-
节点差异性:相同配置的Pod在不同节点上,挂载的主机路径内容可能不同;
-
资源调度问题:K8s调度Pod时,不会考虑hostPath占用的主机资源(可能导致节点存储耗尽);
-
权限问题:容器内用户可能无主机路径的读写权限,需通过`securityContext`配置权限。
1.2.3 其他核心类型
-
nfs:挂载NFS服务器的共享目录,支持多节点数据共享(RWX模式),是分布式场景的常用选择;
-
persistentVolumeClaim(PVC):通过PVC挂载PV,是持久化存储的核心方式(详见第二章);
-
local:挂载节点本地的持久化存储(如本地磁盘分区),性能好但不支持Pod迁移;
-
csi:容器存储接口,统一对接外部存储(如AWS EBS、阿里云OSS),是云原生存储的标准方案。
1.3 hostPath实操实验(含详细步骤与输出)
实验目标
创建一个Pod,将主机的`/test`目录挂载到容器内的`/hostpath`,验证容器与主机的文件共享。
实验步骤
-
编写Pod配置文件(hostpath-pod.yaml):
apiVersion: v1kind: Podmetadata:name: hostpath-pod # Pod名称spec:containers:- name: hostpath-container # 容器名称image: nginx:alpine # 轻量Nginx镜像volumeMounts:- name: hostpath-volume # 与volumes.name对应mountPath: /hostpath # 容器内挂载路径volumes:- name: hostpath-volume # 卷名称hostPath:path: /test # 主机上的路径(Pod调度到的节点)type: DirectoryOrCreate # 目录不存在则创建 -
创建Pod并查看状态:
# 提交配置创建Podkubectl apply -f hostpath-pod.yaml# 查看Pod状态(确保Running)kubectl get pods hostpath-pod输出示例:NAME READY STATUS RESTARTS AGEhostpath-pod 1/1 Running 0 30s -
验证主机路径自动创建: 先获取Pod调度到的节点,再登录节点查看`/test`目录:
# 查看Pod所在节点(记下列出的NODE_NAME)kubectl describe pod hostpath-pod | grep "Node:"# 登录节点(假设节点名为node-1,根据实际情况替换)ssh node-1# 查看/test目录是否存在ls -ld /test输出示例:drwxr-xr-x 2 root root 4096 11月 30 18:00 /test说明:`DirectoryOrCreate`类型已自动创建`/test`目录,权限为0755。 -
验证容器与主机的文件共享: ① 主机节点创建测试文件:
echo "This is a test file from host" > /test/host-file.txt② 进入容器查看文件:# 进入容器终端kubectl exec -it hostpath-pod -- sh# 查看容器内/hostpath目录下的文件ls /hostpath# 读取文件内容(验证与主机一致)cat /hostpath/host-file.txt输出示例:/ # ls /hostpathhost-file.txt/ # cat /hostpath/host-file.txtThis is a test file from host③ 容器内创建文件,主机验证:# 容器内创建文件echo "This is from container" > /hostpath/container-file.txt# 退出容器,在主机节点查看exit # 退出容器cat /test/container-file.txt输出示例:This is from container -
清理资源:
# 删除Podkubectl delete pod hostpath-pod# (可选)删除主机/test目录ssh node-1 "rm -rf /test"
实验结论
hostPath实现了容器与主机的文件互通,`DirectoryOrCreate`类型确保路径不存在时自动创建,适合需要访问主机文件系统的场景。
第二章 PV/PVC:持久化存储的核心方案
Volume(如hostPath、emptyDir)存在“存储供给与使用强耦合”的问题——业务工程师需手动指定底层存储(如NFS路径),与存储团队沟通成本高。PV/PVC通过“抽象层”解决此问题,实现存储供给与使用的分工协作。
2.1 核心概念与分工价值
PV(PersistentVolume,持久卷)
由存储工程师/基础设施团队创建,是对底层物理存储(如EMC、Ceph、NFS)的K8s标准化抽象,代表“可用的持久化存储资源”。
核心特点:独立于Pod生命周期,Pod删除后PV仍存在;包含存储容量、访问模式等固定属性。
PVC(PersistentVolumeClaim,持久卷申领)
由业务工程师创建,是Pod对存储的“需求声明”,描述所需存储的容量、访问模式、存储类别等,无需关注底层存储细节。
核心特点:通过匹配PV完成绑定,Pod只需挂载PVC即可使用存储,实现“按需申领”。
分工价值(核心优势)
-
解耦协作:存储团队管PV(供给),业务团队管PVC(使用),无需直接沟通;
-
标准化:PV定义统一存储属性,PVC按标准声明需求,避免配置混乱;
-
资源复用:一个PV可被多个PVC复用(解绑后),提高存储利用率。
2.2 PV与PVC的核心属性
2.2.1 PV的核心属性(存储侧定义)
| 属性 | 含义 | 示例/说明 |
|---|---|---|
| 容量(capacity) | 存储资源大小 | `capacity: {storage: 10Gi}` |
| 访问模式(accessModes) | 存储的读写权限范围 | RWO/ROX/RWX(三选一或多选,需底层存储支持) |
| 存储类别(storageClassName) | PV的“分组标识”,用于PVC精准匹配 | `storageClassName: "gold"`(金牌存储) |
| 回收策略(persistentVolumeReclaimPolicy) | PVC解绑后PV的处理方式 | Retain(保留)/Delete(删除)/Recycle(回收) |
| 存储源(spec) | 关联的底层存储(如NFS、本地存储) | `nfs: {server: 192.168.1.100, path: /nfs/share}` |
2.2.2 PVC的核心属性(业务侧声明)
| 属性 | 含义 | 示例/说明 |
|---|---|---|
| 资源需求(resources) | 所需存储容量 | `resources: {requests: {storage: 5Gi}}` |
| 访问模式(accessModes) | 需与PV完全匹配 | `accessModes: ["ReadWriteOnce"]` |
| 存储类别(storageClassName) | 需与PV完全匹配(空值匹配空值PV) | `storageClassName: "gold"` |
2.3 核心知识点:访问模式(Access Modes)
访问模式决定了PV可被多少节点/容器访问,是PV与PVC匹配的关键条件,必须完全一致。共三种模式,需结合底层存储支持选择:
| 模式 | 缩写 | 含义 | 支持的存储示例 | 限制说明 |
|---|---|---|---|---|
| 单节点读写 | RWO | 仅允许一个节点上的多个容器读写 | hostPath、iSCSI、云盘(AWS EBS) | 不支持多节点共享,Pod迁移后需重新挂载 |
| 多节点只读 | ROX | 允许多个节点上的容器只读访问 | NFS、CephFS、GlusterFS | 所有节点均无写入权限,适合共享配置文件 |
| 多节点读写 | RWX | 允许多个节点上的容器读写访问 | NFS、CephFS、GlusterFS | 需底层存储支持“网络锁”(如NFS的文件锁),本地文件系统(EXT4/XFS)不支持 |
关键提醒:iSCSI、hostPath等块存储/本地存储不支持RWX,因EXT4/XFS等本地文件系统只有“本地锁”,无“网络锁”——多节点同时写入会导致文件损坏或数据覆盖。
2.4 PV与PVC的匹配流程
PVC通过“预选+优选”两步匹配PV,最终建立唯一绑定关系,流程如下:
-
预选阶段:筛选满足基本条件的PVPVC会过滤掉不符合以下条件的PV:PV容量 ≥ PVC声明的容量;
-
PV的访问模式包含PVC声明的模式;
-
PV的存储类别与PVC完全一致;
-
PV状态为“Available”(可用)。
-
优选阶段:选择最优PV在预选通过的PV中,按“资源浪费最少”原则排序,优先选择:容量与PVC完全匹配的PV;
-
容量最接近PVC需求的PV(如PVC需5Gi,优先5Gi而非6Gi)。
-
绑定阶段:建立唯一关联PVC与最优PV绑定后,PV状态变为“Bound”(绑定),仅该PVC可使用此PV;Pod通过挂载PVC,间接使用PV对应的底层存储。
匹配示例
PVC需求:5Gi容量、RWO模式、存储类别“silver”;
可选PV:
-
PV1:4Gi、RWO、silver → 容量不足,预选淘汰;
-
PV2:5Gi、RWO、silver → 完全匹配,优选第一;
-
PV3:6Gi、RWO、silver → 容量过剩,优选第二;
-
PV4:5Gi、RWX、silver → 访问模式不匹配,预选淘汰;
-
PV5:5Gi、RWO、gold → 存储类别不匹配,预选淘汰。
最终结果:PVC与PV2绑定。
2.5 PV的回收策略与状态
2.5.1 回收策略(PVC解绑后PV的处理方式)
回收策略决定了PVC删除后,PV及其数据的命运,需根据数据重要性选择:
| 策略 | 核心行为 | 适用场景 | 支持的存储类型 |
|---|---|---|---|
| Retain(保留) | 1. PV状态变为“Released”(已释放);2. 数据保留,不允许新PVC绑定;3. 需管理员手动清理数据并重置PV状态。 | 高价值数据(如金融、核心业务数据),防止误删 | 所有存储类型 |
| Recycle(回收) | 1. 自动执行`rm -rf /path/*`清理数据;2. 清理成功后PV状态变为“Available”,可复用。 | 低价值临时数据,允许数据清除 | 仅NFS、HostPath |
| Delete(删除) | 1. 自动删除PV;2. 同时删除底层存储资源(如云盘、NFS共享)。 | 按需付费的云存储,节省成本 | 云存储(AWS EBS、阿里云云盘)、动态供给PV |
生产环境建议:优先选择Retain策略,即使误删PVC,数据仍可通过管理员手动恢复;Recycle策略因清理不彻底(可能残留隐藏文件),不建议生产使用。
2.5.2 PV的四种状态
PV的状态反映其生命周期阶段,通过`kubectl get pv`可查看:
| 状态 | 含义 | 触发场景 |
|---|---|---|
| Available(可用) | PV已创建,等待PVC绑定 | PV刚创建完成,无PVC绑定 |
| Bound(绑定) | PV已与PVC成功绑定 | PVC匹配并绑定PV后 |
| Released(已释放) | PVC已删除,但PV数据未清理,不可复用 | PVC删除,PV回收策略为Retain |
| Failed(失败) | PV回收策略执行失败,需人工介入 | Recycle清理文件失败、Delete删除底层存储失败 |
2.6 PVC保护机制
为防止数据丢失,K8s内置PVC保护机制,避免存储资源被误删:
-
PV保护:若PV已绑定PVC,直接执行`kubectl delete pv
`会被禁止,需先删除关联的PVC; -
Pod保护:若Pod正在使用PVC,执行`kubectl delete pvc
`后,PVC状态会变为“Terminating”(终止中),但不会立即删除,需等Pod终止后才彻底删除。
2.7 实操案例:基于NFS的PV/PVC完整流程(含代码与输出)
以NFS为底层存储,实现“存储侧创建PV→业务侧创建PVC→Pod挂载使用”的完整流程。
环境准备
假设NFS服务器地址为`192.168.1.100`,共享目录为`/nfs/k8s`(需提前创建并配置权限)。
步骤1:存储侧创建PV(nfs-pv.yaml)
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv # PV名称
spec:
capacity:
storage: 10Gi # PV容量
accessModes:
- ReadWriteMany # 支持多节点读写(NFS特性)
storageClassName: "nfs-storage" # 存储类别
persistentVolumeReclaimPolicy: Retain # 回收策略:保留
nfs: # 关联NFS存储
server: 192.168.1.100 # NFS服务器地址
path: /nfs/k8s # NFS共享目录
执行创建并查看PV状态: kubectl apply -f nfs-pv.yaml kubectl get pv nfs-pv输出示例: NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfs-pv 10Gi RWX Retain Available nfs-storage 10s状态为“Available”,表示PV已就绪等待绑定。
步骤2:业务侧创建PVC(nfs-pvc.yaml)
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc # PVC名称
spec:
accessModes:
- ReadWriteMany # 与PV的访问模式匹配
resources:
requests:
storage: 5Gi # 需求容量(≤PV容量)
storageClassName: "nfs-storage" # 与PV的存储类别匹配
执行创建并查看PVC状态: kubectl apply -f nfs-pvc.yaml kubectl get pvc nfs-pvc输出示例: NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE nfs-pvc Bound nfs-pv 10Gi RWX nfs-storage 5s状态为“Bound”,表示PVC已与nfs-pv成功绑定。此时再查看PV状态,会发现“CLAIM”列显示“default/nfs-pvc”。
步骤3:创建Pod挂载PVC(nfs-pod.yaml)
apiVersion: v1
kind: Pod
metadata:
name: nfs-pod
spec:
containers:
- name: nfs-container
image: nginx:alpine
volumeMounts:
- name: nfs-volume # 与volumes.name对应
mountPath: /usr/share/nginx/html # Nginx网页根目录
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-pvc # 关联创建的PVC
执行创建并验证: # 创建Pod kubectl apply -f nfs-pod.yaml # 查看Pod状态 kubectl get pods nfs-pod # 在NFS服务器的共享目录创建测试页面 echo "<h1>Hello PV/PVC!</h1>" > /nfs/k8s/index.html # 访问Pod的Nginx服务(验证数据共享) kubectl exec -it nfs-pod -- curl localhost输出示例: <h1>Hello PV/PVC!</h1>说明Pod通过PVC成功挂载NFS存储,且能读取底层存储的文件。
步骤4:清理资源
# 删除Pod
kubectl delete pod nfs-pod
# 删除PVC(此时PV状态变为Released)
kubectl delete pvc nfs-pvc
# (可选)删除PV(需先确认PVC已删除)
kubectl delete pv nfs-pv
# (可选)删除NFS共享目录内容
rm -rf /nfs/k8s/*
2.8 核心总结
-
Volume:基础存储单元,解决容器数据临时存储与共享,依赖具体存储实现(如hostPath、NFS);
-
PV/PVC:持久化存储的核心抽象,PV是“存储资源”,PVC是“需求声明”,通过匹配机制实现分工解耦;
-
关键匹配条件:容量≥需求、访问模式完全匹配、存储类别完全匹配;
-
核心流程:存储侧建PV→业务侧建PVC→PVC匹配PV→Pod挂载PVC使用存储。
K8s 存储核心知识点笔记
一、Volume 基础
1. 概念通俗理解
K8s 中的 Volume 是 Pod 内容器共享的存储目录,本质是“Pod 级别的存储卷”。它和 Pod 生命周期绑定(Pod 销毁则 Volume 销毁,临时存储除外),可实现容器间数据共享、数据持久化(对接外部存储)。
2. 常见 Volume 类型
- 临时存储:
emptyDir(Pod 内临时目录,Pod 销毁即删)、tmpfs(内存级存储,重启丢失); - 本地存储:
hostPath(挂载节点本地目录,跨节点不可用); - 网络存储:
nfs、cephfs、glusterfs(跨节点共享,支持持久化); - 云厂商存储:
awsElasticBlockStore(AWS EBS)、gcePersistentDisk(GCE 云盘)。
3. 简单案例:Pod 挂载 emptyDir 实现容器共享
# pod-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-emptyDir
spec:
containers:
- name: container-1
image: nginx:1.14-alpine
volumeMounts:
- name: shared-dir
mountPath: /usr/share/nginx/html # 容器1挂载路径
- name: container-2
image: busybox:1.35
command: ["sh", "-c", "echo 'Hello K8s Volume' > /shared/index.html && sleep 3600"]
volumeMounts:
- name: shared-dir
mountPath: /shared # 容器2挂载路径
volumes:
- name: shared-dir
emptyDir: {} # 临时共享目录
执行步骤与输出:
- 创建 Pod:
kubectl apply -f pod-emptyDir.yaml - 查看 Pod 状态:
kubectl get pod test-emptyDir # 输出: # NAME READY STATUS RESTARTS AGE # test-emptyDir 2/2 Running 0 10s - 验证数据共享:
kubectl exec test-emptyDir -c container-1 -- cat /usr/share/nginx/html/index.html # 输出:Hello K8s Volume - 删除 Pod 后,
emptyDir数据随 Pod 销毁。
二、PV/PVC 核心机制
1. 概念通俗理解
- PV(PersistentVolume):集群级别的“存储资源池”,由运维人员提前创建,定义了具体的存储类型(如 NFS、云盘)、容量、访问模式等,与 Pod 解耦。
- PVC(PersistentVolumeClaim):Pod 对存储的“申请单”,由开发人员创建,声明需要的存储容量、访问模式,K8s 会自动匹配符合条件的 PV 进行绑定。
核心价值:业务侧无需关注底层存储细节(如 NFS 服务器地址),仅通过 PVC 声明需求;存储侧通过 PV 统一管理资源,实现存储与业务解耦。
2. PV 关键字段详解
(1)spec.capacity 存储容量声明
- 作用:定义 PV 能提供的最大存储容量,是 PVC 匹配 PV 的核心“容量门槛”。
- 格式要求:
- 固定键为
storage,值为带单位的存储量; - 支持单位:二进制单位(
Ki/Mi/Gi/Ti,推荐)、十进制单位(K/M/G/T); - 示例:
capacity: { storage: 10Gi }。
- 固定键为
- 匹配逻辑:
- PVC 声明的
resources.requests.storage必须 ≤ PV 的capacity.storage,否则无法绑定; - 多 PV 满足条件时,优先匹配容量最接近 PVC 需求的 PV(如 PVC 需 5Gi,优先选 5Gi PV 而非 10Gi PV)。
- PVC 声明的
- 与底层存储的关系:
PV 声明的容量需 ≤ 底层存储实际可用空间(如 NFS 共享目录实际容量为 20Gi,则 PV 容量不能超过 20Gi),否则会出现“存储空间不足”错误。
(2)spec.accessModes 访问模式
| 访问模式 | 含义 |
|---|---|
ReadWriteOnce(RWO) |
仅允许单个节点以读写方式挂载(适合单 Pod 独占存储,如本地磁盘) |
ReadWriteMany(RWX) |
允许多个节点以读写方式挂载(适合多 Pod 共享存储,如 NFS) |
ReadOnlyMany(ROX) |
允许多个节点以只读方式挂载(适合多 Pod 共享只读数据,如配置文件) |
(3)spec.storageClassName 存储类
用于关联 StorageClass,支持动态创建 PV(后续章节详解)。
(4)spec.persistentVolumeReclaimPolicy 回收策略
| 策略 | 含义 |
|---|---|
Retain |
PVC 删除后,PV 保留数据并标记为 Released,需手动清理后可复用 |
Delete |
PVC 删除后,PV 及底层存储数据自动删除(适合云盘等动态存储) |
Recycle |
PVC 删除后,PV 自动清空数据并标记为 Available(已废弃,不推荐) |
3. PV/PVC 实操:基于 NFS 实现存储持久化
步骤 1:搭建 NFS 服务器(CentOS 7/8 环境)
(1)NFS 服务端部署(单节点)
# 1. 安装 NFS 服务
yum install -y nfs-utils rpcbind
# 2. 创建 NFS 共享目录
mkdir -p /exports/nfs
chmod 777 /exports/nfs # 测试环境放宽权限,生产环境需严格控制
# 3. 配置 NFS 共享规则
echo "/exports/nfs *(rw,sync,no_root_squash,no_all_squash)" > /etc/exports
# 4. 启动服务并设置开机自启
systemctl start rpcbind nfs-server
systemctl enable rpcbind nfs-server
# 5. 验证 NFS 共享
showmount -e localhost
# 输出:
# Export list for localhost:
# /exports/nfs *
(2)K8s 所有节点安装 NFS 客户端
yum install -y nfs-utils
# 验证客户端连通性(任选一个节点)
mount -t nfs <NFS服务器IP>:/exports/nfs /mnt
touch /mnt/test.txt
ls /mnt/test.txt # 能看到文件则连通成功
umount /mnt
步骤 2:创建 PV(存储资源池)
# pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-0 # 第一个 PV
spec:
capacity:
storage: 10Gi # 容量 10Gi
accessModes:
- ReadWriteMany # 支持多节点读写
nfs:
path: /exports/nfs
server: <NFS服务器IP> # 替换为实际 NFS 服务器地址
persistentVolumeReclaimPolicy: Retain # 回收策略为保留数据
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv-1 # 第二个 PV
spec:
capacity:
storage: 5Gi # 容量 5Gi(用于测试容量匹配逻辑)
accessModes:
- ReadWriteMany
nfs:
path: /exports/nfs
server: <NFS服务器IP>
persistentVolumeReclaimPolicy: Retain
执行与验证:
kubectl apply -f pv-nfs.yaml
# 查看 PV 状态(初始为 Available,未绑定 PVC)
kubectl get pv
# 输出:
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# nfs-pv-0 10Gi RWX Retain Available 10s
# nfs-pv-1 5Gi RWX Retain Available 10s
步骤 3:创建 PVC(存储申请单)
# pvc-nfs.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany # 需与 PV 访问模式匹配
resources:
requests:
storage: 5Gi # 申请 5Gi 存储,优先匹配 nfs-pv-1
执行与验证:
kubectl apply -f pvc-nfs.yaml
# 查看 PVC 与 PV 绑定状态
kubectl get pvc
# 输出:
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# nfs-pvc Bound nfs-pv-1 5Gi RWX 10s
kubectl get pv
# 输出:
# NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
# nfs-pv-0 10Gi RWX Retain Available 20s
# nfs-pv-1 5Gi RWX Retain Bound default/nfs-pvc 20s
结论:PVC 会优先匹配容量最接近的 PV(5Gi PVC 绑定了 5Gi 的 nfs-pv-1,而非 10Gi 的 nfs-pv-0)。
步骤 4:Pod 挂载 PVC 实现数据持久化
# pod-pvc-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pvc-nfs
spec:
containers:
- name: nginx
image: nginx:1.14-alpine
ports:
- containerPort: 80
volumeMounts:
- name: nfs-storage
mountPath: /usr/share/nginx/html # 挂载到 Nginx 网页根目录
volumes:
- name: nfs-storage
persistentVolumeClaim:
claimName: nfs-pvc # 关联已创建的 PVC
执行与验证:
- 创建 Pod 并写入测试数据:
kubectl apply -f pod-pvc-nfs.yaml kubectl exec test-pvc-nfs -- sh -c "echo 'Hello PV/PVC' > /usr/share/nginx/html/index.html" - 验证 Pod 内数据:
kubectl exec test-pvc-nfs -- curl localhost # 输出:Hello PV/PVC - 验证 NFS 服务端数据(持久化):
cat /exports/nfs/index.html # NFS 服务器上执行 # 输出:Hello PV/PVC - 删除 Pod 后重建,验证数据不丢失:
kubectl delete pod test-pvc-nfs kubectl apply -f pod-pvc-nfs.yaml kubectl exec test-pvc-nfs -- curl localhost # 仍输出:Hello PV/PVC(数据通过 NFS 持久化)
三、有状态服务与 StatefulSet
1. 有状态服务 vs 无状态服务
| 类型 | 核心特征 | 典型应用 |
|---|---|---|
| 无状态服务 | 实例无唯一身份,可随意替换,数据不持久化(或依赖外部存储) | Web 应用、缓存服务(Redis 集群除外) |
| 有状态服务 | 实例有唯一身份(如编号),需持久化数据,实例间有依赖/顺序关系 | 数据库(MySQL)、消息队列(Kafka) |
关键差异:无状态服务 Pod 重建后可直接加入集群;有状态服务 Pod 重建后需保留身份和数据,否则无法正常提供服务。
2. StatefulSet 核心特性
StatefulSet 是 K8s 专门管理有状态服务的控制器,核心特性:
- 稳定的 Pod 身份:Pod 名称为
<StatefulSet名称>-<序号>(如nginx-sts-0、nginx-sts-1),序号从 0 开始递增且唯一; - 有序部署/删除:部署时按序号从 0 到 N 依次创建,删除时按 N 到 0 依次销毁;
- 稳定的网络标识:通过无头服务(Headless Service)为每个 Pod 分配固定域名(
<Pod名称>.<无头服务名>.<命名空间>.svc.cluster.local); - 稳定的存储:通过
volumeClaimTemplates为每个 Pod 自动创建独立 PVC,Pod 重建后仍绑定原 PVC,数据不丢失。
3. StatefulSet 实操:基于 NFS 部署有状态 Nginx 集群
步骤 1:创建无头服务(Headless Service)
无头服务(ClusterIP=None)无负载均衡能力,仅为 Pod 提供稳定 DNS 解析。
# svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
spec:
clusterIP: None # 标记为无头服务
selector:
app: nginx-sts # 匹配 StatefulSet 的 Pod
ports:
- port: 80
targetPort: 80
执行与验证:
kubectl apply -f svc-headless.yaml
# 查看无头服务
kubectl get svc nginx-svc
# 输出:
# NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
# nginx-svc ClusterIP None <none> 80/TCP 10s
步骤 2:创建 StatefulSet(关联 PVC 模板)
# sts-nginx.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-sts
spec:
serviceName: nginx-svc # 关联无头服务
replicas: 3 # 3 个有状态实例
selector:
matchLabels:
app: nginx-sts
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:1.14-alpine
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www # 关联 PVC 模板
mountPath: /usr/local/nginx/html
volumeClaimTemplates: # PVC 模板,每个 Pod 自动创建独立 PVC
- metadata:
name: www
spec:
accessModes: [ "ReadWriteMany" ] # 与 NFS PV 匹配
resources:
requests:
storage: 1Gi # 每个 Pod 申请 1Gi 存储
执行与验证:
- 创建 StatefulSet:
kubectl apply -f sts-nginx.yaml - 查看 Pod 有序创建过程:
watch kubectl get pod # 输出(依次创建 0→1→2): # NAME READY STATUS RESTARTS AGE # nginx-sts-0 1/1 Running 0 10s # nginx-sts-1 1/1 Running 0 5s # nginx-sts-2 1/1 Running 0 2s - 查看自动创建的 PVC(每个 Pod 对应一个 PVC):
(注:若现有 PV 不足,需提前创建更多 NFS PV)kubectl get pvc # 输出: # NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE # www-nginx-sts-0 Bound nfs-pv-0 10Gi RWX 20s # www-nginx-sts-1 Bound nfs-pv-1 5Gi RWX 15s # www-nginx-sts-2 Bound <新PV> 10Gi RWX 10s
步骤 3:验证 StatefulSet 核心特性
(1)稳定的网络标识(DNS 解析)
在集群内任意 Pod 执行 DNS 解析测试:
# 临时创建测试 Pod
kubectl run -it --rm dns-test --image=busybox:1.35 -- sh
# 解析 Pod 域名
nslookup nginx-sts-0.nginx-svc.default.svc.cluster.local
# 输出(包含 Pod 真实 IP):
# Name: nginx-sts-0.nginx-svc.default.svc.cluster.local
# Address 1: <Pod-0-IP>
nslookup nginx-sts-1.nginx-svc.default.svc.cluster.local
# 输出:Address 1: <Pod-1-IP>
(2)稳定的存储(Pod 重建后数据不丢失)
# 1. 给 nginx-sts-0 写入专属数据
kubectl exec nginx-sts-0 -- sh -c "echo 'I am Pod 0' > /usr/local/nginx/html/index.html"
# 2. 删除 nginx-sts-0 触发重建
kubectl delete pod nginx-sts-0
# 3. 重建后验证数据
kubectl exec nginx-sts-0 -- curl localhost
# 输出:I am Pod 0(数据通过 PVC 持久化)
(3)有序扩容/缩容
# 扩容到 4 个实例(按 0→1→2→3 顺序创建)
kubectl scale sts nginx-sts --replicas=4
# 缩容到 2 个实例(按 3→2 顺序删除)
kubectl scale sts nginx-sts --replicas=2
步骤 4:StatefulSet 与 PVC 清理
# 1. 删除 StatefulSet(Pod 会被删除,但 PVC 保留)
kubectl delete sts nginx-sts
# 2. 手动删除 PVC(PVC 删除后 PV 变为 Released 状态)
kubectl delete pvc www-nginx-sts-0 www-nginx-sts-1 www-nginx-sts-2
# 3. 复用 Released 状态的 PV(编辑 PV 删除 claimRef 字段)
kubectl edit pv nfs-pv-0
# 删除 spec.claimRef 字段后保存,PV 状态恢复为 Available
kubectl get pv nfs-pv-0
# 输出:nfs-pv-0 10Gi RWX Retain Available 10m
四、StorageClass 动态存储供给
1. 概念通俗理解
手动创建 PV 存在“资源浪费、扩缩容麻烦”的问题,StorageClass 实现了 PV 动态创建:当 PVC 声明关联的 StorageClass 时,K8s 会通过 provisioner(存储供应器)自动创建符合需求的 PV,无需运维手动预创建。
2. 核心组件
- StorageClass:定义存储类型(如 NFS、Ceph)、
provisioner、回收策略等; - Provisioner:存储供应器,负责实际创建 PV 及底层存储(如 NFS 客户端供应器、云厂商存储供应器);
- PVC:声明
storageClassName,触发动态 PV 创建。
3. 实操:基于 NFS-Client Provisioner 实现动态存储
步骤 1:部署 NFS-Client Provisioner
NFS-Client Provisioner 是社区提供的 NFS 动态存储供应器,需先部署到 K8s 集群。
(1)创建 ServiceAccount 与权限
# nfs-provisioner-rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
(2)部署 NFS-Client Provisioner
# nfs-provisioner-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner # 供应器名称,需与 StorageClass 一致
- name: NFS_SERVER
value: <NFS服务器IP> # 替换为实际 NFS 地址
- name: NFS_PATH
value: /exports/nfs # NFS 共享目录
volumes:
- name: nfs-client-root
nfs:
server: <NFS服务器IP>
path: /exports/nfs
执行部署:
kubectl apply -f nfs-provisioner-rbac.yaml -f nfs-provisioner-deploy.yaml
# 验证 Provisioner 运行状态
kubectl get pod -l app=nfs-client-provisioner
# 输出:
# NAME READY STATUS RESTARTS AGE
# nfs-client-provisioner-7f968c4d9c-9x2zl 1/1 Running 0 20s
步骤 2:创建 StorageClass
# sc-nfs.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client-sc # StorageClass 名称
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 关联 NFS 供应器
parameters:
pathPattern: "${.PVC.namespace}/${.PVC.name}" # 动态 PV 在 NFS 中的目录格式(按命名空间/PVC 名划分)
reclaimPolicy: Retain # 回收策略为保留数据
执行与验证:
kubectl apply -f sc-nfs.yaml
# 查看 StorageClass
kubectl get sc
# 输出:
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# nfs-client-sc k8s-sigs.io/nfs-subdir-external-provisioner Retain Immediate false 10s
步骤 3:创建 PVC 触发动态 PV 创建
# pvc-dynamic.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc-dynamic
spec:
storageClassName: nfs-client-sc # 关联动态存储类
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi # 动态申请 1Gi 存储
执行与验证:
- 创建 PVC:
kubectl apply -f pvc-dynamic.yaml - 查看动态创建的 PV 和 PVC 绑定状态:
kubectl get pvc nfs-pvc-dynamic # 输出: # NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE # nfs-pvc-dynamic Bound pvc-7a8b9c0d-1234-5678-90ab-cdef12345678 1Gi RWX nfs-client-sc 10s kubectl get pv pvc-7a8b9c0d-1234-5678-90ab-cdef12345678 # 输出: # NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE # pvc-7a8b9c0d-1234-5678-90ab-cdef12345678 1Gi RWX Retain Bound default/nfs-pvc-dynamic nfs-client-sc 10s - 验证 NFS 服务端动态创建的目录:
ls /exports/nfs/default/nfs-pvc-dynamic # 对应 pathPattern 定义的目录 # 输出:(PVC 绑定后自动创建空目录) # lost+found
步骤 4:Pod 挂载动态 PVC 验证
# pod-dynamic-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-dynamic-nfs
spec:
containers:
- name: nginx
image: nginx:1.14-alpine
volumeMounts:
- name: dynamic-storage
mountPath: /usr/share/nginx/html
volumes:
- name: dynamic-storage
persistentVolumeClaim:
claimName: nfs-pvc-dynamic
执行与验证:
kubectl apply -f pod-dynamic-nfs.yaml
kubectl exec test-dynamic-nfs -- sh -c "echo 'Dynamic PV from StorageClass' > /usr/share/nginx/html/index.html"
# 验证 NFS 服务端数据
cat /exports/nfs/default/nfs-pvc-dynamic/index.html
# 输出:Dynamic PV from StorageClass
五、实用技巧:K8s 命令补全
为提升 K8s 命令操作效率,可配置 bash 命令自动补全:
# 1. 安装 bash-completion 工具
yum install -y bash-completion
# 2. 配置全局生效
echo "source /usr/share/bash-completion/bash_completion" >> /etc/bashrc
echo "source <(kubectl completion bash)" >> /etc/bashrc
# 3. 立即生效(无需重启终端)
source /etc/bashrc
验证补全功能:
kubectl get po[按Tab键] # 自动补全为 pod
kubectl delete sts[按Tab键] # 自动补全为 statefulset
六、核心知识点总结
- Volume:Pod 级存储,生命周期与 Pod 绑定,适合临时存储或简单共享;
- PV/PVC:集群级存储解耦方案,PV 是资源池,PVC 是申请单,实现存储与业务分离;
- StatefulSet:管理有状态服务的核心控制器,提供稳定身份、网络、存储;
- StorageClass:实现 PV 动态创建,解决手动创建 PV 的资源浪费问题。
Kubernetes(k8s)调度 完整笔记
一、调度器(kube-scheduler)
1. 核心概念
kube-scheduler 是 k8s 集群的核心组件之一,你可以把它理解为集群的“调度员”——负责为新创建的、未指定运行节点的 Pod,选择最合适的 Node(节点)来运行。
它是一个独立运行的程序,核心工作逻辑:
- 启动后持续监听 k8s API Server;
- 当检测到新创建的 Pod 且
podSpec.NodeName字段为空(未指定节点)时,将该 Pod 加入调度队列; - 通过“预选 + 优选”两步策略筛选节点,最终确定 Pod 运行的目标节点。
2. 自定义调度器
k8s 支持替换/新增调度器,无需局限于默认的 kube-scheduler:
- 开发自定义调度器程序(遵循 k8s 调度器规范);
- 创建 Pod 时,通过
spec.schedulerName字段指定自定义调度器名称,Pod 会由该调度器负责调度。
3. 调度核心流程(预选 + 优选)
(1)预选(Predicate):筛选“合格节点”
调度器会遍历集群所有节点,通过一系列“过滤规则”(如节点是否有足够资源、是否匹配 Pod 的基础要求),排除不符合条件的节点,最终得到一个“候选节点列表”。
- 例:若 Pod 请求 2G 内存,所有可用内存 < 2G 的节点会被直接过滤。
(2)优选(Priority):选择“最优节点”
对预选后的候选节点,调度器通过“评分规则”(如节点资源利用率、亲和性权重等)为每个节点打分,最终选择得分最高的节点作为 Pod 的运行节点。
- 例:两个候选节点都满足资源要求,调度器会优先选择资源利用率更低的节点。
二、调度特性
1. 亲和性(Affinity)
亲和性的核心是“让 Pod 按规则‘靠近’特定节点/Pod”,分为节点亲和性和Pod 亲和性,且均支持“硬规则”和“软规则”。
| 类型 | 核心目标 | 硬亲和性(RequiredDuringSchedulingIgnoredDuringExecution) | 软亲和性(PreferredDuringSchedulingIgnoredDuringExecution) |
|---|---|---|---|
| 节点亲和性 | 基于节点标签匹配调度 | 强制匹配标签,无符合节点则 Pod 一直 Pending | 尽量匹配标签,无符合节点也会调度到其他节点 |
| Pod 亲和性 | 基于已有 Pod 标签匹配调度(调度到有特定 Pod 的节点) | 强制匹配,无符合节点则 Pod 一直 Pending | 尽量匹配,无符合节点也会调度到其他节点 |
(1)节点亲和性(Node Affinity)
概念
通过节点的标签筛选目标节点,让 Pod 只调度到符合标签规则的节点上。
案例 1:硬节点亲和性(强制匹配)
需求:Pod 必须调度到带有 env=production 标签的节点上,且节点不能是 disk=ssd 类型。
Pod 配置文件(node-affinity-hard.yaml):
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-hard
spec:
containers:
- name: nginx
image: nginx:alpine
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性
nodeSelectorTerms:
- matchExpressions:
- key: env
operator: In # 操作符:匹配列表中的值
values: ["production"]
- key: disk
operator: NotIn # 操作符:不匹配列表中的值
values: ["ssd"]
执行命令:
kubectl apply -f node-affinity-hard.yaml
输出与验证:
- 若集群中无
env=production且disk!=ssd的节点,执行kubectl get pods会看到 Pod 状态为Pending:NAME READY STATUS RESTARTS AGE node-affinity-hard 0/1 Pending 0 10s - 查看 Pod 事件确认原因:
输出示例:kubectl describe pod node-affinity-hard | grep -A 5 EventsEvents: Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 12s default-scheduler 0/3 nodes are available: 3 node(s) didn't match node selector terms. preemption: 0/3 nodes are available: 3 Preemption is not helpful for scheduling. - 给某节点添加标签后重试:
再次查看 Pod 状态会变为kubectl label nodes node-01 env=production kubectl label nodes node-01 disk=hddRunning:NAME READY STATUS RESTARTS AGE node-affinity-hard 1/1 Running 0 30s
案例 2:软节点亲和性(尽量匹配)
需求:尽量将 Pod 调度到 region=north 的节点,若没有则调度到其他节点。
Pod 配置文件(node-affinity-soft.yaml):
apiVersion: v1
kind: Pod
metadata:
name: node-affinity-soft
spec:
containers:
- name: nginx
image: nginx:alpine
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软亲和性
- weight: 80 # 权重(1-100),权重越高优先级越高
preference:
matchExpressions:
- key: region
operator: In
values: ["north"]
执行与验证:
- 若集群无
region=north的节点,Pod 仍会调度到其他节点,执行kubectl get pods -o wide可看到 Pod 运行在非目标节点,但事件中会提示“未匹配优选规则”:Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal Scheduled 5s default-scheduler Successfully assigned default/node-affinity-soft to node-02 Normal Pulling 4s kubelet Pulling image "nginx:alpine" Normal Pulled 2s kubelet Successfully pulled image "nginx:alpine" in 1.8s Normal Created 2s kubelet Created container nginx Normal Started 2s kubelet Started container nginx
(2)Pod 亲和性(Pod Affinity)
概念
基于集群中已运行 Pod 的标签,将新 Pod 调度到“有这些 Pod 的节点”上(常用于服务就近部署,如应用 Pod 和缓存 Pod 部署在同一节点)。
核心参数
labelSelector:匹配已有 Pod 的标签选择器;topologyKey:拓扑域(如kubernetes.io/hostname按节点、node.kubernetes.io/zone按可用区),表示“在同一个拓扑域内匹配”;weight(仅软亲和性):权重(1-100),用于优选阶段打分。
案例:硬 Pod 亲和性
需求:Pod 必须调度到有 app=redis 标签的 Pod 所在的节点(同一节点)。
步骤 1:先创建一个带 app=redis 标签的 Pod:
# redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
labels:
app: redis
spec:
containers:
- name: redis
image: redis:alpine
kubectl apply -f redis-pod.yaml
步骤 2:创建带 Pod 硬亲和性的应用 Pod:
# pod-affinity-hard.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod-affinity-hard
spec:
containers:
- name: app
image: nginx:alpine
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬亲和性
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["redis"]
topologyKey: kubernetes.io/hostname # 按节点拓扑域匹配
执行与验证:
- 执行
kubectl apply -f pod-affinity-hard.yaml; - 查看 Pod 调度结果:
输出示例(两个 Pod 运行在同一节点kubectl get pods -o widenode-01):NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pod-affinity-hard 1/1 Running 0 15s 10.244.1.5 node-01 <none> <none> redis-pod 1/1 Running 0 1m 10.244.1.4 node-01 <none> <none> - 若删除
redis-pod,新创建的pod-affinity-hard会处于Pending状态(无符合条件的节点)。
2. 反亲和性(Anti-Affinity)
概念
核心是“让 Pod 远离特定节点/Pod”,分为节点反亲和性(远离特定标签的节点)和Pod 反亲和性(远离有特定 Pod 的节点),同样支持硬/软规则。
核心场景
- 避免同一服务的多个 Pod 调度到同一节点(提高容灾性);
- 避免高负载 Pod 调度到同一节点。
案例:软 Pod 反亲和性
需求:尽量避免 app=web 的 Pod 调度到同一节点(同一可用区)。
# pod-anti-affinity-soft.yaml
apiVersion: v1
kind: Pod
metadata:
name: web-pod-1
labels:
app: web
spec:
containers:
- name: web
image: nginx:alpine
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软反亲和性
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["web"]
topologyKey: kubernetes.io/hostname # 按节点拓扑域反亲和
验证:
- 先创建
web-pod-1,运行在node-01; - 再创建
web-pod-2(同配置),调度器会优先将其调度到node-02,若集群只有一个节点,仍会调度到node-01,但事件中会提示“优选反亲和规则未匹配”。
3. 污点(Taints)与容忍(Tolerations)
(1)污点(Taints):节点的“排斥规则”
概念
污点是节点上的标记,用于“拒绝”Pod 调度到该节点(除非 Pod 有对应的容忍)。你可以把污点理解为节点的“门禁”——没有门禁卡(容忍)的 Pod 无法进入。
格式
key=value:effect(value 可省略,仅保留 key:effect):
key:污点标识(如node-role.kubernetes.io/master);value:污点具体值(可选,如gpu=true);effect:污点效果(核心):效果类型 作用 NoSchedule 拒绝调度新 Pod 到该节点(已运行的 Pod 不受影响) PreferNoSchedule 尽量拒绝调度新 Pod 到该节点(非强制,无其他节点时仍会调度) NoExecute 拒绝调度新 Pod,且立即驱逐该节点上已运行的无对应容忍的 Pod
案例 1:给节点添加/查询/移除污点
步骤 1:给 master-01 节点添加污点(拒绝调度带 gpu 需求的 Pod):
kubectl taint nodes master-01 computeengine=gpu:NoSchedule
输出:node/master-01 tainted
步骤 2:查询节点污点:
kubectl describe node master-01 | grep -A 1 Taints
输出示例:
Taints: computeengine=gpu:NoSchedule
node-role.kubernetes.io/master:NoSchedule
步骤 3:移除污点(在 effect 后加 -):
# 移除带 value 的污点
kubectl taint nodes master-01 computeengine=gpu:NoSchedule-
# 移除无 value 的污点(如 master 节点默认污点)
kubectl taint nodes master-01 node-role.kubernetes.io/master:NoSchedule-
输出:node/master-01 untainted
案例 2:k8s 集群默认污点
kubeadm 部署的集群,master 节点默认添加污点 node-role.kubernetes.io/master:NoSchedule,目的是防止普通 Pod 调度到 master 节点(避免 master 负载过高)。
(2)容忍(Tolerations):Pod 的“门禁卡”
概念
容忍是 Pod 上的配置,用于“忽略”节点的特定污点,让 Pod 可以调度到有该污点的节点上。
核心参数
tolerations:
- key: "污点key"
operator: "Equal/Exists" # 匹配规则:Equal(key+value 全匹配)、Exists(仅匹配 key)
value: "污点value"
effect: "NoSchedule/PreferNoSchedule/NoExecute" # 匹配的污点效果
tolerationSeconds: 3600 # 仅对 NoExecute 生效:Pod 被驱逐前的容忍时间(秒)
- 特殊情况:可省略
key/value,仅保留operator: Exists(匹配所有污点)。
案例:Pod 容忍 master 节点的默认污点
需求:让普通 Pod 调度到 master 节点(需先容忍 master 的默认污点)。
# toleration-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: toleration-pod
spec:
containers:
- name: nginx
image: nginx:alpine
tolerations:
- key: node-role.kubernetes.io/master
operator: Exists # 仅匹配 key 存在,忽略 value
effect: NoSchedule # 匹配污点的 effect
执行与验证:
kubectl apply -f toleration-pod.yaml
kubectl get pods -o wide
输出示例(Pod 运行在 master-01 节点):
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
toleration-pod 1/1 Running 0 20s 10.244.0.5 master-01 <none> <none>
特殊场景:kube-proxy 的容忍
kube-proxy 是 DaemonSet 控制器管理的 Pod,需要在所有节点(包括 master)运行,因此其配置包含“匹配所有污点”的容忍:
tolerations:
- operator: Exists # 匹配任意污点
effect: NoSchedule
- operator: Exists
effect: NoExecute
4. 固定节点调度
(1)nodeName:强制指定节点(忽略污点)
概念
直接通过 spec.nodeName 指定 Pod 运行的节点名称,调度器会完全跳过调度流程,强制将 Pod 调度到该节点(即使节点有污点、资源不足)。
案例
# nodeName-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nodeName-pod
spec:
nodeName: master-01 # 强制调度到 master-01
containers:
- name: nginx
image: nginx:alpine
关键特性:忽略节点污点,即使 master-01 有 NoSchedule 污点,Pod 仍会调度到该节点。
(2)nodeSelector:基于标签匹配节点(不忽略污点)
概念
通过节点标签筛选目标节点,Pod 仅调度到带有指定标签的节点上,但会受节点污点影响(无容忍则无法调度)。
案例
步骤 1:给节点添加标签:
kubectl label nodes node-01 type=virtual-machine
步骤 2:创建带 nodeSelector 的 Pod:
# nodeSelector-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nodeSelector-pod
spec:
nodeSelector:
type: virtual-machine # 仅调度到带该标签的节点
containers:
- name: nginx
image: nginx:alpine
关键注意:若 node-01 有污点且 Pod 无对应容忍,Pod 会处于 Pending 状态(匹配标签但无法容忍污点)。
三、调度策略总结表
| 调度策略 | 匹配依据 | 核心参数/操作符 | 拓扑域(仅亲和性) | 是否忽略节点污点 |
|---|---|---|---|---|
| 节点亲和性 | 节点标签 | In/NotIn/Exists/DoesNotExist | 无 | 否 |
| Pod 亲和性 | 已有 Pod 标签 + 拓扑域 | In/NotIn/Exists/DoesNotExist | topologyKey | 否 |
| Pod 反亲和性 | 已有 Pod 标签 + 拓扑域 | In/NotIn/Exists/DoesNotExist | topologyKey | 否 |
| 污点/容忍 | 节点污点 + Pod 容忍 | Equal/Exists | 无 | 容忍后可忽略 |
| nodeName | 节点名称 | 直接指定节点名 | 无 | 是(强制忽略) |
| nodeSelector | 节点标签 | 键值对匹配 | 无 | 否 |
四、常见错误修正与注意事项
-
原大纲错误修正:
- 错误:“节点亲和性的软性亲和性描述为‘调度器会尽量将 Pod 调度到与其他 Pod 亲和的 Node 上’”→ 修正:节点亲和性仅基于节点标签,Pod 亲和性才基于已有 Pod 标签;
- 错误:“容忍的软/硬分类”→ 修正:容忍无软/硬分类,仅通过
effect匹配污点效果,“软/硬”仅针对亲和性; - 错误:“nodeSelector 要求节点无污点”→ 修正:nodeSelector 仅匹配标签,若节点有污点但 Pod 有对应容忍,仍可调度。
-
核心注意事项:
- 亲和性/反亲和性的
topologyKey不能随便指定,需确保集群节点有该标签(如kubernetes.io/hostname是所有节点默认标签); - 污点的
NoExecute效果会驱逐已运行的 Pod,需谨慎使用; nodeName优先级最高,会跳过所有调度规则(包括亲和性、污点),仅用于测试/特殊场景。
- 亲和性/反亲和性的
K8s调度核心安全机制笔记
Kubernetes(简称K8s)作为分布式集群管理平台,集群安全性是其核心能力之一。K8s的安全机制围绕API Server构建——API Server是集群内部组件通信的“中枢”,也是接收外部客户端请求的“入口”,所有请求都需经过“认证→鉴权→准入机制”三道防线,确保集群资源被合法、合规地访问和操作。
一、安全机制整体说明
K8s安全机制的核心目标是“可控访问”,即:确保只有合法的请求者,才能在授权范围内,执行合规的操作。这三道防线层层递进、各司其职:
-
认证(Authentication):解决“你是谁”的问题——验证请求者的身份是否合法(如是否为集群认可的组件、用户或Pod)。
-
鉴权(Authorization):解决“你能做什么”的问题——在认证通过后,判断请求者对某类资源是否拥有对应的操作权限(如是否能删除Pod、修改Service)。
-
准入机制(Admission Control):解决“操作是否合规”的问题——在鉴权通过后,对请求进行额外校验和修改(如禁止创建特权容器、强制给Pod添加标签),是安全机制的“最后一道关卡”。
只有当请求依次通过这三道防线后,API Server才会执行对应的操作(如创建Pod、删除Node)。任何一道防线拦截请求,都会直接返回错误。
二、认证:验证身份的合法性
认证的核心是“身份校验”,K8s支持多种认证方式,核心可分为“集群组件认证”“外部用户认证”和“Pod认证”三类。其中,基于证书的认证和Service Account认证是最常用的两种方式。
2.1 核心认证方式:基于证书的认证
K8s集群内部组件(如kubelet、kube-proxy、controller-manager)与API Server通信时,默认采用“基于X.509证书”的认证方式。证书由集群的CA(证书颁发机构)签发,具备唯一性和不可伪造性,确保组件身份可信。
2.1.1 证书认证的核心逻辑
-
集群初始化时(如kubeadm init),会自动生成CA根证书(存放在
/etc/kubernetes/pki/ca.crt),作为所有证书的“信任根”。 -
每个组件(如kubelet)都持有CA签发的“客户端证书”(包含组件身份信息,如CN字段标识组件名称)和“私钥”。
-
组件向API Server发起请求时,会在请求头中携带客户端证书。
-
API Server使用CA根证书验证客户端证书的有效性:①证书是否由可信CA签发;②证书是否在有效期内;③证书中的身份信息是否合法。验证通过则身份认证成功。
2.1.2 证书签发的两种模式
| 签发模式 | 适用场景 | 操作方式 | 优缺点 |
|---|---|---|---|
| 手动签发 | 集群管理员、kubectl客户端等长期稳定的身份 | 使用cfssl等工具生成证书请求,通过CA根证书签名 | 优点:可控性高;缺点:运维成本高,不适合临时身份 |
| 自动签发 | kubelet等动态组件(节点加入集群时自动生成) | kubeadm join命令触发,由集群CA自动签发证书 | 优点:运维成本低,适配组件动态变化;缺点:需依赖kubeadm等工具 |
2.1.3 实战:kubeadm join命令解析(节点自动认证加入集群)
新节点加入K8s集群时,通过kubeadm join 192.168.0.11:6443 --token abcdef.1234567890abcdef --discovery-token-ca-cert-hash sha256:xxx命令完成“身份认证+集群接入”,核心流程如下:
1. 命令参数拆解
| 参数 | 作用 |
|---|---|
| 192.168.0.11:6443 | 集群控制平面(API Server)的地址和端口 |
| --token | 临时身份凭证(kubeadm init时生成,有效期24小时),用于新节点身份预验证 |
| --discovery-token-ca-cert-hash | 集群CA根证书的哈希值,用于新节点验证集群合法性(防止接入恶意集群) |
2. 执行核心流程(四步完成认证与接入)
-
发现阶段:新节点通过
ip:port访问API Server,请求集群基本信息(CA证书、API Server地址等)。 -
双向校验阶段: 新节点:用
--discovery-token-ca-cert-hash校验集群返回的CA证书哈希,确认集群是可信的(避免“钓鱼”集群)。 -
控制平面:用
--token校验新节点身份,确认是授权接入的节点(token无效则拒绝)。 -
配置阶段:校验通过后,kubeadm在新节点自动完成: 生成kubelet配置文件(
/var/lib/kubelet/config.yaml),写入API Server地址、认证方式等信息。 -
由集群CA自动为新节点的kubelet签发客户端证书(存放在
/var/lib/kubelet/pki/)。 -
kubelet通过证书向API Server注册节点信息(如节点IP、资源容量)。
-
完成阶段:API Server将节点状态标记为
Ready,新节点可接收调度器分配的Pod。
3. 执行效果验证
在控制平面节点执行以下命令,确认新节点接入成功:
# 查看所有节点状态,新节点状态为Ready
kubectl get nodes
# 输出示例(新节点名为node-2)
NAME STATUS ROLES AGE VERSION
master Ready master 1d v1.27.0
node-2 Ready <none> 5m v1.27.0
# 查看新节点的kubelet证书信息
ssh node-2 "ls /var/lib/kubelet/pki/"
# 输出示例(包含CA签发的客户端证书)
kubelet-client-2024-05-20-08-30-15.pem kubelet.crt kubelet.key
2.2 关键配置:kubeconfig文件详解
kubectl等客户端工具通过“kubeconfig文件”与API Server通信,该文件整合了“集群信息、客户端认证信息、访问上下文”三大核心内容,是认证的“载体”。
2.2.1 核心作用
如果没有kubeconfig文件(默认路径~/.kube/config),执行kubectl get pod会直接报错,因为kubectl无法获取认证信息和API Server地址:
kubectl get pod
# 报错输出
The connection to the server localhost:8080 was refused - did you specify the right host or port?
2.2.2 文件结构解析(以admin.conf为例)
kubeadm初始化集群时生成的/etc/kubernetes/admin.conf是管理员级别的kubeconfig文件,结构如下(关键字段已标注):
apiVersion: v1
kind: Config
clusters: # 集群信息:告诉kubectl要连接哪个集群
- cluster:
certificate-authority: /etc/kubernetes/pki/ca.crt # 信任的CA根证书(验证API Server身份)
server: https://192.168.0.11:6443 # API Server地址
name: kubernetes
users: # 客户端认证信息:告诉API Server“我是谁”
- name: kubernetes-admin
user:
client-certificate: /etc/kubernetes/pki/admin.crt # 管理员客户端证书
client-key: /etc/kubernetes/pki/admin.key # 管理员私钥
contexts: # 上下文:关联“集群”和“用户”,定义默认访问规则
- context:
cluster: kubernetes # 关联的集群
user: kubernetes-admin # 关联的用户
namespace: default # 默认访问的命名空间
name: kubernetes-admin@kubernetes
current-context: kubernetes-admin@kubernetes # 当前使用的上下文
2.3 Pod的专属认证:Service Account
集群内部的Pod(如CoreDNS、Calico)需要访问API Server时,不使用证书认证,而是通过“Service Account(服务账号,简称SA)”认证——因为Pod是临时、动态的(如Deployment扩缩容会创建新Pod),证书的静态属性无法适配Pod的生命周期。
2.3.1 为什么Pod必须用SA认证?
证书认证对Pod的致命问题:
-
静态性冲突:证书绑定固定的身份信息(如Pod名称、IP),但Pod重建后名称、IP、UID都会变化,旧证书直接失效。
-
生命周期不匹配:证书有有效期,而Pod可能秒级创建/销毁,手动轮换证书完全不现实。
-
管理复杂度高:集群中可能有上千个Pod,为每个Pod手动生成、挂载证书是运维灾难。
2.3.2 SA的核心组成与默认行为
SA是K8s内置的资源对象,每个SA包含三组关键信息,会自动挂载到Pod的/var/run/secrets/kubernetes.io/serviceaccount目录:
| 组成部分 | 作用 |
|---|---|
| token | API Server签发的临时令牌,Pod用它向API Server证明身份 |
| ca.crt | 集群CA根证书,Pod用它验证API Server的身份(防止访问恶意服务) |
| namespace | 记录SA所属的命名空间,限制Pod的访问范围 |
2.3.3 SA的默认规则
-
每个命名空间默认有一个名为
default的SA(创建命名空间时自动生成)。 -
Pod创建时如果不指定SA,会自动使用其所在命名空间的
defaultSA。 -
只有通过
automountServiceAccountToken: true(默认开启)配置,SA信息才会挂载到Pod内部。
2.3.4 实战:查看Pod中的SA信息
以kube-system命名空间下的CoreDNS Pod为例,验证SA的挂载和内容:
# 1. 查看CoreDNS使用的SA
kubectl get pod -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].spec.serviceAccountName}'
# 输出:coredns(CoreDNS专属SA,非default)
# 2. 进入CoreDNS Pod内部
kubectl exec -it -n kube-system $(kubectl get pod -n kube-system -l k8s-app=kube-dns -o jsonpath='{.items[0].metadata.name}') -- sh
# 3. 查看SA挂载目录
ls /var/run/secrets/kubernetes.io/serviceaccount/
# 输出:ca.crt namespace token
# 4. 查看namespace文件(确认SA所属命名空间)
cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
# 输出:kube-system
# 5. 查看token(部分内容省略,实际为长字符串)
cat /var/run/secrets/kubernetes.io/serviceaccount/token
# 输出:eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50...
三、鉴权:控制权限的范围
认证通过后,鉴权阶段判断“请求者能对资源做什么”。API Server通过--authorization-mode参数指定鉴权策略,当前K8s默认使用RBAC(基于角色的访问控制),因其灵活、可扩展,能满足绝大多数场景。
3.1 主流鉴权策略对比
| 策略名称 | 核心逻辑 | 适用场景 |
|---|---|---|
| AlwaysDeny | 拒绝所有请求 | 仅用于测试,实际集群不使用 |
| AlwaysAllow | 允许所有请求 | 无安全需求的测试集群,生产环境禁用 |
| ABAC | 基于属性(如用户、资源、操作)配置规则 | 复杂定制化场景,配置繁琐,已被RBAC替代 |
| Webhook | 调用外部REST服务判断权限 | 需与企业统一权限系统集成的场景 |
| RBAC(默认) | 基于“角色”分配权限,角色绑定用户/SA | 所有场景,灵活可控,支持命名空间隔离 |
3.2 RBAC核心概念与资源对象
RBAC的核心思想是:将权限打包成“角色”,再将“角色”绑定给“用户/SA”,实现权限的批量管理。RBAC包含4个核心资源对象,分为“角色类”和“绑定类”两组:
3.2.1 核心资源对象
| 资源类型 | 具体对象 | 作用 | 命名空间属性 |
|---|---|---|---|
| 角色类(定义权限) | Role(角色) | 定义某一命名空间内的权限集合 | 有(必须指定namespace) |
| ClusterRole(集群角色) | 定义集群级别的权限集合(跨命名空间) | 无(集群全局生效) | |
| 绑定类(分配权限) | RoleBinding(角色绑定) | 将Role/ClusterRole的权限绑定给用户/SA,仅在指定命名空间生效 | 有(必须指定namespace) |
| ClusterRoleBinding(集群角色绑定) | 将ClusterRole的权限绑定给用户/SA,集群全局生效 | 无(集群全局生效) |
3.2.2 RBAC权限分配逻辑
权限分配需经过“定义角色→绑定角色”两步,核心关系如下:
用户/用户组/Service Account ←(通过)→ 绑定对象(RoleBinding/ClusterRoleBinding) ←(引用)→ 角色对象(Role/ClusterRole) ←(包含)→ 具体权限(资源+操作)
关键规则:
-
Role只能通过RoleBinding绑定,且权限仅作用于自身命名空间。
-
ClusterRole可通过两种方式绑定: 与ClusterRoleBinding绑定:权限作用于集群所有命名空间。
-
与RoleBinding绑定:权限仅作用于RoleBinding所在的命名空间(实现“集群权限的局部复用”)。
-
RBAC权限是“累加的”,不存在“权限回收”——只能通过删除角色或绑定关系移除权限。
3.3 RBAC资源对象实战配置
以下通过完整YAML配置示例,展示不同角色与绑定的使用场景。
3.3.1 Role:命名空间内的权限定义
定义default命名空间内“只读Pod”的权限(仅允许get、watch、list操作):
apiVersion: rbac.authorization.k8s.io/v1 # RBAC固定API组
kind: Role # 资源类型为Role
metadata:
namespace: default # 必须指定命名空间,仅作用于default
name: pod-reader # 角色名称
rules: # 权限规则列表
- apiGroups: [""] # 核心API组(无组名),对应Pod、Service等核心资源
resources: ["pods"] # 权限作用的资源(此处为Pod)
verbs: ["get", "watch", "list"] # 允许的操作(只读权限)
# 可选:用resourceNames限定仅对特定Pod生效(如resourceNames: ["my-pod"])
创建并验证Role:
kubectl apply -f role-pod-reader.yaml
# 查看default命名空间的Role
kubectl get role -n default
# 输出:
NAME CREATED AT
pod-reader 2024-05-20T09:30:00Z
# 查看Role详情(包含权限规则)
kubectl describe role pod-reader -n default
3.3.2 ClusterRole:集群级别的权限定义
定义集群级“只读Secret”的权限(可作用于所有命名空间):
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole # 资源类型为ClusterRole
metadata:
name: secret-reader # 集群角色名称(无命名空间)
rules:
- apiGroups: [""]
resources: ["secrets"] # 权限作用于Secret资源
verbs: ["get", "watch", "list"]
# ClusterRole还支持非资源型权限(如访问/healthz接口)
- nonResourceURLs: ["/healthz", "/metrics"] # 非资源URL
verbs: ["get"] # 允许访问这些接口
创建并验证ClusterRole:
kubectl apply -f clusterrole-secret-reader.yaml
# 查看集群所有ClusterRole(包含系统预设角色)
kubectl get clusterrole
# 过滤自定义角色
kubectl get clusterrole secret-reader
# 输出:
NAME CREATED AT
secret-reader 2024-05-20T09:40:00Z
3.3.3 RoleBinding:绑定角色到用户(命名空间内)
将上述pod-reader角色绑定给用户userA,使userA在default命名空间拥有Pod只读权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding # 资源类型为RoleBinding
metadata:
namespace: default # 与Role的命名空间一致
name: pod-reader-binding
subjects: # 权限接收者(用户/SA/用户组)
- kind: User # 接收者类型为用户
name: userA # 用户名(需提前通过认证配置)
apiGroup: rbac.authorization.k8s.io # 固定API组
roleRef: # 引用的角色(必须与Role/ClusterRole对应)
kind: Role # 引用的是Role类型
name: pod-reader # 引用的角色名称
apiGroup: rbac.authorization.k8s.io # 固定值
3.3.4 RoleBinding+ClusterRole:集群权限的局部复用
将secret-reader集群角色通过RoleBinding绑定给userA,仅让userA在dev命名空间拥有Secret只读权限(集群角色的局部生效):
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
namespace: dev # 仅在dev命名空间生效
name: secret-reader-dev-binding
subjects:
- kind: User
name: userA
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole # 引用的是ClusterRole类型
name: secret-reader # 引用的集群角色名称
apiGroup: rbac.authorization.k8s.io
3.3.5 ClusterRoleBinding:集群级权限绑定
将secret-reader集群角色绑定给manager用户组,使该组所有用户在所有命名空间拥有Secret只读权限:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding # 资源类型为ClusterRoleBinding
metadata:
name: secret-reader-cluster-binding
subjects:
- kind: Group # 接收者类型为用户组
name: manager # 用户组名称
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: ClusterRole
name: secret-reader
apiGroup: rbac.authorization.k8s.io
3.3.6 子资源权限配置(如Pod的logs)
部分资源存在“子资源”(如Pod的logs、exec),需用资源/子资源的格式定义权限。示例:允许读取default命名空间下Pod的日志:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-log-reader
rules:
- apiGroups: [""]
resources: ["pods/log"] # pods的logs子资源
verbs: ["get", "list", "watch"]
3.4 实战:创建仅管理dev命名空间的用户
需求:创建用户dev-user,仅允许管理dev命名空间的所有资源(拥有admin权限),无法访问其他命名空间。完整流程:创建证书→生成kubeconfig→创建命名空间→绑定角色。
3.4.1 步骤1:安装证书生成工具cfssl
在控制平面节点执行,用于生成用户证书:
# 下载cfssl工具
wget https://pkg.cfssl.org/R1.2/cfssl_linux-amd64
wget https://pkg.cfssl.org/R1.2/cfssljson_linux-amd64
# 赋予执行权限并移动到系统路径
chmod +x cfssl_linux-amd64 cfssljson_linux-amd64
sudo mv cfssl_linux-amd64 /usr/local/bin/cfssl
sudo mv cfssljson_linux-amd64 /usr/local/bin/cfssljson
# 验证安装
cfssl version
# 输出示例(表示安装成功)
Version: 1.2.0
Revision: dev
Runtime: go1.6
3.4.2 步骤2:生成dev-user的证书请求与证书
-
创建证书请求文件(dev-csr.json):
{"CN": "dev-user", # 用户名(关键,与后续kubeconfig中的用户对应)"key": {"algo": "rsa","size": 2048},"names": [{"C": "CN", # 国家"ST": "Beijing", # 省份"L": "Beijing", # 城市"O": "k8s", # 组织(可自定义)"OU": "dev-team" # 部门(可自定义)}]} -
生成证书与私钥(使用集群CA根证书签名):
# 进入集群CA证书目录(kubeadm部署的默认路径)cd /etc/kubernetes/pki/# 生成证书请求并通过CA签名(生成dev-user的证书和私钥)cfssl gencert -ca=ca.crt -ca-key=ca.key -config=./ca-config.json -profile=kubernetes dev-csr.json | cfssljson -bare dev-user# 查看生成的文件ls dev-user*# 输出示例(关键文件为dev-user.pem和dev-user-key.pem)dev-user.csr dev-user-key.pem dev-user.pem说明:ca-config.json是集群CA的配置文件,kubeadm部署时自动生成,定义了证书的有效期和用途。
3.4.3 步骤3:生成dev-user的kubeconfig文件
kubeconfig文件整合集群信息和用户认证信息,用于kubectl访问集群:
# 1. 定义环境变量(根据实际集群信息修改)
export KUBE_APISERVER="https://192.168.0.11:6443" # API Server地址
export KUBE_USER="dev-user" # 用户名(与证书CN一致)
export KUBE_NAMESPACE="dev" # 目标命名空间
export KUBECONFIG_PATH="/etc/kubernetes/dev-user.conf" # kubeconfig文件路径
# 2. 设置集群信息(告诉kubectl连接哪个集群)
kubectl config set-cluster kubernetes \
--server=${KUBE_APISERVER} \
--certificate-authority=/etc/kubernetes/pki/ca.crt \ # 信任集群CA
--kubeconfig=${KUBECONFIG_PATH}
# 3. 设置客户端认证信息(告诉API Server“我是谁”)
kubectl config set-credentials ${KUBE_USER} \
--client-certificate=/etc/kubernetes/pki/dev-user.pem \ # 用户证书
--client-key=/etc/kubernetes/pki/dev-user-key.pem \ # 用户私钥
--kubeconfig=${KUBECONFIG_PATH}
# 4. 设置上下文(关联集群、用户和命名空间)
kubectl config set-context ${KUBE_USER}@kubernetes \
--cluster=kubernetes \
--user=${KUBE_USER} \
--namespace=${KUBE_NAMESPACE} \
--kubeconfig=${KUBECONFIG_PATH}
# 5. 设置默认上下文(无需每次指定--context)
kubectl config use-context ${KUBE_USER}@kubernetes \
--kubeconfig=${KUBECONFIG_PATH}
3.4.4 步骤4:创建dev命名空间并绑定角色
将集群预设的admin角色(拥有命名空间内所有权限)绑定给dev-user,仅作用于dev命名空间:
# 1. 创建dev命名空间
kubectl create namespace dev
# 输出:namespace/dev created
# 2. 绑定角色(使用RoleBinding,限定在dev命名空间)
kubectl create rolebinding ${KUBE_USER}-admin-binding \
--clusterrole=admin \ # 引用集群预设的admin角色
--user=${KUBE_USER} \ # 绑定给dev-user
--namespace=${KUBE_NAMESPACE}
# 输出:rolebinding.rbac.authorization.k8s.io/dev-user-admin-binding created
# 3. 验证角色绑定
kubectl get rolebinding -n dev
# 输出:
NAME ROLE AGE
dev-user-admin-binding ClusterRole/admin 10s
3.4.5 步骤5:验证权限(核心环节)
通过切换kubeconfig文件,验证dev-user的权限范围:
# 1. 备份当前管理员的kubeconfig(避免覆盖)
mv ~/.kube/config ~/.kube/config.bak
# 2. 使用dev-user的kubeconfig
cp ${KUBECONFIG_PATH} ~/.kube/config
chmod 600 ~/.kube/config # 必须设置600权限,否则kubectl报错
# 3. 验证dev命名空间的访问权限(正常访问)
kubectl get pod -n dev
# 输出(无Pod时为空,无报错)
No resources found in dev namespace.
# 4. 尝试创建Pod(正常执行)
kubectl run nginx --image=nginx -n dev
# 输出:pod/nginx created
# 5. 验证Pod创建成功
kubectl get pod -n dev
# 输出:
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 30s
# 6. 尝试访问default命名空间(被拒绝,权限验证通过)
kubectl get pod -n default
# 报错输出(符合预期,无权限访问)
Error from server (Forbidden): pods is forbidden: User "dev-user" cannot list resource "pods" in API group "" in the namespace "default"
3.4.6 步骤6:恢复管理员权限
mv ~/.kube/config.bak ~/.kube/config
3.5 关键补充:资源与角色的匹配规则
不同类型的资源需要匹配对应的角色和绑定类型,核心规则如下:
| 访问的资源类型 | 推荐角色类型 | 推荐绑定类型 | 示例资源 |
|---|---|---|---|
| 集群级资源(跨命名空间) | ClusterRole | ClusterRoleBinding | Node、PersistentVolume、StorageClass |
| 非资源型资源(如接口) | ClusterRole | ClusterRoleBinding | /healthz、/metrics、/version |
| 某一命名空间内的资源 | Role 或 ClusterRole | RoleBinding | Pod、Deployment、Service、ConfigMap |
| 所有命名空间的同名资源 | ClusterRole | ClusterRoleBinding | 所有命名空间的Secret、Pod |
3.6 K8s预设的集群角色
K8s内置了多个常用的ClusterRole,可直接使用,无需手动定义:
| 预设角色 | 核心权限 | 适用场景 |
|---|---|---|
| cluster-admin | 集群所有资源的所有操作权限 | 集群管理员 |
| admin | 某一命名空间内所有资源的操作权限(除集群级资源) | 命名空间管理员 |
| edit | 某一命名空间内资源的读写权限(无删除权限) | 开发人员 |
| view | 某一命名空间内资源的只读权限 | 测试/运维人员(仅查看) |
查看所有预设角色:kubectl get clusterrole。
四、准入机制:过滤合规性的最后防线
准入机制是K8s安全机制的“最后一关”——在认证和鉴权通过后,对请求进行额外的“合规性校验”和“资源修改”,确保操作符合集群的统一规则。
4.1 核心作用与特点
-
合规性过滤:拦截“有权限但不合理”的操作,如: 禁止创建特权容器(可能提权攻击)。
-
强制Pod添加指定标签(如“环境=dev”)。
-
限制Pod使用的CPU/内存资源(避免资源耗尽)。
-
资源自动修改:无需用户手动配置,自动为资源添加必要信息,如: 自动为Pod挂载默认SA的token。
-
自动为Pod注入Sidecar容器(如Istio的代理容器)。
-
执行时机:仅在“创建、更新、删除”资源时执行,查询资源(如get、list)时不执行。
4.2 准入控制器类型
准入机制通过“准入控制器(Admission Controller)”实现,K8s内置了数十种控制器,常用的核心控制器如下:
| 控制器名称 | 核心功能 |
|---|---|
| NamespaceLifecycle | 禁止在不存在的命名空间创建资源;禁止删除默认命名空间(default、kube-system等) |
| LimitRanger | 强制Pod遵守命名空间的资源限制(如CPU/内存的最小/最大值) |
| ResourceQuota | 限制命名空间的总资源使用(如最多创建100个Pod) |
| ServiceAccount | 自动为未指定SA的Pod挂载默认SA的token和CA证书 |
| PodSecurity | 基于Pod安全标准(Privileged/Baseline/Restricted)限制Pod权限,禁止特权容器 |
| MutatingAdmissionWebhook | 调用外部Web服务修改资源(如Istio注入Sidecar) |
| ValidatingAdmissionWebhook | 调用外部Web服务验证资源合规性(如自定义权限校验) |
4.3 实战:用PodSecurity控制器禁止特权容器
PodSecurity是K8s 1.25+默认启用的控制器,通过“Pod安全级别”限制Pod权限。示例:将default命名空间设置为Restricted级别(禁止特权容器)。
# 1. 为default命名空间添加Pod安全标签(设置为Restricted级别)
kubectl label namespace default pod-security.kubernetes.io/enforce=restricted
# 输出:namespace/default labeled
# 2. 尝试创建特权容器(会被准入控制器拦截)
kubectl run privileged-pod --image=nginx --privileged=true
# 报错输出(符合预期,被PodSecurity拦截)
Error from server (Forbidden): pods "privileged-pod" is forbidden: violates PodSecurity "Restricted:v1.24": privileged (container "privileged-pod" must not set securityContext.privileged=true)
# 3. 创建非特权容器(正常执行)
kubectl run normal-pod --image=nginx
# 输出:pod/normal-pod created
4.4 准入机制与认证/鉴权的区别
| 维度 | 认证 | 鉴权 | 准入机制 |
|---|---|---|---|
| 核心目标 | 验证“身份合法” | 验证“权限足够” | 验证“操作合规” |
| 判断依据 | 证书、token等身份凭证 | RBAC角色与绑定关系 | 集群规则(如Pod安全标准、资源限制) |
| 可修改资源? | 否 | 否 | 是(如自动挂载SA、注入Sidecar) |
五、安全机制整体流程总结
K8s的安全机制是“层层递进”的流水线,任何请求都需依次通过以下流程:
-
请求到达API Server:客户端(kubectl、Pod、组件)发起请求(如创建Pod)。
-
认证阶段:API Server验证请求者身份——如用证书验证kubelet、用SA token验证Pod、用kubeconfig验证kubectl用户。验证失败则返回401错误。
-
鉴权阶段:API Server通过RBAC判断请求者是否有该操作的权限——如dev-user是否能在dev命名空间创建Pod。验证失败则返回403错误。
-
准入机制阶段:API Server通过准入控制器校验请求合规性——如Pod是否为特权容器、是否符合资源限制。校验失败则返回403错误。
-
执行操作并返回结果:所有阶段通过后,API Server执行请求(如将Pod信息写入etcd),并向客户端返回成功响应。
这三道防线共同构成了K8s的安全基石,确保集群在“开放访问”和“安全可控”之间实现平衡。

浙公网安备 33010602011771号