一、ConfigMap
ConfigMap
功能在 Kubernetes1.2
版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API
给我们提供了向容器中注入配置信息的机制,ConfigMap
可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON
二进制大对象
ConfigMap 的创建
1. 资源清单创建
-
创建 ConfigMap 的资源清单:
apiVersion: v1 # 版本,通过 kubectl explain cm 可以查看 kind: ConfigMap metadata: name: special-config # ConfigMap 的名字 namespace: default # 名称空间 data: # key: value 结构,配置数据 special.how: very special.type: charm
-
创建
kubectl apply -f comfigmap.yaml
2. 使用目录创建
-
创建 /root/k8s/yaml/configmap/game.properties 文件:
enemies=aliens lives=3 enemies.cheat=true enemies.cheat.level=noGoodRotten secret.code.passphrase=UUDDLRLRBABAS secret.code.allowed=true secret.code.lives=30
-
创建 /root/k8s/yaml/configmap/ui.properties 文件:
color.good=purple color.bad=yellow allow.textmode=true how.nice.to.look=fairlyNice
-
创建 configmap ,
--from-file
指定在目录下的所有文件都会被用在ConfigMap
里面创建一个键值对,键的名字就是文件名,值就是文件的内容kubectl create configmap game-config --from-file=../configmap/
-
查看创建的
configmap
(可简写为cm
):$ kubectl get cm NAME DATA AGE game-config 2 6m40s # 查看详细信息 kubectl get cm game-config -o yaml kubectl describe cm game-config
3. 使用文件创建
通过 --from-file
参数只要指定为一个文件就可以从单个文件中创建 ConfigMap
--from-file
这个参数可以使用多次,你可以使用两次分别指定上个实例中的那两个配置文件,效果就跟指定整个目录是一样的
kubectl create configmap game-config-2 --fromfile=game.properties
kubectl get configmaps game-config-2 -o yaml
4. 使用字面值创建
使用文字值创建,利用 --from-literal
参数传递配置信息,该参数可以使用多次,格式如下
kubectl create configmap special-config --from-literal=special.how=very --fromliteral=special.type=charm
kubectl get configmaps special-config -o yaml
Pod 中使用 ConfigMap
1. 使用 ConfigMap 来替代环境变量
-
创建两个
ConfigMap
(configmap.yaml):apiVersion: v1 kind: ConfigMap metadata: name: special-config namespace: default data: special.how: very special.type: charm --- apiVersion: v1 kind: ConfigMap metadata: name: env-config namespace: default data: log_level: INFO
-
创建 Pod,并引入
apiVersion: v1 kind: Pod metadata: name: dapi-test-pod spec: containers: - name: test-container image: wangyanglinux/myapp:v1 command: [ "/bin/sh", "-c", "env" ] # 打印 env env: # 从 ConfigMap 中选择读取的键,并起个别名 - name: SPECIAL_LEVEL_KEY # 键别名,在这值应该是 very valueFrom: configMapKeyRef: name: special-config # ComfigMap 的名称 key: special.how # 上句指定 ConfigMap 中的键名 - name: SPECIAL_TYPE_KEY # 键别名,在这值应该是 charm valueFrom: configMapKeyRef: name: special-config # ComfigMap 的名称 key: special.type # 上句指定 ConfigMap 中的键名 envFrom: # 直接从 ConfigMap 中读取全部配置 - configMapRef: name: env-config # ComfigMap 的名称 restartPolicy: Never
-
查看日志,可以看到 ConfigMap 中的配置已经注入到了容器中
2. 使用 ConfigMap 设置命令行参数
-
创建 ConfigMap
apiVersion: v1 kind: ConfigMap metadata: name: special-config namespace: default data: special.how: very special.type: charm
-
创建 Pod
apiVersion: v1 kind: Pod metadata: name: dapi-test-pod spec: containers: - name: test-container image: wangyanglinux/myapp:v1 command: [ "/bin/sh", "-c", "echo $(SPECIAL_LEVEL_KEY) $(SPECIAL_TYPE_KEY)" ] #可以调整启动Pod时的命令 env: # 从 ConfigMap 中选择读取的键,并起个别名 - name: SPECIAL_LEVEL_KEY # 键别名,在这值应该是 very valueFrom: configMapKeyRef: name: special-config # ComfigMap 的名称 key: special.how # 上句指定 ConfigMap 中的键名 - name: SPECIAL_TYPE_KEY # 键别名,在这值应该是 charm valueFrom: configMapKeyRef: name: special-config # ComfigMap 的名称 key: special.type restartPolicy: Never
-
查看日志
$ kubectl logs dapi-test-pod very charm
3. 通过数据卷插件使用ConfigMap
通过 Volume
方式挂载,ConfigMap
中的键名就是 文件名,键值就是 文件内容
-
创建
ConfigMap
apiVersion: v1 kind: ConfigMap metadata: name: special-config namespace: default data: special.how: very special.type: charm
-
创建
Pod
apiVersion: v1 kind: Pod metadata: name: dapi-test-pod spec: containers: - name: test-container image: wangyanglinux/myapp:v1 command: ["/bin/sh", "-c", "cat /etc/config/special.how"] # 打印挂载目录下的文件内容 volumeMounts: # volume 挂载 - name: config-volume # 挂载下面指定的 volume mountPath: /etc/config # 挂载到的目录(容器内路径,该目录下,文件名就里键名,文件内容就是键值) volumes: - name: config-volume # volume 名称 configMap: # 来自 ConfigMap name: special-config # ConfigMap 名字 restartPolicy: Never
-
查看日志
$ kubectl logs dapi-test-pod very
4. ConfigMap 的热更新
-
创建一个
ConfigMap
和Deployment
:apiVersion: v1 kind: ConfigMap metadata: name: log-config namespace: default data: log_level: INFO --- apiVersion: extensions/v1beta1 kind: Deployment metadata: name: my-nginx spec: replicas: 1 template: metadata: labels: run: my-nginx spec: containers: - name: my-nginx image: wangyanglinux/myapp:v1 ports: - containerPort: 80 volumeMounts: # 这块儿不懂看上一节《通过数据卷插件使用ConfigMap》 - name: config-volume mountPath: /etc/config # 容器内这个目录下会有 log_level 这个文件,内容为 INFO volumes: - name: config-volume configMap: name: log-config
-
查看
/etc/config/log_level
文件的内容$ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level INFO
-
修改 ConfigMap
kubectl edit configmap log-config
-
稍微等一会儿,再次查看
/etc/config/log_level
文件的内容,可以看到,Pod 中的配置也改了$ kubectl exec my-nginx-c484b98b4-sbls9 -it -- cat /etc/config/log_level DEBUG
- 注意:更新 ConfigMap 后:
- 使用该
ConfigMap
挂载的Env
不会同步更新 - 使用该
ConfigMap
挂载的Volume
中的数据需要一段时间(实测大概10秒)才能同步更新
- 使用该
-
让
Pod
滚动更新ConfigMap
更新后,并不会让相应的文件重载。例如,Nginx
在启动时,会加载一次配置文件(配置文件中有ConfigMap
的相关参数),加载完成后,无论这个配置文件再怎么变化,Nginx
都不会再加载它。因此需要ConfigMap
更新后滚动更新Pod
。- 可以通过修改
pod annotations
的方式强制触发滚动更新 - 这里我们在
.spec.template.metadata.annotations
中添加version/config
,每次通过修改version/config
的时间来触发滚动更新
kubectl patch deployment my-nginx --patch \ '{"spec": {"template": {"metadata": {"annotations":{"version/config": "20201110" }}}}}'
- 可以通过修改
二、Secret
Secret
解决了密码、token
、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec
中。Secret
可以以 Volume
或者环境变量的方式使用
Secret
有三种类型:
Service Account
:用来访问Kubernetes API
,由Kubernetes
自动创建,并且会自动挂载到Pod
的/run/secrets/kubernetes.io/serviceaccount
目录中Opaque
:base64
编码格式的Secret
,用来存储密码、密钥等。加密程度不高kubernetes.io/dockerconfigjson
:用来存储私有docker registry
的认证信息
1. Service Account(不常用)
Service Account
用来访问 Kubernetes API
,由 Kubernetes
自动创建,并且会自动挂载到 Pod
的 /run/secrets/kubernetes.io/serviceaccount
目录中
# 1. 随便找一个需要访问 Kubernetes API 的 Pod
$ kubectl get pod -n kube-system
NAME READY STATUS RESTARTS AGE
kube-proxy-2pqkk 1/1 Running 6 40d
# 2. 查看该 Pod 中 /run/secrets/kubernetes.io/serviceaccount 目录下的文件
$ kubectl exec kube-proxy-2pqkk -n kube-system -it -- ls /run/secrets/kubernetes.io/serviceaccount
ca.crt:访问 API Service 时的证书
namespace:名称空间
token:认证的密钥信息
2. Opaque Secret
Opaque
类型的数据是一个 map
类型,要求 value
是 base64
编码格式:
(1)创建 Opaque Secret
-
给用户名和密码用 base64 加密
$ echo -n admin | base64 YWRtaW4= $ echo -n 123 | base64 MTIz
base64 解码:
$ echo -n YWRtaW4= | base64 -d admin
-
使用加密后的用户名和密码创建
Secret
apiVersion: v1 # kubectl explain secret 查看 kind: Secret metadata: name: mysecret # Secret 名称 type: Opaque # Secret 的类型 data: password: MTIz # 密码 username: YWRtaW4= # 用户名
-
查看
Secret
$ kubectl get secret NAME TYPE DATA AGE default-token-fm46c kubernetes.io/service-account-token 3 40d mysecret Opaque 2 12s
- default-token-xxxxx:k8s 默认会在每个名称空间下都创建一个,用于 Pod 的挂载
(2)将 Secret 挂载到 Volume 中
-
创建 Pod
apiVersion: v1 kind: Pod metadata: labels: name: secret-test name: secret-test spec: volumes: # 创建一个卷 - name: secrets # 卷名 secret: # 卷使用的方案 secretName: mysecret # 来自于上一节创建的 mysecret containers: - image: wangyanglinux/myapp:v1 name: db volumeMounts: # 卷挂载 - name: secrets # 挂载的是上面声明的 secrets mountPath: "/etc/secrets" # 挂载的目录(容器内目录) readOnly: true # 只读
-
查看
# Opaque Secret 中的用户名和密码都已经挂载进来了 $ kubectl exec secret-test -it -- ls /etc/secrets password username # 查看内容,发现内容已经自动被解密 $ kubectl exec secret-test -it -- cat /etc/secrets/password 123 $ kubectl exec secret-test -it -- cat /etc/secrets/username admin
(3)将 Secret 导出到环境变量中
-
创建 Deployment:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: pod-deployment spec: replicas: 2 template: metadata: labels: app: pod-deployment spec: containers: - name: pod-1 image: wangyanglinux/myapp:v1 ports: - containerPort: 80 env: - name: TEST_USER # 环境变量名 valueFrom: secretKeyRef: # 从 Secret 中获取 name: mysecret # Secret 的名字 key: username # Secret 中的键名 - name: TEST_PASSWORD # 环境变量名 valueFrom: secretKeyRef: # 从 Secret 中获取 name: mysecret # Secret 的名字 key: password # Secret 中的键名(相比 configmap,Secret 在这儿不需要使用明文,稍微安全一点)
-
查看环境变量
# 进入容器 $ kubectl exec pod-deployment-747f78bc67-2w9wk -it -- /bin/sh # 查看环境变量 $ echo $TEST_USER admin $ echo $TEST_PASSWORD 123
3. kubernetes.io/dockerconfigjson
使用 Kuberctl
创建 docker registry
认证的 secret
# kubectl create secret docker-registry \ # 创建 Secret 的类型
# myregistrykey \ # Secret 的名称
# --docker-server=hub.zyx.com \ # docker server 的地址
# --docker-username=admin \ # docker 用户名
# --docker-password=Harbor12345 \ # docker 密码
# --docker-email=aa@qq.com # docker 邮箱
kubectl create secret docker-registry \
myregistrykey \
--docker-server=hub.zyx.com \
--docker-username=admin \
--docker-password=Harbor12345 \
--docker-email=aa@qq.com
在创建 Pod
的时候,通过 imagePullSecrets
来引用刚创建的 myregistrykey
,来拉取私有仓库的镜像
apiVersion: v1
kind: Pod
metadata:
name: foo
spec:
containers:
- name: foo
image: hub.zyx.com/zyx/myapp:v1
imagePullSecrets: # 当去私有仓库拉取时的认证信息
- name: myregistrykey # 认证信息,上一步创建的 docker registry
三、Volume
容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题。
-
首先,当容器崩溃时,
kubelet
会重启它,但是容器中的文件将丢失——容器以干净的状态(镜像最初的状态)重新启动。 -
其次,在
Pod
中同时运行多个容器时,这些容器之间通常需要共享文件。
Kubernetes
中的 Volume
抽象就很好的解决了这些问题
Kubernetes
中的卷有明确的寿命 —— 与封装它的 Pod
相同。所以,卷的生命比 Pod 中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当 Pod 不再存在时,卷也将不复存在。也许更重要的是,Kubernetes
支持多种类型的卷,Pod
可以同时使用任意数量的卷
Kubernetes 支持以下类型的卷:
awsElasticBlockStore
azureDisk
azureFile
cephfs
csi
downwardAPI
emptyDir
fc
flocker
gcePersistentDisk
gitRepo
glusterfs
hostPath
iscsi
local
nfs
persistentVolumeClaim
projected
portworxVolume
quobyte
rbd
scaleIO
secret
storageos
vsphereVolume
1. emptyDir
当 Pod
被分配给节点时,首先创建 emptyDir
卷,并且只要该 Pod
在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。该卷可以挂载到 Pod
每个容器中的相同或不同路径上,并且每个容器都可以读取和写入 emptyDir
卷中的文件。当出于任何原因从节点中删除 Pod
时, emptyDir
中的数据将被永久删除
注意:容器崩溃不会从节点中移除 pod,因此 emptyDir
卷中的数据在容器时是安全的
emptyDir
的用法有:
- 暂存空间,例如用于基于磁盘的合并排序
- 用作长时间计算崩溃恢复时的检查点
Web
服务器容器提供数据时,保存内容管理器容器提取的文件
-
创建一个
Pod
,里面有两个容器,都挂载同一个emptyDir
apiVersion: v1 kind: Pod metadata: name: test-pd spec: containers: - image: wangyanglinux/myapp:v1 name: test-container # 容器名 volumeMounts: - mountPath: /cache # 挂载到容器的哪个目录下 name: cache-volume # 通过哪个 volume 挂载 - name: test2-container image: busybox args: # 运行命令,睡眠,防止容器退出 - /bin/sh - -c - sleep 6000s volumeMounts: - mountPath: /test # 挂载到容器的哪个目录下 name: cache-volume # 通过哪个 volume 挂载 volumes: - name: cache-volume # volume 名称 emptyDir: {} # volume 类型
-
可以看到,两个容器都能读取到
emptyDir
中的数据# 在容器2的 /test 目录下,创建一个 index.html 文件 $ kubectl exec test-pd -c test2-container -it -- touch /test/index.html # 查看容器1的 /cache 目录 $ kubectl exec test-pd -c test-container -it -- ls /cache index.html
2. hostPath
hostPath
卷将主机节点的文件系统中的文件或目录挂载到集群中
hostPath
的用途如下:
- 运行需要访问
Docker
内部的容器;使用/var/lib/docker
的hostPath
- 在容器中运行
cAdvisor
;使用/dev/cgroups
的hostPath
注意事项:
- 由于每个节点上的文件都不同,具有相同配置(例如从
podTemplate
创建的)的pod
在不同节点上的行为可能会有所不同。 - 当
Kubernetes
按照计划添加资源感知调度时,将无法考虑hostPath
使用的资源 - 在底层主机上创建的文件或目录只能由
root
写入。您需要在特权容器中以root
身份运行进程,或修改主机上的文件权限以便写入hostPath
卷
(1)type
的值
值 | 行为 |
---|---|
空字符串(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate | 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。 |
Directory | 给定的路径下必须存在目录 |
FileOrCreate | 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。 |
File | 给定的路径下必须存在文件 |
Socket | 给定的路径下必须存在 UNIX 套接字 |
CharDevice | 给定的路径下必须存在字符设备 |
BlockDevice | 给定的路径下必须存在块设备 |
(2)示例
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: wangyanglinux/myapp:v1
name: test-container
volumeMounts:
- mountPath: /test-pd # 挂载到的容器内路径
name: test-volume # 选择下面声明的 volume 进行挂载
volumes:
- name: test-volume # volume名称
hostPath: # volume 类型
path: /data # 本机的 /data 目录(Pod 运行在集群的哪个节点,就是哪个节点上的 /data 目录)
type: Directory # 类型(如果不存在 /data 会报错)
注意:要确保每个 k8s 节点上都存在 /data
这个目录
四、PV/PVC
1. 概念
-
PersistentVolume
(PV):是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。
PV
是Volume
之类的卷插件,但具有独立于使用PV
的Pod
的生命周期。此API
对象包含存储实现的细节,即NFS
、iSCSI
或特定于云供应商的存储系统 -
PersistentVolumeClaim
(PVC):是用户存储的请求。它与
Pod
相似。Pod
消耗节点资源,PVC
消耗PV
资源。Pod
可以请求特定级别的资源(CPU 和内存)。PVC
可以声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)
生命周期
PV的分类
-
静态 pv:
集群管理员创建一些 PV。它们带有可供群集用户使用的实际存储的细节,一般保存访问至后端存储的细节(怎么连接,地址多少…)。它们存在于 Kubernetes API 中,可用于消费
-
动态 pv:
当管理员创建的静态 PV 都不匹配用户的
PersistentVolumeClaim
时,集群可能会尝试动态地为 PVC 创建卷。此配置基于StorageClasses
:PVC 必须请求 [存储类],并且管理员必须创建并配置该类才能进行动态创建。声明该类为""
可以有效地禁用其动态配置要启用基于存储级别的动态存储配置,集群管理员需要启用 API server 上的
DefaultStorageClass
[准入控制器]。例如,通过确保DefaultStorageClass
位于 API server 组件的--admission-control
标志,使用逗号分隔的有序值列表中,可以完成此操作
绑定:
master
中的控制环路监视新的 PVC
,寻找匹配的 PV
(如果可能),并将它们绑定在一起。如果为新的 PVC
动态调配 PV
,则该环路将始终将该 PV
绑定到 PVC
。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。
一旦 PV 和 PVC 绑定后, PersistentVolumeClaim
绑定是排他性的,不管它们是如何绑定的。 PVC
跟 PV
绑定是一对一的映射
持久化卷声明的保护
PVC
保护的目的是确保由 Pod 正在使用的 PVC
不会从系统中移除,因为如果被移除的话可能会导致数据丢失。
注意:当 Pod 状态为 Pending
并且 Pod 已经分配给节点或者 Pod 为 Running
状态时,PVC 处于活动状态。
当启用PVC 保护 alpha
功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除。PVC 的删除将被推迟,直到 PVC 不再被任何 pod 使用。
2. PV 一些概念
(1)PV 的类型(插件)
PersistentVolume
类型以插件形式实现。Kubernetes
目前支持以下插件类型:
- GCEPersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC (Fibre Channel)
- FlexVolume、Flocker、
NFS
、iSCS、RBD(Ceph Block Device)、CephFS - Cinder(OpenStack block storage)、
Glusterfs
、VsphereVolume、Quobyte、Volumes HostPath
、VMware、Photon、Portworx、Volumes、ScaleIO、Volumes、StorageOS
(2)访问模式
PersistentVolume
可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个 PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能以只读方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式
ReadWriteOnce
——该卷可以被单个节点以读/写模式挂载ReadOnlyMany
——该卷可以被多个节点以只读模式挂载ReadWriteMany
——该卷可以被多个节点以读/写模式挂载
在命令行中,访问模式缩写为:RWO
:ReadWriteOnceROX
:ReadOnlyManyRWX
:ReadWriteMany
注意:一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。例如,GCEPersistentDisk
可以由单个节点作为 ReadWriteOnce
模式挂载,或由多个节点以 ReadWriteMany
模式挂载,但不能同时挂载
Volume 插件 | ReadWriteOnce | ReadOnlyMany | ReadWriteMany |
---|---|---|---|
AWSElasticBlockStoreAWSElasticBlockStore | ✓ | - | - |
AzureFile | ✓ | ✓ | ✓ |
AzureDisk | ✓ | - | - |
CephFS | ✓ | ✓ | ✓ |
Cinder | ✓ | - | - |
FC | ✓ | ✓ | - |
FlexVolume | ✓ | ✓ | - |
Flocker | ✓ | - | - |
GCEPersistentDisk | ✓ | ✓ | - |
Glusterfs | ✓ | ✓ | ✓ |
HostPath | ✓ | - | - |
iSCSI | ✓ | ✓ | - |
PhotonPersistentDisk | ✓ | - | - |
Quobyte | ✓ | ✓ | ✓ |
NFS | ✓ | ✓ | ✓ |
RBD | ✓ | ✓ | - |
VsphereVolume | ✓ | - | - |
PortworxVolume | ✓ | - | ✓ |
ScaleIO | ✓ | ✓ | - |
StorageOS | ✓ | - | - |
(3)回收策略
Retain
(保留):手动回收Recycle
(回收):基本擦除( 相当于执行了rm -rf /thevolume/*
)Delete
(删除):关联的存储资产(例如 AWS EBS、GCE PD、Azure Disk 和 OpenStack Cinder 卷)将被删除
当前,只有 NFS
和 HostPath
支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略
(4)状态
卷可以处于以下的某种状态:
Available
(可用):一块空闲资源还没有被任何声明绑定Bound
(已绑定):卷已经被声明绑定Released
(已释放):声明被删除,但是资源还未被集群重新声明Failed
(失败):该卷的自动回收失败
命令行会显示绑定到 PV 的 PVC 的名称
(5)模板
apiVersion: v1
kind: PersistentVolume # 类型:PV
metadata:
name: pv0003 # 名称
spec:
capacity:
storage: 5Gi # 卷的大小:5G
volumeMode: Filesystem # 文件类型
accessModes: # 访问策略
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle # 回收策略
storageClassName: slow # 存储类的一个名称
mountOptions: # 其它说明,也可以不指定,让他自己做判断
- hard
- nfsvers=4.1
nfs:
path: /tmp # 挂载到哪个目录下
server: 172.17.0.2 # 挂载到哪个服务器
3. NFS 持久化示例
(1)安装 NFS
-
新建一台虚拟机,安装 NFS,我的虚拟机 IP 为 192.168.66.20
yum install -y nfs-common nfs-utils rpcbind mkdir /nfs chmod 777 /nfs chown nfsnobody /nfs vim /etc/exports # 文件中写入以下内容 /nfs *(rw,no_root_squash,no_all_squash,sync) systemctl start rpcbind systemctl start nfs
-
在 k8s 每个节点中安装 NFS 客户端:
# 安装依赖 $ yum -y install nfs-utils rpcbind
-
NFS 的一些操作
# 查看共享目录 $ showmount -e 192.168.66.20 Export list for 192.168.66.20: /nfs * # 共享目录与本地目录挂载 $ mkdir ~/test $ mount -t nfs 192.168.66.20:/nfs ~/test # 解除挂载 $ umount ~/test
(2)创建 PV 和 StatefulSet
-
创建一个 pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: nfspv1 spec: capacity: # 容量 storage: 10Gi accessModes: # 访问模式 - ReadWriteOnce persistentVolumeReclaimPolicy: Retain # 回收策略 storageClassName: nfs nfs: # nfs服务器配置 path: /nfs # 目录 server: 192.168.66.20 # IP
-
创建 PV
$ kubectl apply -f nfspv.yaml persistentvolume/nfspv1 created $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfspv1 10Gi RWO Retain Available nfs 7s
-
创建 Service 和 StatefulSet
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx --- apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: selector: matchLabels: app: nginx serviceName: "nginx" # 指定 Service 名称(上面创建的,一定要是个无头服务) replicas: 3 # 副本数 template: metadata: labels: app: nginx spec: containers: # 容器信息 - name: nginx image: wangyanglinux/myapp:v2 ports: - containerPort: 80 # 释放的端口 name: web # 端口名字 volumeMounts: # 挂载 - name: www mountPath: /usr/share/nginx/html # 容器内目录 volumeClaimTemplates: # 卷请求声明模板(pvc模板) - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] # 指定要请求的卷的访问模式 storageClassName: "nfs" # 指定要请求的卷的类名,只有与 PV 中的storageClassName 相同时,才会匹配 resources: requests: storage: 1Gi # 指定要请求的卷大小必须满足 1G
-
因为前面只创建了一个 pv,所以
StatefulSet
只有一个 Pod 可以匹配,查看 Pod:# 查看pod,只有一个因为pv,所以只有一个pod匹配成功后正常运行 # 因为是 StatefulSet,第二个没能正常启动,所以第三个Pod不会创建 $ kubectl get pod NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 57s web-1 0/1 Pending 0 54s # 查看 pv,可以看到只有一个绑定成功 # 第二个绑定不成功是因为访问模式不匹配 # 第三个绑定不成功是因为 storageClass 不匹配 $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfspv1 10Gi RWO Retain Bound default/www-web-0 nfs 3m35s nfspv2 5Gi ROX Retain Available nfs 34m nfspv3 5Gi RWO Retain Available nfs1 34m # 查看 pvc,每个Pod有一个pvc $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound nfspv1 10Gi RWO nfs 6m6s www-web-1 Pending nfs 6m3s
-
在 NFS 服务器的
/nfs
目录中创建index.html
,写入“/nfs访问成功”,然后通过nginx
来访问:# 获取IP $ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-0 1/1 Running 0 27m 10.244.1.58 k8s-node01 <none> <none> $ curl 10.244.1.58 /nfs访问成功
-
尝试删除 Pod,pv 中的数据不会丢失
$ kubectl delete pod web-0 pod "web-0" deleted # 可以看到 IP 已经变了,说明上一个Pod删除后又建了个新的(Pod 的 name 一致) $ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-0 1/1 Running 0 15s 10.244.2.60 k8s-node02 <none> <none> # 可以看到仍然可以成功访问,数据不会丢失 $ curl 10.244.2.60 /nfs访问成功
-
删除 StatefulSet 后,pvc 不会自动删除,pv也不会自动释放,需要手动删除
# 删除 StatefulSet 后,pvc 仍然存在 $ kubectl delete statefulset web $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound nfspv1 10Gi RWO nfs 13h # 删除 pvc 后,pv 没有自动释放 $ kubectl delete pvc www-web-0 $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfspv1 10Gi RWO Retain Released default/www-web-0 nfs 13h # 手动释放 pv $ kubectl edit pv nfspv1 # 将下面的 spec.claimRef 删除 spec: claimRef: apiVersion: v1 kind: PersistentVolumeClaim name: www-web-0 namespace: default resourceVersion: "619064" uid: 99cea07e-339e-431c-bcb6-c398c884b29c # 再次查看 pv 已经得到释放 $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nfspv1 10Gi RWO Retain Available nfs 13h
3. PV 的一些说明
-
匹配 Pod name ( 网络标识 ) 的模式为:
$(StatefulSet名称)-$(序号)
,比如上面的示例:web-0,web-1,web-2 -
StatefulSet
为每个 Pod 副本创建了一个 DNS 域名(意味着其它 Pod 可以通过这个域名来访问),这个域名的格式为:$(podname).(headless server name)
,也就意味着服务间是通过 Pod 域名来通信而非 Pod IP,因为当 Pod 所在 Node 发生故障时, Pod 会被飘移到其它 Node 上,Pod IP 会发生变化,但是 Pod 域名不会有变化# 随便进入一个以前的 Pod,没有就新建一个,然后ping域名,可以成功 ping 通 $ ping web-0.nginx PING web-0.nginx (10.244.2.60): 56 data bytes 64 bytes from 10.244.2.60: seq=0 ttl=62 time=0.388 ms 64 bytes from 10.244.2.60: seq=1 ttl=62 time=0.263 ms
-
StatefulSet 使用 Headless 服务来控制 Pod 的域名(意味着 Pod 外部可以通过这个域名来访问),这个域名的 FQDN(完全现定域名) 为:
$(service name).(namespace).svc.cluster.local
,其中,“cluster.local
” 指的是集群的域名# 1. 查看 DNS 服务器 coredns 的 ip $ kubectl get pod -n kube-system -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES coredns-5c98db65d4-5ztqn 1/1 Running 10 46d 10.244.0.19 k8s-master01 <none> <none> coredns-5c98db65d4-pc62t 1/1 Running 10 46d 10.244.0.18 k8s-master01 <none> <none> # 2. 通过 coredns 来解析域名,可以看到解析后的域名对应 StatefulSet 下的 Pod 的 IP # 用 dig 解析域名(没有 dig 要安装:yum -y install bind-utils) # 命令格式:dig -t A 域名 @DNS服务器IP $ dig -t A nginx.default.svc.cluster.local @10.244.0.19 ...省略 nginx.default.svc.cluster.local. 30 IN A 10.244.2.60 ...省略
-
根据
volumeClaimTemplates
,为每个 Pod 创建一个pvc
,pvc
的命名规则匹配模式:
(volumeClaimTemplates.name)-(pod_name)
,比如上面的 volumeMounts.name=www, Pod
name=web-[0-2],因此创建出来的 PVC 是 www-web-0、www-web-1、www-web-2 -
删除 Pod 不会删除其 pvc,手动删除 pvc 将自动释放 pv