Kubernetes 认证和授权
文章摘要
本文系统讲解了 Kubernetes 集群的三大核心运维主题:认证与授权 、集群 Dashboard 以及动态卷供应。
- 认证与授权:深入解析 RBAC 模型,涵盖 ClusterRole 的创建、绑定、回收与删除,并通过实践演示如何为用户和 Service Account 授权;同时对比用户账号与服务账号,详解服务账号令牌卷机制及临时/永久令牌的创建方法。
- 集群 Dashboard:分别介绍 Kuboard 与 Kubernetes Dashboard 的安装、配置、访问及登录方式,帮助读者通过图形化界面高效管理集群。
- 动态卷供应:讲解 StorageClass 的工作原理,并手把手演示如何部署 Local Path Provisioner 与 NFS Provisioner,实现 PVC 的自动创建与绑定,最后通过实际应用验证动态卷的读写能力。
通过本文的实战演练,读者可以掌握 Kubernetes 权限控制、可视化运维与存储管理的核心技能。
学习参考:API 访问控制
环境准备
bash
root@master30:~# kubectl create ns auth
root@master30:~# kubectl config set-context --current --namespace auth
授权管理
clusterrole 管理
常见 clusterrole
kubernetes系统中已经预定义了很多clusterrole,常见的clusterrole如下:
- view,对系统中几乎所有的对象都有get、list和watch权限。
- edit,对系统中几乎所有的对象都有get、list和watch权限。其中部分对象额外具有 create、delete、deletecollection、patch、update 权限。
- admin ,对系统中大部分的对象具有所有权限。
- cluster-admin ,对系统中所有的对象具有所有权限。
创建 clusterrole
bash
root@master30:~# kubectl create clusterrole -h
Usage:
kubectl create clusterrole NAME --verb=verb --resource=resource.group
[--resource-name=resourcename] [--dry-run=server|client|none] [options]
示例1:创建一个可以get、list、watch所有项目中pods的clusterrole
bash
root@master30:~# kubectl create clusterrole pod-role --verb=get,list,watch --resource=pods --dry-run=client -o yaml
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
creationTimestamp: null
name: pod-role
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
bash
root@master30:~# kubectl create clusterrole pod-role --verb=get,list,watch --resource=pods
root@master30:~# kubectl get clusterrole pod-role
NAME CREATED AT
pod-role 2021-08-25T14:31:21Z
root@master30:~# kubectl describe clusterrole pod-role
Name: pod-role
Labels: <none>
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
pods [] [] [get list watch]
示例2:创建一个可以get、list、watch所有项目中pods/readablepod和pods/anotherpod的clusterrole
bash
root@master30:~# kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod
示例3:创建一个可以get、list、watch所有项目中pods和pods/status的clusterrole
bash
root@master30:~# kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
修改 clusterrole
bash
root@master30:~# kubectl edit clusterrole pod-role
......
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
# 添加create权限
- create
clusterrole 绑定
将clusterrole绑定给用户。
bash
root@master30:~# kubectl create clusterrolebinding han-pod-role --clusterrole=pod-role --user=han --dry-run=client -o yaml
yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
creationTimestamp: null
name: han-pod-role
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: pod-role
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: User
name: han
bash
root@master30:~# kubectl create clusterrolebinding han-pod-role --clusterrole=pod-role --user=han
root@master30:~# kubectl get clusterrolebinding han-pod-role
NAME ROLE AGE
han-pod-role ClusterRole/pod-role 35s
root@master30:~# kubectl describe clusterrolebinding han-pod-role
Name: han-pod-role
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: pod-role
Subjects:
Kind Name Namespace
---- ---- ---------
User han
# client节点使用han用户测试权限
root@client:~# kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
calico-kube-controllers-6dfcd885bf-lq4n5 1/1 Running 6 53d
calico-node-44cf4 1/1 Running 6 53d
......
root@client:~# kubectl get pod -n default
NAME READY STATUS RESTARTS AGE
web 1/1 Running 1 15h
clusterrole 回收
bash
root@master30:~# kubectl delete clusterrolebinding han-pod-role
删除 clusterrole
bash
root@master30:~# kubectl delete clusterrole pod-reader
实践 2:使用现有集群角色
- 赋予 han 用户集群角色 admin,指定在auth命名空间
- han 用户在 auth 命名空间:创建、查看和删除 deployment
- han 用户在 default 命名空间:创建、查看和删除 deployment
- 回收 han 用户权限
结果:绑定集群角色的时候,限定特定命名空间是没有意义的,仍然针对集群级别所有命名空间生效。
实践 3:自定义集群角色
- 创建 clusterrole 名称 cluster-pod-deploy-reader,能够查看pod和deployment
- 赋予给 han 用户
- han 用户在 auth 命名空间中:查看pod、deployment
- han 用户在 kube-system 命名空间中:查看pod、deployment
- 删除集群角色绑定和集群角色
服务账户
服务账户概述
Service Account,即服务账户,pod 使用 Service Account 身份运行容器。赋予Service Account相应角色,则使用该Service Account身份运行的pod中进程将具有对应Service Account的权限,进而有权限管理Kubernetes集群。
在每个namespace中都有一个名称为default的Service Account,创建的pod都会以 Service Account-default身份运行。
bash
root@master30:~# kubectl get sa
NAME SECRETS AGE
default 1 53d
# 创建一个pod,验证Service Account信息
root@master30:~# kubectl run web --image=hub.han.cloud/library/httpd --image-pull-policy=IfNotPresent
root@master30:~# kubectl get pod web -o yaml|grep serviceAccount
serviceAccount: default
serviceAccountName: default
- serviceAccountToken:
服务账号与用户账号比较
Kubernetes 区分用户账号和服务账号的概念,主要基于以下原因:
| 类别 | 用户账号 | 服务账号 |
|---|---|---|
| 使用主体 | 针对人 | 针对Pod, Pod 中的应用程序通过服务账号访问集群。 |
| 范围 | 集群范围,集群中的用名称必须唯一。 | 命名空间范围,两个不同的名字空间可以包含具有相同名称的服务账号。 |
| 轻量程度 | 重量,通过认证模块配置。 | 轻量,用户为了具体的任务按需创建服务账号。将服务账户与用户分离开来, 使工作负载更易于遵从权限最小化原则。 |
服务账号令牌卷机制
默认情况下,Kubernetes 添加一个投射卷到 Pod, 此卷包括了访问 Kubernetes API 的令牌。
bash
root@master30:~# kubectl get pod web -o yaml
yaml
...
volumeMounts:
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-sjffr
readOnly: true
...
volumes:
- name: kube-api-access-sjffr
projected:
sources:
- serviceAccountToken:
path: token # 必须与应用所预期的路径匹配
- configMap:
items:
- key: ca.crt
path: ca.crt
name: kube-root-ca.crt
- downwardAPI:
items:
- fieldRef:
apiVersion: v1
fieldPath: metadata.namespace
path: namespace
该清单片段定义了由三个数据源组成的投射卷。在当前场景中,每个数据源也代表该卷内的一条独立路径。这三个数据源是:
-
serviceAccountToken数据源 ,包含 kubelet 从 kube-apiserver 获取的令牌。 kubelet 使用 TokenRequest API 获取有时间限制的令牌**。为 TokenRequest 服务的这个令牌会在 Pod 被删除或定义的生命周期(默认为 1 小时)结束之后过期。**该令牌绑定到特定的 Pod, 并将其 audience(受众)设置为与kube-apiserver的 audience 相匹配。 这种机制取代了之前基于 Secret 添加卷的机制,之前 Secret 代表了针对 Pod 的 ServiceAccount 但不会过期。注意:没有特定的机制可以使通过 TokenRequest 签发的令牌无效。 如果你不再信任为某个 Pod 绑定的服务账号令牌, 你可以删除该 Pod。删除 Pod 将使其绑定的服务账号令牌过期。
-
**
configMap数据源。**ConfigMap 包含一组证书颁发机构数据。 Pod 可以使用这些证书来确保自己连接到集群的 kube-apiserver(而不是连接到中间件或意外配置错误的对等点上)。 -
downwardAPI数据源,用于查找包含 Pod 的名字空间的名称, 并使该名称信息可用于在 Pod 内运行的应用程序代码。
Pod 内挂载这个特定卷的所有容器都可以访问上述信息。
bash
root@master30:~# kubectl exec -it web -- bash
root@web:/usr/local/apache2# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt namespace token
root@web:/usr/local/apache2# cat \
/var/run/secrets/kubernetes.io/serviceaccount/token
eyJhbGciOiJSUzI1NiIsI......
root@web:/usr/local/apache2# cat /var/run/secrets/kubernetes.io/serviceaccount/name pace
auth
服务账户管理
SA 创建
bash
root@master30:~# kubectl create sa sa1
root@master30:~# kubectl get sa sa1
NAME SECRETS AGE
sa1 1 9s
SA 授权
bash
root@master30:~# kubectl create clusterrolebinding auth-sa1-cluster-admin --clusterrole=cluster-admin --serviceaccount=auth:sa1
clusterrolebinding.rbac.authorization.k8s.io/auth-sa1-cluster-admin created
# 选项--serviceaccoun指明Service Account时,格式为namespace:Service Account Name
SA 使用
bash
# 删除 pod 重新创建
root@master30:~# kubectl delete pod web
root@master30:~# kubectl run web --image=hub.han.cloud/library/httpd --image-pull-policy=IfNotPresent --dry-run=client -o yaml > web.yaml
root@master30:~# vim web.yaml
yaml
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: null
labels:
run: web
name: web
spec:
containers:
- image: httpd
imagePullPolicy: IfNotPresent
name: web
resources: {}
dnsPolicy: ClusterFirst
restartPolicy: Always
# 添加以下任一记录
serviceAccount: sa1
serviceAccountName: sa1
status: {}
bash
root@master30:~# kubectl apply -f web.yaml
root@master30:~# kubectl get pod web -o yaml
......
spec:
......
serviceAccount: sa1
serviceAccountName: sa1
重要:虽然 pod/web 中 httpd 进程具有 clusterrole/cluster-admin 角色,但是它只是用来提供httpd服务,不会做其他操作。
回收权限
bash
root@master30:~# kubectl delete clusterrolebindings auth-sa1-cluster-admin
clusterrolebinding.rbac.authorization.k8s.io "auth-sa1-cluster-admin" deleted
使用案例
集群 dashboard 中 pod 使用服务账户管理集群。
手动管理服务账号 token
- v1.22 之前的 Kubernetes 版本会自动创建凭据访问 Kubernetes API。 这种机制是:先创建令牌 Secret,然后将其挂载到正运行的 Pod 中。
- 在包括 Kubernetes v1.28 在内最近的几个版本中,使用 TokenRequest API 直接获得 API 凭据, 并使用投射卷挂载到 Pod 中。这种方法获得的令牌具有绑定的生命周期, 当挂载的 Pod 被删除时这些令牌将自动失效。
创建临时令牌
bash
root@master30:~# kubectl create sa sa1
root@master30:~# kubectl create token sa1
eyJhbGciOiJSUzI1NiIsImt......
# 注意:服务账号属性中是看不到关联的token的。
root@master30:~# kubectl get sa sa1 -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
creationTimestamp: "2023-11-01T04:05:03Z"
name: sa1
namespace: han
resourceVersion: "11548"
uid: 4944f75b-43ae-43fc-abfd-f2d20b7375f1
创建永久令牌
创建一个带有特殊注解 kubernetes.io/service-account.name 的 Secret 对象。一旦你手动创建一个 Secret 并将其关联到 ServiceAccount, Kubernetes 控制平面就会自动将令牌填充到该 Secret 中。
bash
root@master30:~# vim sa1-token.yaml
yaml
apiVersion: v1
kind: Secret
metadata:
name: sa1-secret
annotations:
kubernetes.io/service-account.name: sa1
type: kubernetes.io/service-account-token
bash
root@master30:~# kubectl apply -f sa1-token.yaml
当你删除一个与某 Secret 相关联的 ServiceAccount 时,Kubernetes 的控制面会自动清理该 Secret 中长期有效的令牌。
bash
root@master30:~# kubectl delete sa sa1
root@master30:~# kubectl get secrets
No resources found in han namespace.
说明: 尽管存在手动创建长久 ServiceAccount 令牌的机制,但还是推荐使用 TokenRequest 获得短期的 API 访问令牌。
思考
**问题:**Kubernetes 为什么不提供用户管理和身份认证功能?
Kubernetes 采用 RBAC 模型 管理用户(User)和用户组(Group)权限。但是Kubernetes中找不到真正的用户以及用户组信息,甚至连对应的User Resource以及Group Resource都没有,Kubernetes无法列出可信任的User列表以及Group列表。
换句话说 Kubernetes 并没有提供用户管理和身份认证功能,除了Service Account外,所有的用户信息都依赖外部的用户管理系统来存储,因此通过api-serever根本无法列出User和Group。
**解答:**这其实挺符合UNIX设计哲学的,即 Do One Thing and Do It Well。
Kubernetes只专注于做应用编排,其他的功能则提供接口集成,除了认证和鉴权,我们发现网络、存储也都如此。
这样做的好处也显而易见,用户账户信息与Kubernetes集群松耦合,便于集成企业已有的身份认证系统,如AD、LDAP、Keycloak等。
环境清理
bash
root@master30:~# kubectl delete ns auth
Kubernetes 集群 Dashboard
kuboard
kuboard 介绍
Kuboard 是一款专为 Kubernetes 设计的免费管理界面,兼容 Kubernetes 版本 1.13 及以上。Kuboard 每周发布一个 beta 版本,最长每月发布一个正式版本,经过两年的不断迭代和优化,已经具备多集群管理、权限管理、监控套件、日志套件等丰富的功能,并且有 1000+ 的企业将 Kuboard 应用于其生产环境。Kuboard 自 2019年8月发布第一个版本以来,得到了众多用户的认可,目前已经获得了 10000+ GitHub Star。
Kuboard 的定位和 Dashboard 是相似的,主要的区别 在于:
- Kuboard 关注微服务参考架构的视角对界面进行组织,参考 Kuboard 简介。
- Kuboard 中,不需要手工编写 YAML 文件,进一步降低 K8S 使用难度,提高便捷性。
- Kuboard 可以导出整个微服务架构的部署信息,并在新的名称空间/集群导入配置信息。
- Kuboard 的一个发展方向是,提供内建的 监控套件(目前的全局监控套件成熟度比较高)。
kuboard 特点
相较于 Kubernetes Dashboard 等其他 Kubernetes 管理界面,Kuboard 的主要特点有:
-
多种认证方式:Kuboard 可以使用内建用户库、gitlab / github 单点登录或者 LDAP 用户库进行认证,避免管理员将 ServiceAccount 的 Token 分发给普通用户而造成的麻烦。使用内建用户库时,管理员可以配置用户的密码策略、密码过期时间等安全设置。
-
多集群管理:管理员可以将多个 Kubernetes 集群导入到 Kuboard 中,并且通过权限控制,将不同集群/名称空间的权限分配给指定的用户或用户组。
-
微服务分层展示:在 Kuboard 的名称空间概要页中,以经典的微服务分层方式将工作负载划分到不同的分层,更加直观地展示微服务架构的结构,并且可以为每一个名称空间自定义名称空间布局。
-
工作负载的直观展示:Kuboard 中将 Deployment 的历史版本、所属的 Pod 列表、Pod 的关联事件、容器信息合理地组织在同一个页面中,可以帮助用户最快速的诊断问题和执行各种相关操作。
-
工作负载编辑:Kuboard 提供了图形化的工作负载编辑界面,用户无需陷入繁琐的 YAML 文件细节中,即可轻松完成对容器的编排任务。支持的 Kubernetes 对象类型包括:Node、Namespace、Deployment、StatefulSet、DaemonSet、Secret、ConfigMap、Service、Ingress、StorageClass、PersistentVolumeClaim、LimitRange、ResourceQuota、ServiceAccount、Role、RoleBinding、ClusterRole、ClusterRoleBinding、CustomResourceDefinition、CustomResource 等各类常用 Kubernetes 对象,
-
存储类型支持:在 Kuboard 中,可以方便地对接 NFS、CephFS 等常用存储类型,并且支持对 CephFS 类型的存储卷声明执行扩容和快照操作。
-
丰富的互操作性 :可以提供许多通常只在
kubectl命令行界面中才提供的互操作手段,例如:- Top Nodes / Top Pods
-
容器的日志、终端
- 容器的文件浏览器(支持从容器中下载文件、上传文件到容器)
- KuboardProxy(在浏览器中就可以提供
kubectl proxy的功能)
-
套件扩展:Kuboard 提供了必要的套件库,使得用户可以根据自己的需要扩展集群的管理能力。当前提供的套件有:
- 资源层监控套件,基于 Prometheus / Grafana 提供 K8S 集群的监控能力,可以监控集群、节点、工作负载、容器组等各个级别对象的 CPU、内存、网络、磁盘等资源的使用情况;
-
日志聚合套件,基于 Grafana / Loki / Promtail 实现日志聚合;
- 存储卷浏览器,查看和操作存储卷中的内容;
-
告警配置:可以通过界面直接配置资源层监控套件发送告警消息:
- 支持邮件、微信发送告警消息;
-
支持告警路由配置;
- 支持告警规则配置等;
-
操作审计:Kuboard 支持操作审计的功能:
- 审计用户通过 Kuboard 界面和 Kuboard API 执行的操作;
- 自定义审计规则;
在线演示
在线演示环境中,您具备 只读 权限,只能体验 Kuboard 的一部分功能。
用 户:demo
密 码:demo123
kuboard 版本
Kuboard v2.x 版本说明
Kuboard v2.x 支持 Kubernetes 单集群管理。
版本支持:

Kuboard v3.x 版本说明
Kuboard v3.x 支持 Kubernetes 多集群管理。如果您从 Kuboard v1.0.x 或者 Kuboard v2.0.x 升级到 Kuboard,请注意:
- 您可以同时使用 Kuboard v3.x 和 Kuboard v2.0.x;
- Kuboard v3.x 支持 amd64 (x86) 架构和 arm68 (armv8) 架构的 CPU;
版本支持:

kuboard 安装和配置
本次实验环境kubernetes版本是1.32.0,使用kuboard v3.x版本。
安装方式
基于如下原因,建议您以 独立容器 方式 Kuboard:
- 结构更清晰(Kuboard 作为多个集群的管理界面应该独立于任何集群之外);
- 登录 Kuboard 时使用不同的认证方式;
- 问题排查更简单;
以 独立容器 方式运行
安装计划
在正式安装 kuboard v3 之前,需做好一个简单的部署计划的设计,在本例中,各组件之间的连接方式,如下图所示:
- 假设用户通过 http://外网IP:80 访问 Kuboard v3;
- 安装在 Kubernetes 中的 Kuboard Agent 通过
内网IP访问 Kuboard 的 Web 服务端口 80 和 Kuboard Agent Server 端口 10081。

安装过程
bash
# 创建目录
root@master30:~# mkdir /kuboard-data
# 创建容器
root@master30:~# nerdctl run -d --name=kuboard \
-p 80:80/tcp \
-p 10081:10081/tcp \
-e KUBOARD_ENDPOINT="http://10.1.8.30:80" \
-e KUBOARD_AGENT_SERVER_TCP_PORT="10081" \
-v /kuboard-data:/data \
eipwork/kuboard:v3
# 10.1.8.30是物理机的ip
卸载
bash
root@master30:~# nerdctl container rm kuboard --force
root@master30:~# rm -fr /kuboard-data
以 static pod 方式运行(自学)
安装
bash
# 在master节点上执行
root@master30:~# curl -fsSL https://addons.kuboard.cn/kuboard/kuboard-static-pod.sh -o kuboard.sh
root@master30:~# sh kuboard.sh
current ip address is 10.1.8.30
create file /root/kuboard-sa.yaml
kubectl apply -f /root/kuboard-sa.yaml
namespace/kuboard created
serviceaccount/kuboard-admin created
clusterrolebinding.rbac.authorization.k8s.io/kuboard-admin-crb created
serviceaccount/kuboard-viewer created
clusterrolebinding.rbac.authorization.k8s.io/kuboard-viewer-crb created
create file /etc/kubernetes/manifests/kuboard.yaml
restart kubelet
检查状态 待 kuboard-v3-master30.han.cloud 的容器组变为 Running 状态后,则安装成功,可以通过 http://10.1.8.30 访问 kuboard 界面
No resources found in kuboard namespace.
# 验证
root@master30:~# kubectl get all -n kuboard
NAME READY STATUS RESTARTS AGE
pod/kuboard-v3-han30.redhat.fun 1/1 Running 0 45s
卸载
bash
# 删除静态文件
root@master30:~# rm -f /etc/kubernetes/manifests/kuboard.yaml
# 删除数据目录
root@master30:~# rm -fr /usr/share/kuboard
# 删除命名空间
root@master30:~# kubectl delete ns kuboard
访问过程
在浏览器输入 http://your-host-ip:80 ,访问 Kuboard v3.x 的界面。
登录信息:
- 用户名:
admin - 密 码:
Kuboard123
添加集群
这里选择:.kubeconfig
导入凭据文件(复制~/.kube/config内容到红色方框中)

导入后效果:

选择访问集群时所使用的身份:使用 ServiceAccount kuboard-admin
参考资料
安装手册:https://kuboard.cn/install/v3/install.html
Kubernetes Dashboard
介绍
Kubernetes Dashboard 是 Kubernetes 的官方 Web UI。
Kubernetes Dashboard 具备的功能:
- 向 Kubernetes 集群部署容器化应用
- 诊断容器化应用的问题
- 管理集群的资源
- 查看集群上所运行的应用程序
- 创建、修改Kubernetes 上的资源(例如 Deployment、Job、DaemonSet等)
- 展示集群上发生的错误
例如:您可以伸缩一个 Deployment、执行滚动更新、重启一个 Pod 或部署一个新的应用程序。
部署
bash
# 下载资源yaml文件
root@master30:~# wget https://raw.githubusercontent.com/kubernetes/dashboard/v2.6.1/aio/deploy/recommended.yaml
# 查看images
root@master30:~# grep image: recommended.yaml
image: kubernetesui/dashboard:v2.6.1
image: kubernetesui/metrics-scraper:v1.0.8
# 执行部署
root@master30:~# kubectl apply -f recommended.yaml
验证
bash
root@master30:~# kubens
default
kube-node-lease
kube-public
kube-system
kubernetes-dashboard
root@master30:~# kubens kubernetes-dashboard
root@master30:~# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
dashboard-metrics-scraper 1/1 1 1 2m25s
kubernetes-dashboard 1/1 1 1 2m25s
root@master30:~# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard-metrics-scraper ClusterIP 10.98.116.192 <none> 8000/TCP 3m54s
kubernetes-dashboard-web ClusterIP 10.107.63.216 <none> 8000/TCP 3m54s
root@master30:~# kubectl get serviceaccounts
NAME SECRETS AGE
default 1 23h
kubernetes-dashboard 1 23h
root@master30:~# kubectl describe serviceaccounts kubernetes-dashboard
Name: kubernetes-dashboard
Namespace: kubernetes-dashboard
Labels: app.kubernetes.io/part-of=kubernetes-dashboard
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: <none>
Tokens: <none>
Events: <none>
# 创建了clusterrolebindings/kubernetes-dashboard
root@master30:~# kubectl get clusterrolebindings.rbac.authorization.k8s.io |grep dashboard
kubernetes-dashboard ClusterRole/kubernetes-dashboard 23h
# sa/kubernetes-dashboard具有ClusterRole/kubernetes-dashboard
root@master30:~# kubectl describe clusterrolebindings.rbac.authorization.k8s.io kubernetes-dashboard
Name: kubernetes-dashboard
Labels: <none>
Annotations: <none>
Role:
Kind: ClusterRole
Name: kubernetes-dashboard
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount kubernetes-dashboard kubernetes-dashboard
# ClusterRole/kubernetes-dashboard权限如下:
root@master30:~# kubectl describe clusterroles kubernetes-dashboard
Name: kubernetes-dashboard
Labels: k8s-app=kubernetes-dashboard
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
nodes.metrics.k8s.io [] [] [get list watch]
pods.metrics.k8s.io [] [] [get list watch]
# deployments.apps/kubernetes-dashboard以sa/kubernetes-dashboard身份运行,所以deployments.apps/kubernetes-dashboard部署的pod只可以查看资源nodes.metrics.k8s.io和pods.metrics.k8s.io
root@master30:~# kubectl get deployments.apps kubernetes-dashboard -o yaml|grep ' serviceAccount'
serviceAccount: kubernetes-dashboard
serviceAccountName: kubernetes-dashboard
修改 service/kubernetes-dashboard 类型为 NodePort,从外部访问。
bash
root@master30:~# kubectl edit svc kubernetes-dashboard
spec:
......
type: NodePort
root@master30:~# kubectl get svc kubernetes-dashboard
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-dashboard NodePort 10.111.32.116 <none> 443:30518/TCP 4m45s
使用ingress配置访问,参考:
yamlapiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-dashboard namespace: kubernetes-dashboard annotations: # 关键:透传 TLS,Ingress 不解密 nginx.ingress.kubernetes.io/ssl-passthrough: "true" nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" spec: ingressClassName: nginx rules: - host: dashboard.han.cloud http: paths: - path: / pathType: Prefix backend: service: name: kubernetes-dashboard port: number: 443客户端配置dashboard.han.cloud域名到ingress服务地址,实验环境是10.1.8.40
访问 Dashboard
这个图片代表登录成功了包括下面的步骤

新版本
Kubernetes Dashboard 新版本只支持使用 Bearer Token登录。
Kubernetes Dashboard 默认部署时,只配置了最低权限的 RBAC。因此,我们要创建一个名为 admin-user 的 ServiceAccount,再创建一个 ClusterRolebinding,将其绑定到 Kubernetes 集群中默认初始化的 cluster-admin 这个 ClusterRole。
bash
root@master30:~# cat > kubernetes-dashboard-admin.yaml <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: kubernetes-dashboard-admin
namespace: kubernetes-dashboard
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubernetes-dashboard-admin
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: cluster-admin
subjects:
- kind: ServiceAccount
name: kubernetes-dashboard-admin
namespace: kubernetes-dashboard
EOF
yaml
root@master30:~# kubectl apply -f kubernetes-dashboard-admin.yaml
# 创建 Bearer Token
root@master30:~# kubectl -n kubernetes-dashboard create token kubernetes-dashboard-admin
eyJhbGciOiJSUzI1NiIsImtpZCI6ImhLWEhaTWQtNGZTek5zUFUyUmNobzN1V2ZlOG1iWkwzSU5kZDZLbU1WUTAifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjc2NDU4ODI3LCJpYXQiOjE2NzY0NTUyMjcsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJhZG1pbi11c2VyIiwidWlkIjoiYmViNGI3MWMtNmI5MS00ODk4LThiMTMtZWQwZWE3N2JiMmQ5In19LCJuYmYiOjE2NzY0NTUyMjcsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDphZG1pbi11c2VyIn0.kBwU3g3Euqj5jMKD9AYu-OQW2yc9pc2hb7uaDi2PO3AdkOBVMkaIiXnWEUC98hpFyXUWGSiaZWPsylJHXMpWz_X-J03nd7zZegUhPbzVxPh7vHyaOaILmqATlakAHMHbwcEQDygBQPBNbPNd-hD-rzaVNkmr2oARNGVRaX7fMNRkuvcbji0-s6IO5yotKkWaGXkIAtwmMTgtgcGnOpVsB97UYjPgWz6RMBL9ZCZIVXHxE_FIl_If7ewhObyaijZIbWeZ21job5PPvb1H1VAHOZoDXtFK_lZdIRIK6e6MYYU4mfYqTcOyiEG8c5SxCOaD4xchWGdzFcfZfn52VQJ7hQ
提示:Token默认有效期24小时,过期后自动删除。下一次登录需要生成新的Token。
旧版本(自学)
之前的一些版本,默认已经为sa账户kubernetes-dashboard 创建了token。
获取token过程如下:
bash
# sa/kubernetes-dashboard使用的token是kubernetes-dashboard-token-hm26j
root@master30:~# kubectl describe sa kubernetes-dashboard
Name: kubernetes-dashboard
Namespace: kubernetes-dashboard
Labels: k8s-app=kubernetes-dashboard
Annotations: <none>
Image pull secrets: <none>
Mountable secrets: kubernetes-dashboard-token-hm26j
Tokens: kubernetes-dashboard-token-hm26j
Events: <none>
# 获取token内容
root@master30:~# kubectl describe secrets kubernetes-dashboard-token-hm26j
Name: kubernetes-dashboard-token-hm26j
Namespace: kubernetes-dashboard
Labels: <none>
Annotations: kubernetes.io/service-account.name: kubernetes-dashboard
kubernetes.io/service-account.uid: d4b7d7ec-a76b-46c5-9dc5-629217cd07ba
Type: kubernetes.io/service-account-token
Data
====
token: eyJhbGciOiJSUzI1NiIsImtpZCI6IkNVVmdmMGNUckVGZFFPUVJ0dTkxZGtEc2hoOTlGZ2ktWjI2RmtnRWI5dFEifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZC10b2tlbi1obTI2aiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImQ0YjdkN2VjLWE3NmItNDZjNS05ZGM1LTYyOTIxN2NkMDdiYSIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlcm5ldGVzLWRhc2hib2FyZDprdWJlcm5ldGVzLWRhc2hib2FyZCJ9.nBy6Hpv3a5jM-LWzqi95i2jkdLT8bx-jnTOuM5eroxGjpUq8OFOIFgky-0LUlJ-4dlFLdh3FZ2RV8SwQwQoctaay2p-xGfsB1IWNJkySYlGDy9-iMnH6Lc3J_Kb085rgqvY1nLPbSIhBlkueHUFsG9ii52gnGZf2RqroNBtsl_t2ofWfNDJRE7dm51B-WM_rSnNgTChFygRSoMXpzP7jG64dTrvQyxqHKRwr3zMNeYUlleHECUx0Z525hkdFv5B-gPV6ev_2cRS0BR2TM0V2qr1TKqM6G56E7CDkHiu6S2B1mrLdz5E9IKQCPFvxGVM4O4-bia4MHdmJo_INFD57pA
ca.crt: 1066 bytes
namespace: 20 bytes
登录页面
使用上面的 token 登录
选择Pods
分析 SA 使用案例
Kubernetes Dashboard 默认部署时,只配置了最低权限的 RBAC。接下来分析默认的权限配置。
bash
# Kubernetes Dashboard 以deployment形式运行
root@master30:~# kubectl get deployments.apps
NAME READY UP-TO-DATE AVAILABLE AGE
dashboard-metrics-scraper 1/1 1 1 146m
kubernetes-dashboard 1/1 1 1 146m
# 以 serviceAccount: kubernetes-dashboard 身份运行
root@master30:~# kubectl get deployments.apps kubernetes-dashboard -o yaml|grep serviceAc
serviceAccount: kubernetes-dashboard
serviceAccountName: kubernetes-dashboard
# serviceAccount: kubernetes-dashboard 绑定了Role/kubernetes-dashboard
root@master30:~# kubectl get rolebindings.rbac.authorization.k8s.io
NAME ROLE AGE
kubernetes-dashboard Role/kubernetes-dashboard 147
# Role/kubernetes-dashboard 具备能力如下:
root@master30:~# kubectl describe role kubernetes-dashboard
Name: kubernetes-dashboard
Labels: k8s-app=kubernetes-dashboard
Annotations: <none>
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
secrets [] [kubernetes-dashboard-certs] [get update delete]
secrets [] [kubernetes-dashboard-csrf] [get update delete]
secrets [] [kubernetes-dashboard-key-holder] [get update delete]
configmaps [] [kubernetes-dashboard-settings] [get update]
services/proxy [] [dashboard-metrics-scraper] [get]
services/proxy [] [heapster] [get]
services/proxy [] [http:dashboard-metrics-scraper] [get]
services/proxy [] [http:heapster:] [get]
services/proxy [] [https:heapster:] [get]
services [] [dashboard-metrics-scraper] [proxy]
services [] [heapster] [proxy]
# 创建 serviceAccount: kubernetes-dashboard Token
root@master30:~# kubectl -n kubernetes-dashboard create token kubernetes-dashboard
eyJhbGciOiJSUzI1NiIsImtpZCI6Im9jekNIUUFRZUpKYzA3VXdmRHJvT3ZhaU1fMFNxVkh6NEJZa2lyUnhaaXcifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNzc4MDUwMjI4LCJpYXQiOjE3NzgwNDY2MjgsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwianRpIjoiMTY0YzllOTItMzUwMS00Y2M2LWEwNjctOGZiNDI4OTQ4MDIwIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsInNlcnZpY2VhY2NvdW50Ijp7Im5hbWUiOiJrdWJlcm5ldGVzLWRhc2hib2FyZCIsInVpZCI6IjU3ZDAwNjUxLTZmYWQtNDJmOC04ZmYxLTM3YmRmZGRiY2Y3ZCJ9fSwibmJmIjoxNzc4MDQ2NjI4LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQ6a3ViZXJuZXRlcy1kYXNoYm9hcmQifQ.EehmwsmNT6OVbazZs1pUOr_7kletmrpduE-8p4m2Y49c_hWXKs3ic7u3DMxw4jNJgbPfnvBiq2nrIObknsXBi6BKXL5XLDSlwsZobYUd0Uw0op2YOZZ3O_B4A-sSOiD-qD1eFMw2RYyR6aRuycWLngvYvyN3pfFCsd1GS_Gh4rAetsPtW4QzlFf230HL8Az2fU-M7A-0_kkt_ujpgMXQpyPSxV5qgyffjnDkOo-llDR-j_QsyyMt12k1zSjzN5ApcGQtHn9y8vbIS0xoqHulmU38kOjnKNpeQJ41nwKnf53q2I2MDoatO5r-hnD_TGx6xcckW0qFNrpje38XgRPHTQ
使用 serviceAccount: kubernetes-dashboard Token 登录

无法看到Pods。

Kubernetes 动态卷供应
动态卷介绍
之前创建存储卷(PV)都需要管理员手动操作,这类卷被称为静态卷。那么 Kubernetes 能不能 "智能" 管理卷呢?比如需要用卷时自动创建,不用时自动清理?
答案是可以的 ------ 这就是动态卷供应的能力。它彻底省去了集群管理员提前手动配置存储的工作,用户只要提出存储需求,系统就会自动创建对应的存储卷。
动态卷供应流程
- 管理员先创建 "存储模板"(StorageClass),指定用哪个 "造卷工具"(Provisioner,制备器)来创建卷;
- 用户创建 "存储申请单"(PVC)时,指定要用哪个 "存储模板";
- 系统收到申请后,会让模板绑定的 "造卷工具" 自动创建一个 PV,并且把 PV 和用户的 PVC 绑定,用户直接用 PVC 就行。
StorageClass
StorageClass 就像管理员定义的 "存储套餐"------ 不同套餐对应不同的存储类型、服务质量(比如读写速度)、备份策略等。Kubernetes 不关心套餐叫什么,只负责按套餐规则造卷。
- 命名很重要:用户创建 PVC 时,要通过这个名字选择对应的存储套餐;
- 创建后不能改:StorageClass 一旦创建,名字和参数就没法修改了,要改只能重新建。
核心配置
每个 "存储套餐" 都包含以下核心信息,用来指导系统创建 PV:
-
provisioner:造卷工具(制备器),指定用哪个工具创建 PV,必须设置。
Kubernetes 自带一些 Provisioner,名字以 "kubernetes.io" 开头(比如 AWS EBS、Azure Disk);也可以使用第三方提供的(比如 NFS 没有内置工人,需要自己装)Provisioner,按 Kubernetes 规则运行即可。
存储类型 是否内置 说明 AWSElasticBlockStore ✅ 是 亚马逊云硬盘 AzureFile/AzureDisk ✅ 是 微软云文件 / 硬盘 Ceph RBD ✅ 是 Ceph 块存储 Glusterfs ✅ 是 分布式文件系统 NFS ❌ 否 网络文件系统,需装外部工人 本地存储(Local) ❌ 否 节点本地硬盘,需装外部工人 iSCSI/CephFS ❌ 否 需外部工人 -
reclaimPolicy:PV 回收策略,指定 PV 不用怎么处理 PV。
- Delete(默认):PVC 删除后,PV 也自动删,对应的后端存储(比如云硬盘、NFS 目录)也会清掉;
- Retain:PVC 删除后,PV 保留下来,后端存储的数据也不删,管理员可以手动处理。
-
volumeBindingMode:PVC 创建出来后,指定 PVC 与P V 绑定时机。
- Immediate(立即绑定):创建 PVC 就造卷,不管 Pod 会不会用;适合存储能被所有节点访问的场景(比如云硬盘);
- WaitForFirstConsumer(等使用者):先不造卷,等第一个用这个 PVC 的 Pod 创建后,根据 Pod 要跑的节点造卷;适合本地存储、只能被特定节点访问的存储。
-
mountOptions:指定 PV 挂载时附加的 "额外设置",比如权限、调试模式。
注意:如果存储不支持这些选项,造卷会失败;如果选项填错,PV 挂载会失败。
-
parameters:比如存储类型(SSD/HDD)、副本数等,不同工具参数不一样。
基础示例
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: standard # 套餐名,用户PVC要填这个
provisioner: kubernetes.io/aws-ebs # 用AWS EBS的造卷工具
parameters:
type: gp2 # 存储类型为gp2(AWS的通用型SSD)
reclaimPolicy: Retain # PVC 不用了保留,不删除
allowVolumeExpansion: true # 允许扩容
mountOptions:
- debug # 挂载时开启调试模式
volumeBindingMode: Immediate # 立即创建并绑定PV
部署 Local Path provisioner
Local Path Provisioner 是 K8s 官方推荐的本地存储动态卷插件,适合需要使用节点本地磁盘的场景。
部署本地路径制备器
bash
# 下载官方模板
root@master30:~# wget https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
# 查看资源配置
root@master30:~# cat local-path-storage.yaml
yaml
apiVersion: v1
kind: Namespace
metadata:
name: local-path-storage
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: local-path-provisioner-role
namespace: local-path-storage
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: local-path-provisioner-role
rules:
- apiGroups: [""]
resources: ["nodes", "persistentvolumeclaims", "configmaps", "pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "patch", "update", "delete"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "patch"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: local-path-provisioner-bind
namespace: local-path-storage
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: local-path-provisioner-role
subjects:
- kind: ServiceAccount
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: local-path-provisioner-bind
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: local-path-provisioner-role
subjects:
- kind: ServiceAccount
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: local-path-provisioner
namespace: local-path-storage
spec:
replicas: 1
selector:
matchLabels:
app: local-path-provisioner
template:
metadata:
labels:
app: local-path-provisioner
spec:
serviceAccountName: local-path-provisioner-service-account
containers:
- name: local-path-provisioner
image: rancher/local-path-provisioner:v0.0.35
imagePullPolicy: IfNotPresent
command:
- local-path-provisioner
- --debug
- start
- --config
- /etc/config/config.json
volumeMounts:
- name: config-volume
mountPath: /etc/config/
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
- name: CONFIG_MOUNT_PATH
value: /etc/config/
volumes:
- name: config-volume
configMap:
name: local-path-config
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage
data:
config.json: |-
{
"nodePathMap":[
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
}
setup: |-
#!/bin/sh
set -eu
mkdir -m 0777 -p "$VOL_DIR"
teardown: |-
#!/bin/sh
set -eu
rm -rf "$VOL_DIR"
helperPod.yaml: |-
apiVersion: v1
kind: Pod
metadata:
name: helper-pod
spec:
priorityClassName: system-node-critical
tolerations:
- key: node.kubernetes.io/disk-pressure
operator: Exists
effect: NoSchedule
containers:
- name: helper-pod
image: busybox
imagePullPolicy: IfNotPresent
local-path-storage.yaml 核心由 5 类资源 组成,每部分各司其职:
| 资源类型 | 名称 | 核心作用 |
|---|---|---|
| Namespace | local-path-storage | 隔离 Local Path Provisioner 相关资源 |
| ConfigMap | local-path-config | 定义本地存储路径 规则(如默认 /opt/local-path-provisioner) |
| ServiceAccount + RBAC | local-path-provisioner | 赋予 Provisioner 操作 PV/PVC 的权限 |
| Deployment | local-path-provisioner | 运行 Local Path Provisioner 核心程序(动态创建本地 PV) |
| StorageClass | local-path | 提供给 PVC 调用的「本地存储模板」 |
bash
# 资源默认部署在命名空间:local-path-storage
root@master30:~# kubectl apply -f local-path-storage.yaml
# 确认资源状态
root@master30:~# kubectl get sc local-path
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path rancher.io/local-path Delete WaitForFirstConsumer false 31s
root@master30:~# kubectl get all -n local-path-storage
NAME READY STATUS RESTARTS AGE
pod/local-path-provisioner-74b5c4bf98-955sk 1/1 Running 0 63s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/local-path-provisioner 1/1 1 1 63s
NAME DESIRED CURRENT READY AGE
replicaset.apps/local-path-provisioner-74b5c4bf98 1 1 1 63s
验证部署
bash
# 设置默认命名空间
root@master30:~# kubectl config set-context --current --namespace default
1. 创建 pvc
bash
root@master30:~# cat > local-path-pvc.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
namespace: default
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 5Gi
storageClassName: local-path # 指定 Local Path 的 StorageClass
EOF
root@master30:~# kubectl apply -f local-path-pvc.yaml
root@master30:~# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
local-path-pvc Pending local-path <unset> 6s
# 等待pod关联才会创建本地目录
2. 创建 deployment
bash
root@master30:~# cat > local-path-deployment.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webapp
name: webapp
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: webapp-data
mountPath: /usr/share/nginx/html
volumes:
- name: webapp-data
persistentVolumeClaim:
claimName: local-path-pvc
EOF
root@master30:~# kubectl apply -f local-path-deployment.yaml
# 查看 pod 状态
root@master30:~# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
webapp-6d46b54487-6nqmh 1/1 Running 0 114s 10.224.19.21 worker31.han.cloud <none> <none>
webapp-6d46b54487-kzkd5 1/1 Running 0 114s 10.224.19.22 worker31.han.cloud <none> <none>
# 所有节点都调度到 worker31,因为只用worker31可以提供这个卷。
# 查看 pvc 状态
root@master30:~# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
local-path-pvc Bound pvc-e992833c-af6a-4d4d-aff5-fc544394db6d 5Gi RWO local-path <unset> 4m41s
# 写入测试文件
root@master30:~# kubectl exec webapp-6d46b54487-6nqmh -- bash -c 'echo Hello World From Nginx > /usr/share/nginx/html/index.html'
# 验证本地路径
root@worker31:~# ls /opt/local-path-provisioner/pvc-e992833c-af6a-4d4d-aff5-fc544394db6d_default_local-path-pvc/
index.html
root@worker31:~# cat /opt/local-path-provisioner/pvc-e992833c-af6a-4d4d-aff5-fc544394db6d_default_local-path-pvc/index.html
Hello World From Nginx
3. 清理资源
bash
# 删除 deployment
root@master30:~# kubectl delete deployment webapp
# 删除 pvc
root@master30:~# kubectl delete pvc local-path-pvc
# 本地目录也被删除
root@worker31:~# ls /opt/local-path-provisioner/pvc-e992833c-af6a-4d4d-aff5-fc544394db6d_default_local-path-pvc/
ls: cannot access '/opt/local-path-provisioner/pvc-e992833c-af6a-4d4d-aff5-fc544394db6d_default_local-path-pvc/': No such file or directory
部署 NFS provisioner
部署 NFS 服务
bash
# 安装 NFS server
root@master30:~# apt install -y nfs-kernel-server
# 安创建NFS目录 修改创建文件夹的权限
root@master30:~# mkdir -m 777 /shares/
# 配置共享,允许所有客户端访问
root@master30:~# cat << EOF > /etc/exports
/shares *(rw,sync,no_root_squash,no_all_squash,insecure)
EOF
# 重启 nfs server
root@master30:~# systemctl restart nfs-server.service
# 客户端安装
root@worker31:~# apt install -y nfs-common
root@worker32:~# apt install -y nfs-common
部署 NFS provisioner
NFS没有内置制备器,这里我们自定义外部分配器。
bash
# 资源部署到命名空间:kube-storage
root@master30:~# kubectl create ns kube-storage
root@master30:~# kubectl config set-context --current --namespace kube-storage
1. 创建 RBAC 权限
创建 nfs-rbac.yaml 文件,赋予 Provisioner 操作 PV/PVC 的权限。
yaml
root@master30:~# cat > nfs-rbac.yaml <<'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kube-storage
---
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"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "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: kube-storage
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
EOF
root@master30:~# kubectl apply -f nfs-rbac.yaml
2. 部署 NFS Provisioner 应用
yaml
root@master30:~# cat > nfs-provisioner.yaml <<'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
namespace: kube-storage
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
volumes:
- name: nfs-client-root
nfs:
server: 10.1.8.30 # 同上 NFS 服务器 IP
path: /shares # 核心:同步改为 /shares
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: fuseim.pri/ifs
- name: NFS_SERVER
value: 10.1.8.30 # 替换为你的 NFS 服务器 IP
- name: NFS_PATH
value: /shares # 核心:共享路径改为 /shares
EOF
# 部署 NFS Provisioner 应用
root@master30:~# kubectl apply -f nfs-provisioner.yaml
# 查看 NFS Provisioner 应用
root@master30:~# kubectl get deployments.apps -n kube-storage
NAME READY UP-TO-DATE AVAILABLE AGE
nfs-client-provisioner 1/1 1 1 1m
3. 创建 NFS StorageClass
yaml
root@master30:~# cat > nfs-storageclass.yaml <<'EOF'
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-storage
namespace: kube-storage
provisioner: fuseim.pri/ifs # 必须和 Provisioner 名称一致
parameters:
onDelete: retain # 删除 PVC 保留 NFS 数据
archiveOnDelete: "false"
# 按需设置回收策略
#reclaimPolicy: Retain
reclaimPolicy: Delete
allowVolumeExpansion: true
volumeBindingMode: Immediate
EOF
# 创建 StorageClass
root@master30:~# kubectl apply -f nfs-storageclass.yaml
# 查看 StorageClass
root@master30:~# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storage fuseim.pri/ifs Delete Immediate true 2s
验证部署
在default命名空间测试。
bash
root@master30:~# kubens default
1. 创建 pvc
bash
root@master30:~# cat > nfs-pvc.yaml <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: webclaim
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: "nfs-storage"
EOF
# 创建 pvc
root@master30:~# kubectl apply -f nfs-pvc.yaml
# 查看 pvc 状态
root@master30:~# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
webclaim Bound pvc-6a020ff4-6b7f-4ef4-90fc-3dc94d3e8ddc 5Gi RWO default <unset> 3s
# 查看 pv 状态
root@master30:~# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pvc-6a020ff4-6b7f-4ef4-90fc-3dc94d3e8ddc 5Gi RWO Delete Bound default/webclaim default <unset> 65s
# 查看 nfs 共享目录
root@master30:~# ls /shares/
default-webclaim-pvc-6a020ff4-6b7f-4ef4-90fc-3dc94d3e8ddc
# 准备 web 主页
root@master30:~/nfs-storage# echo Hello World From Nginx > /shares/default-webclaim-pvc-6a020ff4-6b7f-4ef4-90fc-3dc94d3e8ddc/index.html
2. 创建 deployment
bash
root@master30:~# cat > nfs-deploy.yaml << EOF
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: webapp
name: webapp
spec:
replicas: 2
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: webapp-data
mountPath: /usr/share/nginx/html
volumes:
- name: webapp-data
persistentVolumeClaim:
claimName: webclaim
EOF
root@master30:~# kubectl apply -f nfs-deploy.yaml
root@master30:~# kubectl expose deployment webapp --target-port 80 --port 80 --protocol TCP
root@master30:~# kubectl get svc webapp
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
webapp ClusterIP 10.109.140.205 <none> 80/TCP 5m11s
# 访问测试
root@master30:~# curl 10.109.140.205
Hello World From Nginx
root@master30:~# curl 10.109.140.205
Hello World From Nginx
3. 清理资源
bash
# 删除 deployment
root@master30:~# kubectl delete deployments.apps webapp
# 删除 pvc,pv也将一起删除
root@master30:~# kubectl delete pvc webclaim
persistentvolumeclaim "webclaim" deleted
root@master30:~# kubectl get pv
No resources found
# 查看后端存储中数据,仍然保留
root@master30:~# ls /shares/default-webclaim-pvc-6a020ff4-6b7f-4ef4-90fc-3dc94d3e8ddc/
index.html
保留 NFS provisioner ,下一章 statefulset 使用。