Skip to main content

k8s实验二 挂载本机目录,用 Nginx 输出网页

本文继续以 nginx 为工具,探究 k8s 的存储挂载方法。

本文的实验运行在 k8s 单节点,尝试创建一个 nginx(版本1.19.0) 应用,挂载本机目录 /tmp/www,至容器目录 /usr/share/nginx/html

预填充网页文件

去到本机目录 /tmp/www,编写网页 index.html

mkdir -p /tmp/www
echo 'Hello, Experiment2' > /tmp/www/index.html

如果是用 docker 实现

这次的实验用 docker 的实现如下:

docker run --rm --name experiment2 -v /tmp/www:/usr/share/nginx/html nginx:1.19.0

此时进入容器,执行 curl 即可看到结果 Hello, Experiment2

docker exec experiment2 curl localhost

接下来用 k8s 部署。

编写配置文件,创建本地持久卷 (PV),指向本机 /tmp/www

持久卷(Persistent Volume, PV)是 k8s 的存储资源,持久卷有很多种类型,比如 Azure Disk, NFS 等,详细查看文档: 持久卷的类型

pv.yaml
# Source: pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example2pv
  labels:
    customlabel: customlabelvalue
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  local:
    path: /tmp/www
  nodeAffinity: # 本地卷必须有这个设定
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node1
pv.yaml 带注释
# Source: pv.yaml
# 解析配置文件的版本,v1即可
apiVersion: v1
# 资源的类型,PersistentVolume 即持久卷资源
kind: PersistentVolume
# 资源的元数据
metadata:
  # 资源的名称
  name: example2pv
  # 资源的标签,标签是自定义的
  labels:
    # 自定义标签 customlabel
    customlabel: customlabelvalue
# 资源描述
spec:
  # 持久卷的容量信息
  capacity:
    # 持久卷能提供的容量,本实验为 10GiB
    storage: 10Gi
  # 持久卷的模式,Filesystem(默认的文件系统),Block(块,没有文件系统)
  volumeMode: Filesystem
  # 持久卷的访问模式
  accessModes:
      # ReadWriteOnce(RWO) 仅一个节点读写
      # ReadOnlyMany(ROX) 多个节点只读
      # ReadWriteMany(RWX) 多个节点读写
    - ReadWriteMany
  # 本地挂载信息
  local:
    # 本地挂载的目录
    path: /tmp/www
  # 本地卷必须配置,表示本地卷应该部署到哪个节点
  nodeAffinity:
    # 固定格式
    required:
      # 用标签过滤出最合适的节点
      nodeSelectorTerms:
          # 过滤条件1: 节点标签 kubernetes.io/hostname 包含 node1
        - matchExpressions:
          - key: kubernetes.io/hostname
            operator: In
            values:
              - node1

这个配置文件描述了一个持久卷资源,能提供容量10GB,卷中的数据来自本机目录 /tmp/www

详细字段说明

  • apiVersion 解析配置文件的版本,v1即可。

  • kind 资源的类型,PersistentVolume 即持久卷资源。

  • metadata 资源的元数据,存储资源的名称等属性。

  • metadata.name 资源的名称。

  • metadata.labels 资源的标签,标签是自定义的,可填可不填,建议加上标签用来标识。

  • metadata.labels.customlabel 示例的自定义标签 customlabel

  • spec 资源描述

  • spec.capacity 持久卷的容量信息

  • spec.capacity.storage 声明持久卷能提供的容量,本实验为 10GiB

  • spec.volumeMode 持久卷的模式,有 Filesystem(文件系统)Block(块),默认 FilesystemBlock 挂载的对象没有文件系统。

    有关卷模式详细查看文档: 卷模式,本文用 Filesystem,所以其实可以不加的。

  • spec.accessModes 持久卷的访问模式,虽然是数组形式,但只能写一个。有以下可用的值:

    • ReadWriteOnce(RWO) 可以被一个节点以读写方式挂载
    • ReadOnlyMany(ROX) 可以被多个节点以只读方式挂载
    • ReadWriteMany(RWX) 可以被多个节点以读写方式挂载

    关于卷访问模式的详细解释: 访问模式

  • spec.local 持久卷的本地挂载信息。

  • spec.local.path 该卷在目标机器上挂载的目录,因为本实验用k8s单节点,所以该卷一定在该机器上挂载。

  • spec.nodeAffinity 这个卷映射的是哪些节点的目录。 本地卷必须配置

  • spec.nodeAffinity.required 固定格式。

  • spec.nodeAffinity.required.nodeSelectorTerms 如何让卷找到合适的节点,以标签过滤。

  • spec.nodeAffinity.required.nodeSelectorTerms.matchExpressions 符合以下条件的节点,都会在该节点部署卷。

  • spec.nodeAffinity.required.nodeSelectorTerms.matchExpressions.key 节点标签名称,kubernetes.io/hostname 表示节点的主机名,用 kubectl get nodes 可以看到。

  • spec.nodeAffinity.required.nodeSelectorTerms.matchExpressions.operator 操作符,本实验用 In 表示符合以下任一值的标签都可通过筛选。

  • spec.nodeAffinity.required.nodeSelectorTerms.matchExpressions.values 标签值。

检查部署状态

写完配置文件,apply 即可。

kubectl apply -f pv.yaml

创建后,检查该卷,可以看到卷的 STATUS 处于 Available 状态,由于暂时没有卷需求,这个卷没有被绑定。

kubectl get pv example2pv

编写配置文件,创建卷需求 (PVC)

pv.yaml
# Source: pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: example2pvc
  labels:
    customlabel: customlabelvalue
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Mi
pv.yaml 带注释
# Source: pvc.yaml
# 解析配置文件的版本,v1即可
apiVersion: v1
# PersistentVolumeClaim 即卷需求资源
kind: PersistentVolumeClaim
# 元数据
metadata:
  # 资源的名称
  name: example2pvc
  # 资源的标签
  labels:
    # 自定义标签 customlabel,带有标签 customlabel: customlabelvalue 的 PV 可以与该 PVC 绑定
    customlabel: customlabelvalue
# 资源描述
spec:
  # 访问模式
  accessModes:
      # 带有 ReadWriteMany 的 PV 可以与该 PVC 绑定
    - ReadWriteMany
  # 需要的存储容量,容量大于 10MiB 的 PV 可以与该 PVC 绑定
  resources:
    requests:
      storage: 10Mi

详细字段说明

  • apiVersion 同上,解析配置文件的版本,v1即可。
  • kind 资源的类型,PersistentVolumeClaim 即卷需求资源。
  • metadata 资源的元数据。
  • metadata.name 资源的名称。
  • metadata.labels 资源的标签,只有与该 PVC 所有的标签都存在并相等的 PV 才能让该 PVC 绑定
  • metadata.labels.customlabel 示例的自定义标签 customlabel注意这个标签和值都与上面部署的 PV 相同
  • spec 资源描述
  • spec.accessModes 该 PVC 要求的访问模式,只有拥有该访问模式的 PV 才能与它绑定。
  • spec.resources 该 PVC 要求的资源信息
  • spec.resources.requests 该 PVC 对于资源的请求
  • spec.resources.storage 声明该 PVC 需要的存储容量,只有能提供大于等于该容量的 PV 才能与它绑定,本实验为 10MiB

检查运行结果

写完配置文件,用 apply 部署之。

kubectl apply -f pvc.yaml

PVC 创建后,因为已经部署了能满足它要求的 PV,调度器会将该 PVC 绑定到 PV 上。

# 执行后,可以看到 PVC 的 STATUS 为 Bound
kubectl get pvc example2pvc
# 执行后,可以看到 PV 的 STATUS 为 Bound,CLAIM 为 example2pvc
kubectl get pv example2pv

PV(卷需求) 和 PVC(持久卷) 的关系

PVC 存在时,调度器会自动寻找符合条件的 PV,并绑定,应用就可以通过引用 PVC 来引用 PV 了。

如果 PVC 存在,但是没有符合条件的 PV,则 PVC 会一直挂起(Pending),此时任何应用都不能引用这个 PVC。

下面做一个小实验,可以随时重新部署,不影响后面的步骤。

解绑 PVC

解绑时,先移除 PVC,再移除 PV。

解绑 PVC,再查看 PV 状态。

kubectl delete pvc example2pvc
kubectl get pv example2pv

解绑 PVC 后,会发现 PV 的 STATUS 变成了 Retain。按官网的介绍,PV 现在是回收保留状态,这个状态的 PV 是不能再次被绑定的,此时如果再部署 PVC,PVC 会因为找不到可用的 PV 而一直处于挂起状态。所以只能先释放 PV,再重新部署 PV

回收被占用过的 PV,并重新部署

移除 PV 资源,本机目录的 PV 移除时不会删除本机目录的文件。

kubectl delete pv examplepv

现在可以重新部署 PV 和 PVC 了,顺序不限,调度器会自动匹配。

编写配置文件,创建 Nginx 应用,绑定 PVC

pod.yaml
# Source: pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: example2pod
spec:
  containers:
    - name: e2container
      image: nginx:1.19.0
      imagePullPolicy: IfNotPresent
      volumeMounts:
        - mountPath: /usr/share/nginx/html
          name: e2volume
  volumes:
    - name: e2volume
      persistentVolumeClaim:
        claimName: example2pvc
pod.yaml 带注释
# Source: pod.yaml
# 解析配置文件的版本,v1即可
apiVersion: v1
# Pod 资源
kind: Pod
# 元数据
metadata:
  # 资源的名称
  name: example2pod
# 资源描述
spec:
  # Pod 运行容器
  containers:
      # 容器名称
    - name: e2container
      # 容器使用的镜像
      image: nginx:1.19.0
      # 镜像拉取策略
      imagePullPolicy: IfNotPresent
      # 挂载临时卷
      volumeMounts:
          # 临时卷 e2volume,挂载到容器目录 /usr/share/nginx/html
        - name: e2volume
          mountPath: /usr/share/nginx/html
  # 临时卷绑定
  volumes:
      # 创建临时卷 e2volume,该卷由 PVC example2pvc 提供
    - name: e2volume
      persistentVolumeClaim:
        claimName: example2pvc

详细字段说明

  • apiVersion 同上,解析配置文件的版本,v1即可。
  • kind 资源的类型,此处创建 Pod 资源。
  • metadata 资源的元数据。
  • metadata.name 资源的名称。
  • spec 资源描述。
  • spec.containers Pod 需要运行的容器。
  • spec.containers.name 容器名称。
  • spec.containers.image 容器使用的镜像。
  • spec.containers.imagePullPolicy 容器镜像拉取策略,IfNotPresent如果本地有,则不拉取,没有时再拉取。
  • spec.containers.volumeMounts 容器挂载的 临时卷
  • spec.containers.volumeMounts.mountPath 容器挂载 临时卷 到容器内部的目录,/usr/share/nginx/html 是 Nginx 默认提供服务的目录。
  • spec.containers.volumeMounts.name 容器挂载 临时卷 的名称。
  • spec.volumes 该 Pod 提供的 临时卷
  • spec.volumes.name 临时卷 的名称。
  • spec.volumes.presistentVolumeClaim 表示这个 临时卷 的数据来自 PVC。
  • spec.volumes.presistentVolumeClaim.claimName 临时卷 绑定的 PVC 名称。

解释到这里,Pod 使用 PVC 是通过 临时卷(Volume) 绑定 PVC,PVC 再绑定 PV,从而使 Pod 可以访问到 PV 的。

起动 Pod

kubectl apply -f pod.yaml

检查运行结果

本次实验没有部署服务,所以没有做端口映射,直接在 Pod 里面访问 localhost 看看吧。

在 Pod 中嵌入进程的方法和 docker 类似。

kubectl exec example2pod curl localhost

执行后获得结果 Hello, Experiment2

清理部署资源

实验结束,总共创建3个资源:PV、PVC、Pod。

kubectl delete pod example2pod
kubectl delete pvc example2pvc
kubectl delete pv  example2pv

小结

相对于 docker,k8s 确实复杂很多,但是 k8s 对资源的高度解耦,可以实现对资源的自由操作,而不是把资源声明和数据都集中在容器中。不过对于小工具一些场合,还是 docker 方便。