K8s实践练习6_持久化存储

官网链接

本文将介绍如何配置Pod使用PersistentVolumeClaim(持久卷申领)作为存储

  1. 作为集群管理员创建由物理存储支持的不与任何Pod关联的PersistentVolume(持久卷)。

  2. 以开发人员或者集群用户的角色创建一个PersistentVolumeClaim,它将自动绑定到合适的PersistentVolume。

  3. 创建一个使用刚创建的PersistentVolumeClaim作为存储的Pod。

前置准备

搭建好一套k8s集群,可以参考我写的这篇教程:搭建k8s集群

k8s官方的镜像站在国内是拉不下来的,有几种方法解决:

  1. 在拉取镜像的虚拟机/服务器上科学上网
  2. 配置k8s的镜像源,目前国内只有阿里云支持改版后的k8s镜像源(registry.k8s.io)。
  3. 需要拉取镜像的时候,指定拉取策略为本地拉取(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.ioregistry.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,更安全更精细。

  1. 移除k8s1控制平面上的NoSchedule污点(仅学习测试环境,生产环境不建议,因为控制平面节点上有很多关键组件,如apiserver、etcd、controller-manager 等,资源压力大会影响集群稳定性)
  2. 让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
网站总访客数:Loading
网站总访问量:Loading
使用 Hugo 构建
主题 StackJimmy 设计