保姆级 K8s 运维:RBAC 认证 + Dashboard+Local/NFS 动态卷实战

Kubernetes 认证和授权

学习参考:API 访问控制

环境准备

bash 复制代码
root@master30 ~ 08:45:00# kubectl create ns auth
root@master30 ~ 08:46:30# 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 ~ 08:47:30# kubectl create clusterrole -h
Usage:
  kubectl create clusterrole NAME --verb=verb --resource=resource.group
[--resource-name=resourcename] [--dry-run=server|client|none] [options]

示例 1:创建一个可对所有命名空间中的 Pod 执行 get、list、watch 操作的 ClusterRole。

bash 复制代码
root@master30 ~ 08:49:00# 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 ~ 08:50:30# kubectl create clusterrole pod-role --verb=get,list,watch --resource=pods

root@master30 ~ 08:52:00# kubectl get clusterrole pod-role
NAME       CREATED AT
pod-role   2021-08-25T14:31:21Z

root@master30 ~ 08:52:45# 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 :创建一个可对所有命名空间中名为 readablepodanotherpod 的 Pod 执行 get、list、watch 操作的 ClusterRole。

bash 复制代码
root@master30 ~ 08:53:30# kubectl create clusterrole pod-reader --verb=get --resource=pods --resource-name=readablepod --resource-name=anotherpod

示例 3 :创建一个可对所有命名空间中的 Pod 及 pods/status 子资源执行 get、list、watch 操作的 ClusterRole。

bash 复制代码
root@master30 ~ 08:55:00# kubectl create clusterrole foo --verb=get,list,watch --resource=pods,pods/status
修改 clusterrole
bash 复制代码
root@master30 ~ 08:56:30# kubectl edit clusterrole pod-role
......
rules:
- apiGroups:
  - ""
  resources:
  - pods
  verbs:
  - get
  - list
  - watch
  # 添加create权限
  - create
clusterrole 绑定

将 ClusterRole 绑定给用户。

bash 复制代码
root@master30 ~ 08:58:00# kubectl create clusterrolebinding liu-pod-role --clusterrole=pod-role --user=liu --dry-run=client -o yaml
yaml 复制代码
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  creationTimestamp: null
  name: liu-pod-role
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: pod-role
subjects:
- apiGroup: rbac.authorization.k8s.io
  kind: User
  name: liu
bash 复制代码
root@master30 ~ 08:59:30# kubectl create clusterrolebinding liu-pod-role --clusterrole=pod-role --user=liu

root@master30 ~ 09:01:00# kubectl get clusterrolebinding liu-pod-role
NAME             ROLE                   AGE
liu-pod-role   ClusterRole/pod-role   35s

root@master30 ~ 09:01:45# kubectl describe clusterrolebinding liu-pod-role
Name:         liu-pod-role
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  pod-role
Subjects:
  Kind  Name   Namespace
  ----  ----   ---------
  User  liu

# client节点使用liu用户测试权限
root@client ~ 09:02:30# 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 ~ 09:03:15# kubectl get pod -n default
NAME   READY   STATUS    RESTARTS   AGE
web    1/1     Running   1          15h
clusterrole 回收
bash 复制代码
root@master30 ~ 09:04:00# kubectl delete clusterrolebinding liu-pod-role
删除 clusterrole
bash 复制代码
root@master30 ~ 09:05:30# kubectl delete clusterrole pod-reader

服务账户

学习参考:管理服务账号配置服务账号

服务账户概述

Service Account(服务账户)用于为 Pod 中的容器提供身份标识。为 Service Account 绑定相应的角色后,以该 Service Account 身份运行的 Pod 中的进程将继承其对应权限,从而具备管理 Kubernetes 集群的能力。

每个命名空间中都存在一个名为 default 的 Service Account,未显式指定 Service Account 的 Pod 都会以 default 身份运行。

bash 复制代码
root@master30 ~ 09:07:00# kubectl get sa
NAME      SECRETS   AGE
default   1         53d

# 创建一个pod,验证Service Account信息
root@master30 ~ 09:07:45# kubectl run web --image=hub.laoma.cloud/library/httpd --image-pull-policy=IfNotPresent
root@master30 ~ 09:08:45# kubectl get pod web -o yaml|grep serviceAccount
  serviceAccount: default
  serviceAccountName: default
      - serviceAccountToken:

服务账号与用户账号比较

Kubernetes 区分用户账号与服务账号,主要基于以下原因:

类别 用户账号 服务账号
使用主体 面向人 面向 Pod,Pod 中的应用程序通过服务账号访问集群。
范围 集群范围,集群中的用户名必须唯一。 命名空间范围,两个不同的命名空间可以包含同名的服务账号。
轻量程度 重量级,通过认证模块配置。 轻量级,可根据具体任务按需创建。将服务账户与用户分离,使工作负载更易于遵循最小权限原则。

服务账号令牌卷机制

默认情况下,Kubernetes 会为 Pod 添加一个投射卷,该卷中包含用于访问 Kubernetes API 的令牌。

bash 复制代码
root@master30 ~ 09:09:30# 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

该清单片段定义了由三个数据源组成的投射卷,每个数据源对应卷内的一条独立路径。这三个数据源分别是:

  1. serviceAccountToken 数据源 :包含 kubelet 从 kube-apiserver 获取的令牌。kubelet 通过 TokenRequest API 获取具有时效性的令牌,该令牌会在 Pod 被删除或达到定义的生命周期(默认为 1 小时)后过期。令牌绑定到特定的 Pod,并将其 audience(受众)设置为与 kube-apiserver 的 audience 相匹配。这种机制取代了此前基于 Secret 挂载卷的方式,旧机制中 Secret 代表 Pod 的 ServiceAccount 且不会过期。

    注意:目前没有特定机制可以主动使通过 TokenRequest 签发的令牌失效。如果不再信任某个 Pod 绑定的服务账号令牌,可以删除该 Pod,删除操作会使其绑定的服务账号令牌随之过期。

  2. configMap 数据源:ConfigMap 中包含一组证书颁发机构(CA)数据,Pod 可以使用这些证书验证所连接的是否为集群的 kube-apiserver,避免连接到中间件或配置错误的对等节点。

  3. downwardAPI 数据源:用于获取 Pod 所在命名空间的名称,并将该信息暴露给 Pod 内运行的应用程序代码。

Pod 中挂载了该投射卷的所有容器均可访问上述信息。

bash 复制代码
root@master30 ~ 09:10:15# kubectl exec -it web -- bash
root@web /usr/local/apache2 09:11:00# ls /var/run/secrets/kubernetes.io/serviceaccount
ca.crt	namespace  token
root@web /usr/local/apache2 09:11:45# cat \
/var/run/secrets/kubernetes.io/serviceaccount/token 
eyJhbGciOiJSUzI1NiIsI......
root@web /usr/local/apache2 09:12:30# cat /var/run/secrets/kubernetes.io/serviceaccount/namepace
auth

服务账户管理

SA 创建
bash 复制代码
root@master30 ~ 09:13:15# kubectl create sa sa1
root@master30 ~ 09:14:45# kubectl get sa sa1
NAME   SECRETS   AGE
sa1    1         9s
SA 授权
bash 复制代码
root@master30 ~ 09:15:30# 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 ~ 09:17:00# kubectl delete pod web
root@master30 ~ 09:18:30# kubectl run web --image=hub.laoma.cloud/library/httpd --image-pull-policy=IfNotPresent --dry-run=client -o yaml > web.yaml
root@master30 ~ 09:19:30# 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 ~ 09:20:30# kubectl apply -f web.yaml
root@master30 ~ 09:22:00# kubectl get pod web -o yaml
......
spec:
......
  serviceAccount: sa1
  serviceAccountName: sa1

重要 :虽然 pod/web 中的 httpd 进程具有 clusterrole/cluster-admin 角色,但它仅用于提供 httpd 服务,不会执行其他操作。

回收权限
bash 复制代码
root@master30 ~ 09:22:45# kubectl delete clusterrolebindings auth-sa1-cluster-admin
clusterrolebinding.rbac.authorization.k8s.io "auth-sa1-cluster-admin" deleted
使用案例

集群 Dashboard 中的 Pod 使用服务账户来管理集群。

手动管理服务账号 token

  • Kubernetes v1.22 之前的版本会自动创建访问 Kubernetes API 的凭据,其机制为:先创建令牌 Secret,再将其挂载到运行中的 Pod 中。
  • 在包括 Kubernetes v1.28 在内的近期版本中,通过 TokenRequest API 直接获取 API 凭据,并使用投射卷挂载到 Pod 中。通过这种方式获取的令牌具有绑定的生命周期,当挂载该令牌的 Pod 被删除时,令牌将自动失效。
创建临时令牌
bash 复制代码
root@master30 ~ 09:24:15# kubectl create sa sa1
root@master30 ~ 09:25:45# kubectl create token sa1
eyJhbGciOiJSUzI1NiIsImt......

# 注意:服务账号属性中是看不到关联的token的。
root@master30 ~ 09:27:15# kubectl get sa sa1 -o yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  creationTimestamp: "2023-11-01T04:05:03Z"
  name: sa1
  namespace: liu
  resourceVersion: "11548"
  uid: 4944f75b-43ae-43fc-abfd-f2d20b7375f1
创建永久令牌

创建一个带有特殊注解 kubernetes.io/service-account.name 的 Secret 对象。一旦手动创建该 Secret 并将其关联到 ServiceAccount,Kubernetes 控制平面就会自动将令牌填充到该 Secret 中。

bash 复制代码
root@master30 ~ 09:28:00# 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 ~ 09:29:00# kubectl apply -f sa1-token.yaml

当删除与某个 Secret 相关联的 ServiceAccount 时,Kubernetes 控制平面会自动清理该 Secret 中长期有效的令牌。

bash 复制代码
root@master30 ~ 09:30:30# kubectl delete sa sa1
root@master30 ~ 09:32:00# kubectl get secrets
No resources found in liu namespace.

说明: 尽管存在手动创建长久 ServiceAccount 令牌的机制,但还是推荐使用 TokenRequest 获得短期的 API 访问令牌。

思考

问题: Kubernetes 为什么不提供用户管理和身份认证功能?

Kubernetes 采用 RBAC 模型管理用户(User)和用户组(Group)的权限。然而,Kubernetes 中并不存储真正的用户和用户组信息,甚至没有对应的 User 和 Group 资源对象,因此 Kubernetes 无法列出受信任的用户列表和用户组列表。

换言之,Kubernetes 本身并未提供用户管理和身份认证功能。 除 Service Account 外,所有用户信息都依赖外部用户管理系统进行存储,因此通过 kube-apiserver 根本无法列出 User 和 Group。

**解答:**这其实挺符合UNIX设计哲学的,即 Do One Thing and Do It Well

Kubernetes 只专注于做应用编排,其他的功能则提供接口集成,除了认证和鉴权,我们发现网络、存储也都如此。

这样做的好处也显而易见,用户账户信息与Kubernetes集群松耦合,便于集成企业已有的身份认证系统,如AD、LDAP、Keycloak等。

环境清理

bash 复制代码
root@master30 ~ 09:32:45# kubectl delete ns auth

Kubernetes 集群 Dashboard

kuboard

kuboard 介绍

Kuboard 是一款专为 Kubernetes 设计的免费管理界面,兼容 Kubernetes 1.13 及以上版本。Kuboard 每周发布一个 beta 版本,最长每月发布一个正式版本,经过两年的持续迭代和优化,已具备多集群管理、权限管理、监控套件、日志套件等丰富功能,已有 1000 多家企业将 Kuboard 应用于生产环境。Kuboard 自 2019 年 8 月发布首个版本以来,获得了众多用户的认可,目前已获得 10000+ GitHub Star。

Kuboard 的定位与 Kubernetes Dashboard 相似,主要区别在于:

  • Kuboard 从微服务参考架构的视角组织界面,参考 Kuboard 简介
  • Kuboard 无需手工编写 YAML 文件,进一步降低了 Kubernetes 的使用门槛,提升了便捷性。
  • 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 提供 Kubernetes 集群的监控能力,可监控集群、节点、工作负载、容器组等各级别对象的 CPU、内存、网络、磁盘等资源使用情况;
  • 日志聚合套件,基于 Grafana / Loki / Promtail 实现日志聚合;

    • 存储卷浏览器,查看和操作存储卷中的内容;
  • 告警配置:可通过界面直接配置资源层监控套件的告警消息发送:

    • 支持邮件、微信发送告警消息;
  • 支持告警路由配置;

    • 支持告警规则配置等;
  • 操作审计:Kuboard 支持操作审计功能:

    • 审计用户通过 Kuboard 界面和 Kuboard API 执行的操作;
    • 自定义审计规则;

在线演示

在线演示环境中,您仅具备只读权限,只能体验 Kuboard 的部分功能。

地址:https://demo.kuboard.cn

用 户: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 v3.x,请注意:

  • 您可以同时使用 Kuboard v3.x 和 Kuboard v2.0.x;
  • Kuboard v3.x 支持 amd64(x86)架构和 arm64(armv8)架构的 CPU;

kuboard 安装和配置

本次实验环境的 Kubernetes 版本为 1.32.0,使用 Kuboard v3.x 版本。

安装方式
  1. 独立容器 方式运行
  2. 在 Kubernetes 中运行
  3. static pod 方式运行

基于以下原因,建议以 独立容器 方式运行 Kuboard:

  • 结构更清晰(Kuboard 作为多集群管理界面,应独立于任何集群之外);
  • 登录 Kuboard 时可使用不同的认证方式;
  • 问题排查更简单;
独立容器 方式运行
安装计划

在正式安装 Kuboard v3 之前,需要先设计一个简单的部署计划。本例中各组件之间的连接方式如下图所示:

  • 假设用户通过 http://外网IP:80 访问 Kuboard v3;
  • 安装在 Kubernetes 中的 Kuboard Agent 通过 内网IP 访问 Kuboard 的 Web 服务端口 80 和 Kuboard Agent Server 端口 10081。
安装过程
bash 复制代码
# 创建目录
root@master30 ~ 10:45:00# mkdir /kuboard-data

# 创建容器
root@master30 ~ 10:46:00# 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 ~ 10:47:00# nerdctl container rm kuboard --force
root@master30 ~ 10:48:00# rm -fr /kuboard-data
卸载
bash 复制代码
# 删除静态文件
root@master30 ~ 10:51:30# rm -f /etc/kubernetes/manifests/kuboard.yaml

# 删除数据目录
root@master30 ~ 10:52:30# rm -fr /usr/share/kuboard

# 删除命名空间
root@master30 ~ 10:53:30# kubectl delete ns kuboard
访问过程

在浏览器中输入 http://your-host-ip:80,访问 Kuboard v3.x 界面。

登录信息:

  • 用户名: admin
  • 密 码: Kuboard123

参考资料

安装手册:https://kuboard.cn/install/v3/install.html

Kubernetes Dashboard

介绍

项目地址:https://github.com/kubernetes/dashboard/

Kubernetes Dashboard 是 Kubernetes 的官方 Web UI。

Kubernetes Dashboard 具备以下功能:

  • 向 Kubernetes 集群部署容器化应用
  • 诊断容器化应用的问题
  • 管理集群资源
  • 查看集群上运行的应用程序
  • 创建、修改 Kubernetes 上的资源(例如 Deployment、Job、DaemonSet 等)
  • 展示集群中发生的错误

例如:您可以对一个 Deployment 进行伸缩、执行滚动更新、重启一个 Pod 或部署一个新的应用程序。

部署

bash 复制代码
# 下载资源yaml文件
root@master30 ~ 10:45:00# wget -O recommended.yaml http://192.168.46.200/class/course-materials/softwares/stage03/kubernetes-dashboard-recommended-v2.6.1.yaml

# 查看images
root@master30 ~ 10:46:30# grep image: recommended.yaml
          image: hub.laoma.cloud/kubernetesui/dashboard:v2.6.1
          image: hub.laoma.cloud/kubernetesui/metrics-scraper:v1.0

# 执行部署
root@master30 ~ 10:47:30# kubectl apply -f recommended.yaml

验证

bash 复制代码
root@master30 ~ 10:49:00# kubens
default
kube-node-lease
kube-public
kube-system
kubernetes-dashboard

root@master30 ~ 10:50:00# kubens kubernetes-dashboard
root@master30 ~ 10:51:00# 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 ~ 10:51:45# 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 ~ 10:52:30# kubectl get serviceaccounts
NAME                   SECRETS   AGE
default                1         23h
kubernetes-dashboard   1         23h

root@master30 ~ 10:53:15# 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 ~ 10:54:00# kubectl get clusterrolebindings.rbac.authorization.k8s.io |grep dashboard
kubernetes-dashboard                                   ClusterRole/kubernetes-dashboard                                                   23h

# sa/kubernetes-dashboard具有ClusterRole/kubernetes-dashboard
root@master30 ~ 10:54:45# 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 ~ 10:55:30# 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 ~ 10:56:15# kubectl get deployments.apps kubernetes-dashboard -o yaml|grep ' serviceAccount'
      serviceAccount: kubernetes-dashboard
      serviceAccountName: kubernetes-dashboard

service/kubernetes-dashboard 的类型修改为 NodePort,以便从外部访问。

bash 复制代码
root@master30 ~ 10:57:00# kubectl edit svc kubernetes-dashboard
spec:
......
  type: NodePort

root@master30 ~ 10:58:30# 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 配置访问,参考:

yaml 复制代码
apiVersion: 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.liu.cloud
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service: 
            name: kubernetes-dashboard
            port: 
              number: 443

在客户端将 dashboard.liu.cloud 域名解析到 Ingress 服务地址,实验环境中为 10.1.8.40

后续直接访问 https://dashboard.liu.cloud

访问 Dashboard

访问 https://10.1.8.30:30518

新版本

Kubernetes Dashboard 新版本仅支持使用 Bearer Token 登录

Kubernetes Dashboard 默认部署时仅配置了最低权限的 RBAC。因此,需要为服务账号 kubernetes-dashboard 绑定 cluster-admin 这个 ClusterRole。

bash 复制代码
root@master30 ~ 10:59:15# kubectl create clusterrolebinding kubernetes-dashboard-cluster-admin --clusterrole cluster-admin --serviceaccount kubernetes-dashboard:kubernetes-dashboard
yaml 复制代码
# 创建 Bearer Token
root@master30 ~ 11:00:45# 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 默认部署时仅配置了最低权限的 RBAC。接下来分析默认的权限配置。

bash 复制代码
# Kubernetes Dashboard 以deployment形式运行
root@master30 ~ 11:03:15# 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 ~ 11:04:00# kubectl get deployments.apps kubernetes-dashboard -o yaml|grep serviceAc
      serviceAccount: kubernetes-dashboard
      serviceAccountName: kubernetes-dashboard

# serviceAccount: kubernetes-dashboard 绑定了Role/kubernetes-dashboard 
root@master30 ~ 11:04:45# kubectl get rolebindings.rbac.authorization.k8s.io
NAME                   ROLE                        AGE
kubernetes-dashboard   Role/kubernetes-dashboard   147

# Role/kubernetes-dashboard 具备能力如下:
root@master30 ~ 11:05:30# 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 ~ 11:06:15# 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 登录。

Kubernetes 动态卷供应

动态卷介绍

此前创建存储卷(PV)都需要管理员手动操作,这类卷被称为静态卷。那么 Kubernetes 能否"智能"地管理卷呢?例如在需要时自动创建、不用时自动清理?

答案是肯定的 ------ 这就是动态卷供应的能力。它彻底省去了集群管理员预先手动配置存储的工作,用户只需提出存储需求,系统就会自动创建对应的存储卷。

动态卷供应流程

  1. 管理员先创建"存储模板"(StorageClass),指定使用哪个"制备器"(Provisioner)来创建卷;
  2. 用户创建"存储申请单"(PVC)时,指定要使用的"存储模板";
  3. 系统收到申请后,由模板绑定的"制备器"自动创建一个 PV,并将 PV 与用户的 PVC 绑定,用户直接使用 PVC 即可。

StorageClass

StorageClass 就像管理员定义的"存储套餐"------不同套餐对应不同的存储类型、服务质量(如读写速度)、备份策略等。Kubernetes 不关心套餐的名称,只负责按照套餐规则创建卷。

  • 命名很重要:用户创建 PVC 时,需要通过名称选择对应的存储套餐;
  • 创建后不可修改:StorageClass 一旦创建,名称和参数均无法修改,如需变更只能重新创建。

核心配置

每个"存储套餐"都包含以下核心信息,用于指导系统创建 PV:

  • provisioner:制备器,指定使用哪个工具创建 PV,为必填项。

    Kubernetes 内置了一些 Provisioner,名称以 kubernetes.io 开头(如 AWS EBS、Azure Disk);也可以使用第三方提供的 Provisioner(例如 NFS 没有内置制备器,需要自行安装),只需按照 Kubernetes 规范运行即可。

    存储类型 是否内置 说明
    AWSElasticBlockStore ✅ 是 亚马逊云硬盘
    AzureFile/AzureDisk ✅ 是 微软云文件 / 硬盘
    Ceph RBD ✅ 是 Ceph 块存储
    Glusterfs ✅ 是 分布式文件系统
    NFS ❌ 否 网络文件系统,需安装外部制备器
    本地存储(Local) ❌ 否 节点本地硬盘,需安装外部制备器
    iSCSI/CephFS ❌ 否 需外部制备器
  • reclaimPolicy:PV 回收策略,指定 PV 不再使用时的处理方式。

    • Delete(默认):PVC 删除后,PV 也会自动删除,对应的后端存储(如云硬盘、NFS 目录)也会被清除;
    • Retain:PVC 删除后,PV 予以保留,后端存储的数据也不会删除,管理员可手动处理。
  • volumeBindingMode:指定 PVC 创建后与 PV 绑定的时机。

    • 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 不用了,pv保留
allowVolumeExpansion: true  # 允许扩容
mountOptions:
  - debug  # 挂载时开启调试模式
volumeBindingMode: Immediate  # 立即创建并绑定PV

部署 Local Path provisioner

Local Path Provisioner 是 Kubernetes 官方推荐的本地存储动态卷插件,适用于需要使用节点本地磁盘的场景。

部署本地路径制备器

bash 复制代码
# 下载官方模板
root@master30 ~ 14:10:00# wget https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml

# 查看资源配置
root@master30 ~ 14:11:30# 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: hub.laoma.cloud/rancher/local-path-provisioner:v0.0.37
          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 ~ 14:12:15# kubectl apply -f local-path-storage.yaml

# 确认资源状态
root@master30 ~ 14:13:45# kubectl get sc local-path
NAME         PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path   rancher.io/local-path   Delete          WaitForFirstConsumer   false                  31s

root@master30 ~ 14:14:30# 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 ~ 14:15:15# kubectl config set-context --current --namespace default
创建 pvc
bash 复制代码
root@master30 ~ 14:16:15# 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 ~ 14:17:00# kubectl apply -f local-path-pvc.yaml
root@master30 ~ 14:18:30# kubectl get pvc
NAME             STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
local-path-pvc   Pending                                      local-path     <unset>                 6s

# 等待pod关联才会创建本地目录
创建 deployment
bash 复制代码
root@master30 ~ 14:19:15# 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 ~ 14:20:00# kubectl apply -f local-path-deployment.yaml

# 查看 pod 状态
root@master30 ~ 14:21:30# 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.liu.cloud   <none>           <none>
webapp-6d46b54487-kzkd5   1/1     Running   0          114s   10.224.19.22   worker31.liu.cloud   <none>           <none>
# 所有节点都调度到 worker31,因为只用worker31可以提供这个卷。

# 查看 pvc 状态
root@master30 ~ 14:22:15# 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 ~ 14:23:00# kubectl exec webapp-6d46b54487-6nqmh -- bash -c 'echo Hello World From Nginx > /usr/share/nginx/html/index.html'

# 验证本地路径
root@worker31 ~ 14:23:45# ls /opt/local-path-provisioner/pvc-e992833c-af6a-4d4d-aff5-fc544394db6d_default_local-path-pvc/
index.html
root@worker31 ~ 14:24:30# cat /opt/local-path-provisioner/pvc-e992833c-af6a-4d4d-aff5-fc544394db6d_default_local-path-pvc/index.html
Hello World From Nginx
清理资源
bash 复制代码
# 删除 deployment
root@master30 ~ 14:25:15# kubectl delete deployment webapp

# 删除 pvc
root@master30 ~ 14:26:45# kubectl delete pvc local-path-pvc

# 本地目录也被删除
root@worker31 ~ 14:28:15# 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 ~ 14:45:00# apt install -y nfs-kernel-server

# 安创建NFS目录 修改创建文件夹的权限
root@master30 ~ 14:46:30# mkdir -m 777 /shares/

# 配置共享,允许所有客户端访问
root@master30 ~ 14:47:30# cat << EOF > /etc/exports
/shares *(rw,sync,no_root_squash,no_all_squash,insecure)
EOF

# 重启 nfs server
root@master30 ~ 14:48:15# systemctl restart nfs-server.service

# 客户端安装
root@worker31 ~ 14:49:45# apt install -y nfs-common
root@worker32 ~ 14:51:15# apt install -y nfs-common

部署 NFS provisioner

NFS 没有内置制备器,这里我们自定义一个外部分配器。

bash 复制代码
# 资源部署到命名空间:kube-storage
root@master30 ~ 14:52:45# kubectl create ns kube-storage
root@master30 ~ 14:54:15# kubectl config set-context --current --namespace kube-storage
创建 RBAC 权限

创建 nfs-rbac.yaml 文件,赋予 Provisioner 操作 PV/PVC 的权限。

yaml 复制代码
root@master30 ~ 14:55:15# 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 ~ 14:56:00# kubectl apply -f nfs-rbac.yaml
部署 NFS Provisioner 应用
yaml 复制代码
root@master30 ~ 14:57:30# 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: hub.laoma.cloud/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 ~ 14:58:15# kubectl apply -f nfs-provisioner.yaml

# 查看 NFS Provisioner 应用
root@master30 ~ 14:59:45# kubectl get deployments.apps -n kube-storage
NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
nfs-client-provisioner   1/1     1            1           1m
创建 NFS StorageClass
yaml 复制代码
root@master30 ~ 15:00:30# 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 ~ 15:01:15# kubectl apply -f nfs-storageclass.yaml

# 查看 StorageClass
root@master30 ~ 15:02:45# kubectl get sc
NAME          PROVISIONER      RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-storage   fuseim.pri/ifs   Delete          Immediate           true                   2s

验证部署

在 default 命名空间中进行测试。

bash 复制代码
root@master30 ~ 15:03:30# kubens default
创建 pvc
bash 复制代码
root@master30 ~ 15:04:30# 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 ~ 15:05:15# kubectl apply -f nfs-pvc.yaml

# 查看 pvc 状态
root@master30 ~ 15:06:45# 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 ~ 15:07:30# 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 ~ 15:08:15# 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
创建 deployment
bash 复制代码
root@master30 ~ 15:09:00# 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 ~ 15:09:45# kubectl apply -f nfs-deploy.yaml

root@master30 ~ 15:11:15# kubectl expose deployment webapp --target-port 80 --port 80 --protocol TCP
root@master30 ~ 15:12:15# 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 ~ 15:13:00# curl 10.109.140.205
Hello World From Nginx
root@master30 ~ 15:13:45# curl 10.109.140.205
Hello World From Nginx
清理资源
bash 复制代码
# 删除 deployment
root@master30 ~ 15:14:30# kubectl delete deployments.apps webapp

# 删除 pvc,pv也将一起删除
root@master30 ~ 15:16:00# kubectl delete pvc webclaim
persistentvolumeclaim "webclaim" deleted
root@master30 ~ 15:17:30# kubectl get pv
No resources found

# 查看后端存储中数据,仍然保留
root@master30 ~ 15:18:15# ls  /shares/default-webclaim-pvc-6a020ff4-6b7f-4ef4-90fc-3dc94d3e8ddc/
index.html
相关推荐
hyf3266331 小时前
低成本站群泛程序部署:单服务器托管百站稳定运行教程
运维·服务器·前端·搜索引擎·蜘蛛池
爱莉希雅&&&1 小时前
Kubernetes Service 完整学习笔记
笔记·学习·kubernetes
小此方1 小时前
Re:Linux系统篇(五十六)线程篇 · 九:基于阻塞队列的生产者消费者模型与条件变量
linux·运维·服务器
电商API_1800790524710 小时前
京东商品详情API技术文章
大数据·运维·人工智能·网络爬虫
三言老师11 小时前
秘诀-如何远程SSN访问家里的八台电脑(frp 实操方案)
linux·运维·ssh
BTU_YC13 小时前
Docker Compose 部署 DozerDB 完整教程
运维·docker·容器
流烟默13 小时前
Kubernetes 调度器完全指南:从原理到生产实战
云原生·容器·kubernetes
RisunJan15 小时前
Linux命令-scriptreplay(终端会话回放)
linux·运维·chrome
x²+(y-√³x²)²=116 小时前
Linux打包文件到Windows,文件/文件类型丢失
linux·运维·windows