官网链接
本文将介绍如何配置Pod使用PersistentVolumeClaim(持久卷申领)作为存储
-
作为集群管理员创建由物理存储支持的不与任何Pod关联的PersistentVolume(持久卷)。
-
以开发人员或者集群用户的角色创建一个PersistentVolumeClaim,它将自动绑定到合适的PersistentVolume。
-
创建一个使用刚创建的PersistentVolumeClaim作为存储的Pod。
前置准备
搭建好一套k8s集群,可以参考我写的这篇教程:搭建k8s集群
k8s官方的镜像站在国内是拉不下来的,有几种方法解决:
- 在拉取镜像的虚拟机/服务器上科学上网
- 配置k8s的镜像源,目前国内只有阿里云支持改版后的k8s镜像源(registry.k8s.io)。
- 需要拉取镜像的时候,指定拉取策略为本地拉取(
imagePullPolicy:Never),每次需要拉取镜像前都手动拉取/上传一份镜像到服务器上再导入镜像
这里给出阿里云镜像源的配置教程:
旧版的k8s直接修改/etc/containerd/config.toml里的mirror信息,添加上阿里云的镜像站就行。但是新版的不支持inline或者说暂时兼容,未来不支持。所以这里就只给出新版k8s镜像源配置教程。
修改/etc/containerd/config.yaml,填入下列信息(如果你已经有了config.yaml且这个配置文件是从containerd默认配置里生成的,那直接备份,然后使用下面的内容)。sudo vim /etc/containerd/config.yaml
1
2
3
4
5
6
7
8
9
10
|
version = 2
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
runtime_type = "io.containerd.runc.v2"
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
[plugins."io.containerd.grpc.v1.cri".registry]
config_path = "/etc/containerd/certs.d"
|
创建/etc/containerd/certs.d目录,在这个目录填入docker.io和registry.k8s.io的镜像源。
注意:k8s里修改镜像源之后,使用kubectl describe pod <pod_name> 查看时还是显示的docker.io和registry.k8s.io。配置镜像源只物理修改从哪里修改,不改镜像拉取的逻辑源。所以改好镜像源之后也不太好验证成功,随便拉个镜像sudo crictl pull nginx:1.14.2,能拉下来就是成了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
# Docker Hub 加速
sudo mkdir -p /etc/containerd/certs.d/docker.io
sudo tee /etc/containerd/certs.d/docker.io/hosts.toml << 'EOF'
server = "https://registry-1.docker.io"
[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
# K8s 镜像加速
sudo mkdir -p /etc/containerd/certs.d/registry.k8s.io
sudo tee /etc/containerd/certs.d/registry.k8s.io/hosts.toml << 'EOF'
server = "https://registry.k8s.io"
[host."https://registry.cn-hangzhou.aliyuncs.com/google_containers"]
capabilities = ["pull", "resolve"]
override_path = true
EOF
|
到这里,镜像源就配置好了,如果不出意外,文件目录应该是下面这样:
1
2
3
4
5
6
7
|
rust@k8s1:/etc/containerd$ ll
总计 28
drwxr-xr-x 3 root root 4096 2月 4 10:31 ./
drwxr-xr-x 144 root root 12288 2月 2 17:01 ../
drwxr-xr-x 4 root root 4096 2月 2 16:44 certs.d/
-rw-r--r-- 1 root root 423 2月 2 19:02 config.toml
-rw-r--r-- 1 root root 886 12月 19 02:48 config.toml.dpkg-dist
|
修改完配置文件后需要重启containerd:
1
2
|
sudo systemctl restart containerd
sudo systemctl status containerd
|
创建index.html
1
2
3
4
5
6
|
# 创建文件夹
sudo mkdir /mnt/data
# 创建index.html并写入数据
sudo sh -c "echo 'Hello from Kubernetes storage' > /mnt/data/index.html"
# 验证查看数据
cat /mnt/data/index.html
|
创建PersistentVolume
本练习将创建一个 hostPath 类型的 PersistentVolume。 Kubernetes 支持用于在单节点集群上开发和测试的 hostPath 类型的 PersistentVolume。 hostPath 类型的 PersistentVolume 使用节点上的文件或目录来模拟网络附加存储。
hostPath类型的PV会无视调度,直接绑定到主机上。如果 Pod 漂移到了另一个节点,它依然会尝试去挂载那个路径。
即使 Pod 被删除,Node 上的文件依然存在(除非手动清理)。这有时会导致“幽灵数据”问题(新 Pod 跑过来读取到了旧 Pod 的残留数据)。
风险较高。Pod 可以直接访问 Node 的系统目录(如 /var/run/docker.sock),如果 Pod 被攻破,黑客可以直接控制宿主机。
生产环境一般禁用或者限制使用hostPath的PV。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
# 与local卷的关键区别,local类型的没有hostPath关键字。而是用nodeAffinity(节点亲和性)绑定节点
hostPath:
path: "/mnt/data" # 宿主机上的物理路径
|
创建本地持久卷。
1
2
|
vim pv-volume.yaml
kubectl apply -f pv-volume.yaml
|
此配置文件指定卷位于集群节点上的/mnt/data路径。其配置还指定了卷的容量大小为 10 GB,访问模式为ReadWriteOnce, 这意味着该卷可以被单个节点以读写方式挂载。此配置文件还在 PersistentVolume 中定义了StorageClass的名称为 manual。 它将用于将PersistentVolumeClaim的请求绑定到此 PersistentVolume。
说明:为了简化,本示例采用了ReadWriteOnce访问模式。然而对于生产环境,Kubernetes 项目建议改用 ReadWriteOncePod 访问模式。
K8s Access Mode(访问模式)
| 名称 |
简称 |
介绍 |
适用场景 |
| ReadWriteOnce |
RWO |
卷可以被单个节点上的单个Pod以读写方式挂载 |
数据库、单实例有状态应用 |
| ReadWriteOncePod |
RWOP |
V1.29+稳定,卷可以被整个集群上的单个Pod以读写方式挂载 |
需要强数据一致性保障、不支持并发读写的有状态应用,如云盘 |
| ReadOnlyMany |
ROX |
卷可以被多个节点以只读方式挂载 |
共享配置文件、静态Web内容、只读数据集 |
| ReadWriteMany |
RWX |
卷可以被多个节点以读写方式挂载 |
多副本Web应用共享上传目录、分布式缓存共享存储、日志聚合 |
查看 PersistentVolume 的信息,该持久卷状态为可用,代表还未与PVC(持久卷申领)绑定。
1
2
3
4
5
|
kubectl get pv task-pv-volume
rust@k8s1:~$ kubectl get pv task-pv-volume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
task-pv-volume 10Gi RWO Retain Available manual <unset> 7s
|
创建 PersistentVolumeClaim
Pod 使用 PersistentVolumeClaim来请求物理存储。 下面创建一个 PersistentVolumeClaim,它请求至少 3 GB 容量的卷,该卷一次最多可以为一个节点提供读写访问。
1
2
|
vim pv-claim.yaml
kubectl apply -f pv-claim.yaml
|
1
2
3
4
5
6
7
8
9
10
11
|
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pv-claim
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
|
创建 PersistentVolumeClaim 之后,Kubernetes 控制平面将查找满足申领要求的 PersistentVolume。 如果控制平面找到具有相同 StorageClass 的适当的 PersistentVolume, 则将 PersistentVolumeClaim 绑定到该 PersistentVolume 上。
1
2
3
4
5
6
|
rust@k8s1:~$ kubectl get pv task-pv-volume
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
task-pv-volume 10Gi RWO Retain Bound default/task-pv-claim manual <unset> 6m18s
rust@k8s1:~$ kubectl get pvc task-pv-claim
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
task-pv-claim Bound task-pv-volume 10Gi RWO manual <unset> 82s
|
创建Pod
创建一个使用刚创建的PersistentVolumeClaim作为存储卷的Pod。对 Pod 而言,PersistentVolumeClaim 就是一个存储卷。普通用户一般没有PV的权限,只有PVC的使用权限。而PVC绑定的PV的制备细节(动态或者静态制备,制备参数)对用户透明。
k8s创建pod会先进行调度,控制平面上有控制平面的污点(.spec.taints),所以创建的普通pod不会调度到控制平面上执行。
这也是为什么官网教程里用的是单节点集群。解决方法有两种,这里选择方案2,更安全更精细。
- 移除k8s1控制平面上的NoSchedule污点(仅学习测试环境,生产环境不建议,因为控制平面节点上有很多关键组件,如apiserver、etcd、controller-manager 等,资源压力大会影响集群稳定性)
- 让pod"不普通",给pod添加上容忍度(tolerations)和节点选择器(nodeSelector)
1
2
|
vim pv-pod.yaml
kubectl apply -f pv-pod.yaml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
# 1. 让这个 Pod 能容忍控制平面节点的 NoSchedule 污点
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
# 2. 强制把 Pod 调度到 k8s1 这个节点
nodeSelector:
kubernetes.io/hostname: k8s1
volumes:
- name: task-pv-storage
persistentVolumeClaim:
claimName: task-pv-claim
containers:
- name: task-pv-container
image: nginx:1.25
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: task-pv-storage
|
验证查看pod状态:
1
2
3
4
5
6
7
8
9
|
kubectl get pod task-pv-pod
# 打开nginx容器的终端,验证 Nginx 是否正在从 hostPath 卷提供 index.html
kubectl exec -it task-pv-pod -- curl http://localhost/
rust@k8s1:~$ kubectl get pod task-pv-pod
NAME READY STATUS RESTARTS AGE
task-pv-pod 1/1 Running 1 (2m25s ago) 112m
rust@k8s1:~$ kubectl exec -it task-pv-pod -- curl http://localhost/
Hello from Kubernetes storage
|
清理现场,删除pod
1
|
kubectl delete pod task-pv-pod
|
在两个位置挂载一个卷
同一个 volumes.name(同一个卷对象);
被两个 volumeMounts 各自引用,指定不同的 mountPath 和 subPath。
更直白地讲就是PVC对应的PV的文件夹挂载到容器里的两个目录或文件。
创建/mnt/data/nginx.conf和/mnt/data/html。将index.html重复利用一下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
sudo mkdir /mnt/data/html
sudo mv /mnt/data/index.html /mnt/data/html
sudo tee /mnt/data/nginx.conf > /dev/null << 'EOF'
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}
}
EOF
|
创建一个 Pod,使用已有的 PersistentVolume 和 PersistentVolumeClaim。
不过,这个 Pod 只将特定的文件 nginx.conf 和目录 html 挂载到容器中。
1
2
|
vim pv-duplicate.yaml
kubectl apply -f pv-duplicate.yaml
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
apiVersion: v1
kind: Pod
metadata:
name: test
spec:
# 1. 让这个 Pod 能容忍控制平面节点的 NoSchedule 污点
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
# 2. 强制把 Pod 调度到 k8s1 这个节点
nodeSelector:
kubernetes.io/hostname: k8s1
containers:
- name: test
image: nginx:1.25
volumeMounts:
# 网站数据挂载
- name: config
mountPath: /usr/share/nginx/html
subPath: html
# Nginx 配置挂载
- name: config
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: config
persistentVolumeClaim:
claimName: task-pv-claim
|
subPath:此字段允许将挂载的 PersistentVolume 中的特定文件或目录暴露到容器内的不同位置。在本例中:
subPath: html挂载 html 目录。
subPath: nginx.conf挂载一个特定文件 nginx.conf。
nginx 容器中会挂载两个路径:
- /usr/share/nginx/html:用于静态网站
- /etc/nginx/nginx.conf:用于默认配置
验证nginx容器运行情况,验证 nginx 是否从 hostPath 卷中加载了 nginx.conf 文件(keepalived_timeout改为了65)
1
2
3
4
5
6
7
|
kubectl exec -it test -- curl http://localhost/
kubectl exec -it test -- cat /etc/nginx/nginx.conf | grep keepalive_timeout
rust@k8s1:~$ kubectl exec -it test -- curl http://localhost/
Hello from Kubernetes storage
rust@k8s1:~$ kubectl exec -it test -- cat /etc/nginx/nginx.conf | grep keepalive_timeout
keepalive_timeout 65;
|
清理现场
1
2
3
4
5
|
kubectl delete pod test
kubectl delete pvc task-pv-claim
kubectl delete pv task-pv-volume
sudo rm -rf /mnt
|