ConfigMap 功能在 Kubernetes1.2 版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API 给我们提供了向容器中注入配置信息的机制,ConfigMap 可以被用来保存单个属性,也可以用来保存整个配置文件或者 JSON 二进制大对象,提供了一种集中式的配置管理。
主要分为资源清单创建、命令行创建。
(1) 资源清单创建
apiVersion: v1 # 版本,通过 kubectl explain cm 可以查看
kind: ConfigMap
metadata:
name: special-config # ConfigMap 的名字
namespace: default # 名称空间
data: # key: value 结构,配置数据
special.how: very
special.type: charm
在不同的应用下,kv会表现为具体的属性或者是文件。比如:替代环境变量时,通过key获取value属性;挂载数据卷时,key是文件名,value就是文件内容,confiMap就可以理解为是一个目录。
创建:
kubectl apply -f comfigmap.yaml
(2)命令行创建
通过目录创建:
kubectl create configmap game-config --from-file=../configmap/
--from-file
指定在目录下的所有文件都会被用在 ConfigMap
里面创建一个键值对,键的名字就是文件名,值就是文件的内容
通过文件创建:
kubectl create configmap game-config-2 --fromfile=game.properties
--from-file
参数只要指定为一个文件就可以从单个文件中创建 ConfigMap。
–from-file` 这个参数可以使用多次,你可以使用两次分别指定上个实例中的那两个配置文件,效果就跟指定整个目录是一样的
通过字面值创建:
kubectl create configmap special-config --from-literal=special.how=very --fromliteral=special.type=charm
使用文字值创建,利用 --from-literal
参数传递配置信息,该参数可以使用多次。
创建两个 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 中的配置已经注入到了容器中:
创建 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
通过 Volume
方式挂载,ConfigMap
中的键名就是 文件名,键值就是 文件内容,而这个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
通过**kubectl edit configmap [configmap name]**命令直接修改内容就可以达到热更新。如下:
创建一个 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 解决了密码、token、密钥等敏感数据的配置问题,而不需要把这些敏感数据暴露到镜像或者 Pod Spec 中。Secret 可以以 Volume 或者环境变量的方式使用。
Secret 有三种类型:
Opaque
和configMap很像, 数据是一个 map
类型,要求 value
是 base64
编码格式,可以用于环境变量和数据卷挂载;Service Account
用来访问 Kubernetes API
,由 Kubernetes
自动创建,并且会自动挂载到 Pod
的 /run/secrets/kubernetes.io/serviceaccount
目录中。并不是所有的Pod都能访问Kubernetes API,只有拥有Service Account的Pod才能访问。
# 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:认证的密钥信息
ServiceAccount 中包含三个部分:
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
默认情况下,每个 namespace 都会有一个 ServiceAccount,如果 Pod 在创建时没有指定 ServiceAccount,就会使用 Pod 所属的 namespace 的 ServiceAccount。
Opaque
和configMap很像, 数据是一个 map
类型,要求 value
是 base64
编码格式,可以用于环境变量和数据卷挂载。
(1)给用户名和密码用 base64 加密
$ echo -n admin | base64
YWRtaW4=
$ echo -n 123 | base64
MTIz
解密的话可以用:
$ echo -n YWRtaW4= | base64 -d
admin
(2)使用加密后的用户名和密码创建 Secret
apiVersion: v1 # kubectl explain secret 查看
kind: Secret
metadata:
name: mysecret # Secret 名称
type: Opaque # Secret 的类型
data:
password: MTIz # 密码
username: YWRtaW4= # 用户名
(3)查看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 的挂载
具体的应用:
(1)将 Secret 导出到环境变量中
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 在这儿不需要使用明文,稍微安全一点)
(2)将 Secret 挂载到 Volume 中
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
使用 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 密码
# [email protected] # docker 邮箱
kubectl create secret docker-registry \
myregistrykey \
--docker-server=hub.zyx.com \
--docker-username=admin \
--docker-password=Harbor12345 \
--docker-email=[email protected]
在创建 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
容器磁盘上的文件的生命周期是短暂的,这就使得在容器中运行重要应用时会出现一些问题:
Kubernetes 中的 Volume 抽象就很好的解决了这些问题:
Kubernetes 中的卷有明确的寿命 —— 与封装它的 Pod 相同。所以,卷的生命比 Pod 中的所有容器都长,当这个容器重启时数据仍然得以保存。当然,当 Pod 不再存在时,卷也将不复存在。也许更重要的是,Kubernetes 支持多种类型的卷,Pod 可以同时使用任意数量的卷
Kubernetes 支持以下类型的卷:
其中configmap和secret在前面已经说过了,这两个很像。
当 Pod 被分配给节点时,首先创建 emptyDir 卷,并且只要该 Pod 在该节点上运行,该卷就会存在。正如卷的名字所述,它最初是空的。该卷可以挂载到 Pod 每个容器中的相同或不同路径上,并且每个容器都可以读取和写入 emptyDir 卷中的文件。当出于任何原因从节点中删除 Pod 时, emptyDir 中的数据将被永久删除。
注意:容器崩溃不会从节点中移除 pod,因此 emptyDir 卷中的数据在容器时是安全的
emptyDir 的用法有:
创建一个 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
hostPath 卷将主机节点的文件系统中的文件或目录挂载到集群中。
hostPath 的用途如下:
注意事项:
spec.volumes[].hostPath.type 的值:
值 | 行为 |
---|---|
空字符串。(默认)用于向后兼容,这意味着在挂载 hostPath 卷之前不会执行任何检查。 | |
DirectoryOrCreate | 如果在给定的路径上没有任何东西存在,那么将根据需要在那里创建一个空目录,权限设置为 0755,与 Kubelet 具有相同的组和所有权。 |
Directory | 给定的路径下必须存在目录 |
FileOrCreate | 如果在给定的路径上没有任何东西存在,那么会根据需要创建一个空文件,权限设置为 0644,与 Kubelet 具有相同的组和所有权。 |
File | 给定的路径下必须存在文件 |
Socket | 给定的路径下必须存在 UNIX 套接字 |
CharDevice | 给定的路径下必须存在字符设备 |
BlockDevice | 给定的路径下必须存在块设备 |
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 会报错)
注意:Directory类型要确保每个 k8s 节点上都存在 /data
这个目录。
PersistentVolume (PV):是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV 也是集群中的资源。 PV 是 Volume 之类的卷插件,但具有独立于使用 PV 的 Pod 的生命周期。此 API 对象包含存储实现的细节,即 NFS、iSCSI 或特定于云供应商的存储系统。Pod会有不同的访问模式,根据访问模式的不同可以挂载到一个或多个Pod中。
PersistentVolumeClaim (PVC):是用户存储的请求。它与 Pod 相似。Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。PVC 可以声明可以请求特定的大小和访问模式(例如,可以以读/写一次或 只读多次模式挂载)。
绑定:master 中的控制环路监视新的 PVC,寻找匹配的 PV(如果可能),并将它们绑定在一起。如果为新的 PVC 动态调配 PV,则该环路将始终将该 PV 绑定到 PVC。否则,用户总会得到他们所请求的存储,但是容量可能超出要求的数量。一旦 PV 和 PVC 绑定后, PersistentVolumeClaim 绑定是排他性的,不管它们是如何绑定的。 PVC 跟 PV 绑定是一对一的映射。
持久化卷声明的保护:PVC 保护的目的是确保由 Pod 正在使用的 PVC 不会从系统中移除,因为如果被移除的话可能会导致数据丢失。当启用PVC 保护 alpha 功能时,如果用户删除了一个 pod 正在使用的 PVC,则该 PVC 不会被立即删除,需要手动删除。
注意:当 Pod 状态为 Pending 并且 Pod 已经分配给节点或者 Pod 为 Running 状态时,PVC 处于活动状态。(Pending状态可能是PVC并没有找到一个合适的PV进行绑定。)
生命周期:
静态 pv:集群管理员创建一些 PV。它们带有可供群集用户使用的实际存储的细节,一般保存访问至后端存储的细节(怎么连接,地址多少…)。它们存在于 Kubernetes API 中,可用于消费。
动态 pv:当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试动态地为 PVC 创建卷。此配置基于 StorageClasses :PVC 必须请求 [存储类],并且管理员必须创建并配置该类才能进行动态创建。声明该类为 “” 可以有效地禁用其动态配置。
要启用基于存储级别的动态存储配置,集群管理员需要启用 API server 上的 DefaultStorageClass [准入控制器]。例如,通过确保 DefaultStorageClass 位于 API server 组件的 --admission-control 标志,使用逗号分隔的有序值列表中,可以完成此操作。
PersistentVolume 类型以插件形式实现。Kubernetes 目前支持以下插件类型:
PersistentVolume 可以以资源提供者支持的任何方式挂载到主机上。如下表所示,供应商具有不同的功能,每个 PV 的访问模式都将被设置为该卷支持的特定模式。例如,NFS 可以支持多个读/写客户端,但特定的 NFS PV 可能以只读方式导出到服务器上。每个 PV 都有一套自己的用来描述特定功能的访问模式
在命令行中,访问模式缩写为:
注意:一个卷一次只能使用一种访问模式挂载,即使它支持很多访问模式。例如,GCEPersistentDisk 可以由单个节点作为 ReadWriteOnce 模式挂载,或由多个节点以 ReadWriteMany 模式挂载,但不能同时挂载。
卷插件 | ReadWriteOnce | ReadOnlyMany | ReadWriteMany | ReadWriteOncePod |
---|---|---|---|---|
AWSElasticBlockStore | ✓ | - | - | - |
AzureFile | ✓ | ✓ | ✓ | - |
AzureDisk | ✓ | - | - | - |
CephFS | ✓ | ✓ | ✓ | - |
Cinder | ✓ | - | 如果多次挂接卷可用 | - |
CSI | 取决于驱动 | 取决于驱动 | 取决于驱动 | 取决于驱动 |
FC | ✓ | ✓ | - | - |
FlexVolume | ✓ | ✓ | 取决于驱动 | - |
Flocker | ✓ | - | - | - |
GCEPersistentDisk | ✓ | ✓ | - | - |
Glusterfs | ✓ | ✓ | ✓ | - |
HostPath | ✓ | - | - | - |
iSCSI | ✓ | ✓ | - | - |
Quobyte | ✓ | ✓ | ✓ | - |
NFS | ✓ | ✓ | ✓ | - |
RBD | ✓ | ✓ | - | - |
VsphereVolume | ✓ | - | - (Pod 运行于同一节点上时可行) | - |
PortworxVolume | ✓ | - | ✓ | - |
StorageOS | ✓ | - | - | - |
当前,只有 NFS 和 HostPath 支持回收策略。AWS EBS、GCE PD、Azure Disk 和 Cinder 卷支持删除策略
卷可以处于以下的某种状态:
命令行会显示绑定到 PV 的 PVC 的名称。
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 # 挂载到哪个服务器
(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
(2)在 k8s 每个节点中安装 NFS 客户端:
# 安装依赖
$ yum -y install nfs-utils rpcbind
(3)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
(1)创建一个 pv
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
$ 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
(2)创建 Service 和 StatefulSet。这个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
(3)因为前面只创建了一个 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
注意:因此StatefulSet是有序部署的,因此第二个Pod的PVC没有绑定到一个PV,就会处于Pending状态,后序的Pod也没法创建了。
(4)在 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访问成功
(5)尝试删除 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访问成功
(6)删除 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
匹配 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。