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 等,详细查看文档: 持久卷的类型
# 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
# 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(块)
,默认Filesystem
,Block
挂载的对象没有文件系统。有关卷模式详细查看文档: 卷模式,本文用
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)
# Source: pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: example2pvc
labels:
customlabel: customlabelvalue
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Mi
# 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
# 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
# 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 方便。