在 “Docker基础知识 (20) - Kubernetes(三) | 在 K8s 集群上部署 Nginx” 里部署的 Nginx,通过存储卷(volumes)挂载到 master 的 /home/k8s/nginx-test/nginx 目录下的子目录。
当 K8s 集群把 Pod 调度到 node 上时,node 上没有 /home/k8s/nginx-test/nginx 目录,很显然,这种情况下 nginx 无法在 node 上正常运行。
要想让存储卷(volumes)能被 Pod 任意挂载,我们需要变更存储的方式,不能限定在本地磁盘,而是要改成网络存储,这样 Pod 无论在哪里运行,只要知道 IP 地址或者域名,就可以通过网络访问存储设备。
本文我们在 “Docker基础知识 (20) - Kubernetes(三) | 在 K8s 集群上部署 Nginx” 基础上,来演示如何部署 NFS 和创建静态 PV/PVC。
Kubernetes 的共享存储详细介绍,请参考 “系统架构与设计(7)- Kubernetes 的共享存储”。
NFS (Network File System),即网络文件系统,是 FreeBSD 支持的文件系统中的一种。NFS 允许一个系统在网络上与它人共享目录和文件。通过使用 NFS,用户和程序可以像访问本地文件一样访问远端系统上的文件。
1. 部署环境
虚拟机: Virtual Box 6.1.30(Windows 版)
操作系统: Linux CentOS 7.9 64位
Docker 版本:20.10.7
Docker Compose 版本:2.6.1
Kubernetes 版本:1.23.0
工作目录:/home/k8s
Linux 用户:非 root 权限用户 (用户名自定义,这里以 xxx 表示),属于 docker 用户组,
1) 主机列表
主机名 IP 角色 操作系统 k8s-master 192.168.0.10 master CentOS 7.9 k8s-node01 192.168.0.11 node CentOS 7.9
2) 工作目录结构
以下目录结构处于 master 节点 /home/k8s 目录下:
k8s |- init.default.yaml |- kube-flannel.yml |- nginx-test |- namespace.yaml |- nginx-deployment.yaml |- nginx-service.yaml |- nginx | |- conf.d | | |- nginx.conf | | | |- html | | |- test.html | | | |- log | |- nfs |- nginx-nfs-pv.yaml |- nginx-nfs-pvc.yaml |- nginx-nfs-deployment.yaml
2. 安装配置 NFS
NFS 采用的是 Client/Server 架构,需要选定一台主机安装 NFS 服务端,其他要使用存储的主机安装 NFS 客户端。
1) 服务端操作(master,192.168.0.10)
NFS 服务端可以安装在一台独立的主机上,本文把 NFS 服务端安装在 master 上。
$ sudo yum install -y nfs-utils
... Installed: nfs-utils.x86_64 1:1.3.0-0.68.el7.2 Dependency Installed: gssproxy.x86_64 0:0.7.0-30.el7_9 keyutils.x86_64 0:1.5.8-3.el7 libbasicobjects.x86_64 0:0.1.1-32.el7 libcollection.x86_64 0:0.7.0-32.el7 libevent.x86_64 0:2.0.21-4.el7 libini_config.x86_64 0:1.3.1-32.el7 libnfsidmap.x86_64 0:0.25-19.el7 libpath_utils.x86_64 0:0.2.1-32.el7 libref_array.x86_64 0:0.1.5-32.el7 libtirpc.x86_64 0:0.2.4-0.16.el7 libverto-libevent.x86_64 0:0.2.5-4.el7 quota.x86_64 1:4.01-19.el7 quota-nls.noarch 1:4.01-19.el7 rpcbind.x86_64 0:0.2.0-49.el7 tcp_wrappers.x86_64 0:7.6-77.el7 Complete!
# 配置 NFS 服务
$ sudo vim /etc/exports
# 共享目录
/home/k8s 192.168.0.0/16(rw,sync,all_squash)
参数说明:
读写权限: rw - 读写权限,ro - 只读权限;
用户映射:
all_squash:无论 NFS 客户端使用什么身份访问,都映射为 NFS 服务器的匿名用户;
no_all_squash:无论 NFS 客户端使用什么身份访问,都不压缩用户权限;
root_squash: 当 NFS 客户端以 root 身份访问时,映射为 NFS 服务器的匿名用户;
no_root_squash:当 NFS 客户端以 root 身份访问时,映射为 NFS 服务器的 root 管理员;
其它:
secure: NFS 通过 <= 1024 的安全 TCP/IP 端口发送;
insecure: NFS 通过 > 1024 的端口发送;
sync:同时将数据写入到内存和硬盘中,不丢失数据;
async:优先将数据保存到内存中,然后在写入到硬盘中,效率高,但有可能丢失数据;
anonuid=xxx NFS 服务器 /etc/passwd 文件中匿名用户的 UID;
anongid=xxx NFS 服务器 /etc/passwd 文件中匿名用户的 GID;
192.168.0.0/16: 允许访问 NFS 服务端的网段,* 表示所有网段;
# 启动 nfs 服务,并设置开机启动
$ sudo systemctl enable nfs
$ sudo systemctl start nfs
# 查看 nfs 服务状态
$ sudo systemctl status nfs
# 查看 /home/k8s 共享目录
$ cd /home/k8s
$ ls -la
total 12 drwxr-xr-x 3 xxx xxx 73 Nov 20 02:20 . drwxr-xr-x. 5 root root 46 Nov 20 02:27 .. -rw-r--r-- 1 xxx xxx 834 Nov 20 02:19 init.default.yaml -rw-r--r-- 1 xxx xxx 4583 Nov 20 02:20 kube-flannel.yml drwxr-xr-x 4 xxx xxx 107 Nov 20 02:25 nginx-test
2) 客户端操作(node,192.168.0.11)
$ sudo yum install -y nfs-utils
# 查看服务端里面可以挂载的目录
$ showmount -e 192.168.0.10
Export list for 192.168.0.10:
/home/k8s 192.168.0.0/16
# 创建本地 /home/k8s 目录,把目录 owner 从 root 改成当前登陆用户
$ sudo mkdir -p /home/k8s
$ sudo chown -R xxx:xxx /home/k8s
# 手动挂载 master 的共享目录
$ sudo mount -t nfs 192.168.0.10:/home/k8s /home/k8s
$ cd /home/k8s
$ ls -la
total 12 drwxr-xr-x 3 xxx xxx 73 Nov 20 02:20 . drwxr-xr-x. 4 root root 31 Nov 20 09:21 .. -rw-r--r-- 1 xxx xxx 834 Nov 20 02:19 init.default.yaml -rw-r--r-- 1 xxx xxx 4583 Nov 20 02:20 kube-flannel.yml drwxr-xr-x 4 xxx xxx 107 Nov 20 02:25 nginx-test
注:node 上挂载的 /home/k8s 目录和 master 上 /home/k8s 目录里的内容一致,挂载成功。
此时 node 上的 /home/k8s 目录就相当于本地目录,被调度到 node 的 Pod 可以正常访问 /home/k8s/nginx-test/nginx 目录,Pod 里的 nginx 服务可以正常运行。
本文的测试环境下,master 和 node 上使用的 Linux 用户名/用户组,都是同名的 xxx。没有测试在不同 Linux 用户名/用户组的情况,如果遇到文件和目录访问权限问题,可以尝试把 master 上的 /home/k8s 目录改成 777 属性。
# 卸载共享目录
$ sudo umount /home/k8s
# 删除共享目录
$ sudo rm -rf /home/k8s
3. 修改 NFS Server 配置
上面在 node 节点上手动挂载 master 节点共享目录的方式,在 node 节点不多(3 ~ 5 个)的情况下,或许是一种方便的共享存储实现方式,但这个种方式有很明显的弊端:
(1) node 节点超过一定数量时,在 node 各节点上手动挂载目录,是一件工作量很大的事情;
(2) 没有程序监控各手动挂载目录的状态,出现挂载失效时,无法实现自动通知和修复;
(3) 在 master 上 Deployment 里要调整 Volumes 时,各 node 上的手动挂载目录也需要手动调整,这显然违背了 K8s 轻松部署的原则;
我们需要使用基于 K8s 提供的 PersistentVolume(PV)、PersistentVolumeClaim(PVC)和存储分配器(Provisioner)等来实现更灵活的共享存储配置。
# 添加共享目录
$ sudo vim /etc/exports
# 共享目录 /home/k8s 192.168.0.0/16(rw,sync,all_squash) /home/k8s/nginx-test/nginx/conf.d 192.168.0.0/16(ro,sync,all_squash) /home/k8s/nginx-test/nginx/html 192.168.0.0/16(ro,sync,all_squash) /home/k8s/nginx-test/nginx/log 192.168.0.0/16(rw,sync,all_squash,anonuid=1000,anongid=1000)
注:假设 master 上当前登陆用户是 xxx:xxx (用户:用户组),可是运行 id 命令获取 uid 和 gid。
$ id
uid=1000(xxx) gid=1000(xxx) groups=1000(xxx),994(docker)
把 /home/k8s 目录及子目录的 owner 设置为 xxx:xxx (即当前登陆用户:用户组)。
在 /etc/exports 里的配置:
home/k8s/nginx-test/nginx/log 192.168.0.0/16(rw,sync,all_squash,anonuid=1000,anongid=1000)
其中 anonuid=1000,anongid=1000 就是用户 xxx:xxx,/home/k8s 目录及子目录的 owner 也是 xxx:xxx,所以 NFS 间接有了 /home/k8s/nginx-test/nginx/log 目录的写操作权限。
# 重启 nfs 服务
$ sudo systemctl restart nfs
# 查看服务端里面可以挂载的目录
$ showmount -e 192.168.0.10
Export list for 192.168.0.10: /home/k8s/nginx-test/nginx/log 192.168.0.0/16 /home/k8s/nginx-test/nginx/html 192.168.0.0/16 /home/k8s/nginx-test/nginx/conf.d 192.168.0.0/16 /home/k8s 192.168.0.0/16
4. 创建静态 PV/PVC
1) 创建 nginx-nfs-pv.yaml 文件
$ cd /home/k8s/nginx-test/nfs # 手动创建 nfs 子目录
$ vim nginx-nfs-pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: nginx-nfs-pv-confd labels: name: nginx-nfs-pv-confd spec: storageClassName: nfs-server accessModes: - ReadOnlyMany capacity: storage: 1Mi nfs: path: /home/k8s/nginx-test/nginx/conf.d server: 192.168.0.10 --- apiVersion: v1 kind: PersistentVolume metadata: name: nginx-nfs-pv-html labels: name: nginx-nfs-pv-html spec: storageClassName: nfs-server accessModes: - ReadOnlyMany capacity: storage: 100Mi nfs: path: /home/k8s/nginx-test/nginx/html server: 192.168.0.10 --- apiVersion: v1 kind: PersistentVolume metadata: name: nginx-nfs-pv-log labels: name: nginx-nfs-pv-log spec: storageClassName: nfs-server accessModes: - ReadWriteMany capacity: storage: 50Mi nfs: path: /home/k8s/nginx-test/nginx/log server: 192.168.0.10
参数说明:
spec.storageClassName: 驱动类型 nfs-server,可以简写成 nfs
spec.accessModes:
ReadWriteOnce – 可以 read-write 模式被 mount 到单个节点;
ReadOnlyMany – 可以 read-only 模式被 mount 到多个节点;
ReadWriteMany – 可以 read-write 模式被 mount 到多个节点;
spec.capacity.storage: PV 的容量大小,比如 1Mi,1Gi
spec.nfs.path: NFS 服务器上的共享目录全路径;
spec.nfs.server: NFS 服务器的 DNS 或 IP 地址;
注:spec.nfs.path 要先手动创建好,否则 K8s 按照 PV 的描述会无法挂载 NFS 共享目录,PV 就会处于 Pending 状态无法使用。
# 执行创建命令
$ kubectl apply -f nginx-nfs-pv.yaml
persistentvolume/nginx-nfs-pv-confd created persistentvolume/nginx-nfs-pv-html created persistentvolume/nginx-nfs-pv-log created
# 查看 PV
$ kubectl get pv
NAME CAPACITY ACCESS MODES ... STATUS STORAGECLASS nginx-nfs-pv-confd 1Mi ROX Available nfs nginx-nfs-pv-html 100Mi ROX Available nfs nginx-nfs-pv-log 50Mi RWX Available nfs
2) 创建 nginx-nfs-pvc.yaml 文件
$ cd /home/k8s/nginx-test/nfs
$ vim nginx-nfs-pvc.yaml
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-nfs-pvc-confd namespace: nginx-test spec: storageClassName: nfs-server accessModes: - ReadOnlyMany resources: requests: storage: 1Mi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-nfs-pvc-html namespace: nginx-test spec: storageClassName: nfs-server accessModes: - ReadOnlyMany resources: requests: storage: 100Mi --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-nfs-pvc-log namespace: nginx-test spec: storageClassName: nfs-server accessModes: - ReadWriteMany resources: requests: storage: 50Mi
参数说明:
spec.resources.requests.storage: 表示请求(或申请)多大的容量,比如 1Mi,1Gi;
# 执行创建命令
$ kubectl apply -f nginx-nfs-pvc.yaml
persistentvolumeclaim/nginx-nfs-pvc-confd created persistentvolumeclaim/nginx-nfs-pvc-html created persistentvolumeclaim/nginx-nfs-pvc-log created
# 查看 PVC
$ kubectl get pvc -n nginx-test
NAME STATUS VOLUME CAPACITY ACCESS ... STORAGECLASS nginx-nfs-pvc-confd Bound nginx-nfs-pv-confd 1Mi ROX nfs nginx-nfs-pvc-html Bound nginx-nfs-pv-html 100Mi ROX nfs nginx-nfs-pvc-log Bound nginx-nfs-pv-log 50Mi RWX nfs
# 查看 PV
$ kubectl get pv
NAME CAPACITY ACCESS ... STATUS CLAIM STORAGECLASS nginx-nfs-pv-confd 1Mi ROX Bound nginx-test/nginx-nfs-pvc-confd nfs nginx-nfs-pv-html 100Mi ROX Bound nginx-test/nginx-nfs-pvc-html nfs nginx-nfs-pv-log 50Mi RWX Bound nginx-test/nginx-nfs-pvc-log nfs
# 删除 PVC
$ kubectl delete pvc [PVC_NAME] -n nginx-test
# 删除 PV,要先删除 CLAIM 的 PVC
$ kubectl delete pv [PV_NAME]
3) 创建 nginx-nfs-deployment.yaml 文件
$ cd /home/k8s/nginx-test/nfs
$ vim nginx-nfs-deployment.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-nfs-deployment namespace: nginx-test spec: replicas: 1 selector: matchLabels: app: nginx-nfs-pod template: metadata: labels: app: nginx-nfs-pod spec: containers: - name: nginx-nfs-1-21 image: nginx:1.21 ports: - containerPort: 80 volumeMounts: - name: conf mountPath: /etc/nginx/conf.d - name: log mountPath: /var/log/nginx - name: html mountPath: /usr/share/nginx/html volumes: - name: conf persistentVolumeClaim: claimName: nginx-nfs-pvc-confd - name: log persistentVolumeClaim: claimName: nginx-nfs-pvc-log - name: html persistentVolumeClaim: claimName: nginx-nfs-pvc-html
# 执行创建命令
$ kubectl apply -f nginx-nfs-deployment.yaml
deployment.apps/nginx-nfs-deployment created
# 查询 pod
$ kubectl get pod -n nginx-test -o wide
NAME READY STATUS ... IP NODE ... nginx-nfs-deployment-7d5ff8978c-xmrvk 1/1 Running 10.244.1.7 k8s-node01
# 查看 pod 详情
$ kubectl describe pod nginx-nfs-deployment-7d5ff8978c-xmrvk -n nginx-test
Name: nginx-nfs-deployment-7d5ff8978c-xmrvk Namespace: nginx-test Priority: 0 Node: k8s-node01/192.168.0.11 Start Time: Sun, 20 Nov 2022 06:08:28 -0500 Labels: app=nginx-nfs-pod pod-template-hash=7d5ff8978c Annotations: <none> Status: Running IP: 10.244.1.20 ...
# 查看 pod 日志
$ kubectl logs nginx-nfs-deployment-7d5ff8978c-xmrvk -n nginx-test
# 集群内访问 nginx(Pod 的虚拟 IP,外部无法访问)
$ curl http://10.244.0.10/test.html
<p>K8s Nginx - HTML page</p>
注:静态 PV 的每一个目录,都需要在 NFS Server 上创建一个对应的共享目录,每个共享目录本身是由 NFS Server 管理的,NFS client 不能修改这个目录本身,只能根据指定的权限操作目录下的子文件夹和文件。