Kubernetes存储
# Kubernetes存储
# 为什么需要存储卷?
容器部署过程中一般有以下三种数据:
启动时需要的初始数据,例如配置文件
启动过程中产生的临时数据,该临时数据需要多个容器间共享
启动过程中产生的持久化数据,例如MySQL的data目录
# 数据卷概述
Kubernetes中的Volume提供了在容器中挂载外部存储的能力
Pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后才可以使用相应的Volume
使用数据卷的流程:镜像里程序写的目录 > mountPath > 引用的volumes对应的name > 相关卷的配置
常用的数据卷:
本地(hostPath,emptyDir)
网络(NFS,Ceph,GlusterFS)
公有云(AWS EBS)
K8S资源(configmap,secret)
# 临时存储卷:emptyDir
emptyDir卷:
- 是一个临时存储卷,与Pod生命周期绑定一起,如果 Pod删除了卷也会被删除。
- 共享目录是在Pod的所在节点创建的。
应用场景:
Pod中容器之间数据共享
sideacer(边车)
initcontainer(初始容器)
emptyDir的用法
使用emptyDir的临时存储卷测试Pod之间
一个Pod是写入1到100到hello文件,另外一个Pod是读取,他们之间使用临时卷共享
[root@master ~]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: write #容器一是写入
command: ["bash","-c","for i in {1..100};do echo $i >> /data/hello;sleep 1;done"]
image: centos
volumeMounts: #数据卷的挂载
- name: data #数据卷绑定的名称
mountPath: /data #使用路径挂载在容器内部
- name: read #容器二是读取
command: ["bash","-c","tail -f /data/hello"]
image: centos
volumeMounts:
- name: data
mountPath: /data
volumes: #数据卷定义
- name: data #数据卷指定一个名称
emptyDir: {} #数据卷的类型
查看Pod运行在哪个节点上面
这里显示是在Node1节点上面,可以去Node1上查看
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 2/2 Running 1 2m42s 10.244.166.131 node1 <none> <none>
- 在node1节点查看容器运行的进程
[root@node1 ~]# docker ps | grep my-pod
4d0d67966468 centos "bash -c 'tail -f /d…" About a minute ago Up About a minute k8s_read_my-pod_default_4a359535-0bea-4231-9ac7-e7b0d91afb74_0 #这里的后面的id就是数据卷存储的目录
60cdb7fa3d29 centos "bash -c 'for i in {…" About a minute ago Up About a minute k8s_writemy-pod_default_4a359535-0bea-4231-9ac7-e7b0d91afb74_0
1c898733272d registry.aliyuncs.com/google_containers/pause:3.2 "/pause" About a minute ago Up About a minute k8s_POD_my-pod_default_4a359535-0bea-4231-9ac7-e7b0d91afb74_0
查找数据卷映射的目录
[root@node1 ~]# cd /var/lib/kubelet/pods/
[root@node1 pods]# ll
total 12
drwxr-x--- 5 root root 4096 Nov 5 00:24 4a359535-0bea-4231-9ac7-e7b0d91afb74 #第一个就是这个Pod
drwxr-x--- 5 root root 4096 Nov 4 23:31 b08e29c6-ba4b-4416-89e2-48df0a101535
drwxr-x--- 5 root root 4096 Nov 4 23:32 b98cfa42-b9c9-47db-a0d7-2aeb468a24d1
[root@node1 pods]# cd 4a359535-0bea-4231-9ac7-e7b0d91afb74/volumes/kubernetes.io~empty-dir/
[root@node1 kubernetes.io~empty-dir]# ls
data #这个data就是该Pod进行的数据卷共享的目录
[root@node1 kubernetes.io~empty-dir]# cd data/
[root@node1 data]# ls
hello
# 节点存储卷:hostPath
hostPath卷:
- 挂载Node文件系统(Pod所在节点)上文件或者目 录到Pod中的容器。
应用场景:
- Pod中容器需要访问宿主机文件
- 我的应用程序想读取系统的某个文件
hostPath的用法
使用挂载宿主机的/tmp的目录
容器挂载在/data目录下
[root@master ~]# cat hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: busybox
image: busybox
args:
- /bin/sh
- -c
- sleep 36000
volumeMounts: #挂载在容器的数据卷
- name: data #指定定义名称
mountPath: /data #挂载在容器的/data位置
volumes: #指定数据卷的来源
- name: data #指定定义名称
hostPath:
path: /tmp #指定数据卷源目录
type: Directory #指定数据卷类型
- 进入容器查看/data下面是否有数据
[root@master tmp]# kubectl exec -it my-pod -- sh
/ # cd /data/
/data # ls
hsperfdata_root
systemd-private-55c453af06944c75a71a7703af924fc9-chronyd.service-re7Kjy
wrapper-619-1-in
wrapper-619-1-out
/data # touch a.tzxt #创建一个文件
查看该Pod是运行在哪一个节点上面
查看该节点的/tmp下面是否有我创建的文件
[root@master tmp]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 1/1 Running 0 14m 10.244.166.132 node1 <none> <none>
[root@node1 ~]# cd /tmp/
[root@node1 tmp]# ll
total 8
-rw-r--r-- 1 root root 0 Nov 5 00:53 a.tzxt
drwxr-xr-x 2 root root 4096 Nov 4 21:39 hsperfdata_root
drwx------ 3 root root 4096 Nov 4 21:38 systemd-private-55c453af06944c75a71a7703af924fc9-chronyd.service-re7Kjy
prw-r--r-- 1 root root 0 Nov 5 00:56 wrapper-619-1-in
prw-r--r-- 1 root root 0 Nov 5 00:56 wrapper-619-1-out
# 网络存储卷: NFS
Kubernetes的NFS存储用于将某事先存在的NFS服务器导出export的存储空间挂载到Pod中来供Pod容器使用。
与emptyDir不同的是,NFS存储在Pod对象终止后仅是被卸载而非删除。另外,NFS是文件系统及共享服务,它支持同时存在多路挂载请求。
使用nfs数据卷目标:
- pod启动之后产生的数据持久化到远程存储
- pod多副本共享数据
定义NFS存储时,常用到以下字段。
server
:NFS服务器的IP地址或者主机名,必选字段。path
:NFS服务器导出(共享)的文件系统路径,必选字段。readOnly
:是否以只读挂载,默认为false。
NFS持久化的用法
- 在node1上安装NFS服务端
[root@node ~]# yum install -y nfs-utils
[root@node ~]# echo "/nfs/kubernetes *(rw,no_root_squash)" > /etc/exports
[root@node ~]# mkdir -p /nfs/kubernetes
[root@node ~]# systemctl restart rpcbind && systemctl restart nfs
[root@node ~]# echo "666" > index.html /nfs/kubernetes
[root@node ~]# showmount -e 192.168.1.88
配置说明:
/data/k8s:
是共享的数据⽬录**
* :
表示任何⼈都有权限连接,当然也可以是⼀个⽹段,⼀个 IP,也可以是域名**
rw:
读写的权限**
sync:
表示⽂件同时写⼊硬盘和内存**
no_root_squash:
当登录 NFS 主机使⽤共享⽬录的使⽤者是 root 时,其权限将被转换成为匿名使⽤者,通常它的 UID 与 GID,都会变成 nobody 身份
- 在master节点上安装NFS服务端
[root@master ~]# yum install -y nfs-utils
[root@master ~]# systemctl restart rpcbind && systemctl restart nfs
[root@master ~]# mount -t nfs 192.168.1.88:/nfs/kubernetes /mnt/
- 创建一个pod控制器使用网络共享NFS存储挂载
[root@master ~]# kubectl create deployment nginx --image=nginx --port=80 --dry-run -o yaml > deployment.yaml
[root@master ~]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
volumeMounts:
- name: wwwroot
mountPath: /usr/share/nginx/html
volumes:
- name: wwwroot
nfs:
server: 192.168.1.95
path: /nfs/kubernetes
[root@master ~]# kubectl apply -f deployment.yaml
deployment.apps/nginx created
- 检查nfs存储是否生效
- 发现容器内挂载在/usr/share/nginx/html下面的index.html正是所写入得到内容
[root@master ~]# kubectl exec -it nginx-cfb45c844-8zcqh bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx-cfb45c844-8zcqh:/# cd /usr/share/nginx/html/
root@nginx-cfb45c844-8zcqh:/usr/share/nginx/html# ls
index.html
root@nginx-cfb45c844-8zcqh:/usr/share/nginx/html# cat index.html
6666
- 暴露该端口使用Nodeport访问
[root@master ~]# kubectl expose deployment nginx --port=80 --target-port=80 --type=NodePort
[root@master ~]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 115m
nginx NodePort 10.110.18.210 <none> 80:30354/TCP 35m
# 持久卷:PV与PVC
持久卷的概述:
PersistentVolume(PV)
:对存储资源创建和使用的抽象,使得存储作为集群中的资源管理PersistentVolumeClaim(PVC)
:让用户不需要关心具体的Volume实现细节
持久卷(PersistentVolume,PV)是集群中的一块存储,可以由管理员事先供应,或者 使用存储类(Storage Class) (opens new window)来动态供应。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样,也是使用 卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。
持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载
# PV生命周期
Capacity(存储能⼒)
⼀般来说,⼀个 PV 对象都要指定⼀个存储能⼒,通过 PV 的 capacity属性来设置的,⽬前只⽀持存储 空间的设置,就是我们这⾥的 storage=1Gi,不过未来可能会加⼊ IOPS、吞吐量等指标的配置。
AccessModes(访问模式)
AccessModes 是⽤来对 PV 进⾏访问模式的设置,⽤于描述⽤户应⽤对存储资源的访问权限,访问权 限包括下⾯⼏种⽅式:*
ReadWriteOnce(RWO)
:读写权限,但是只能被单个节点挂载ReadOnlyMany(ROX)
:只读权限,可以被多个节点挂载ReadWriteMany(RWX)
:读写权限,可以被多个节点挂载注意
:⼀些 PV 可能⽀持多种访问模式,但是在挂载的时候只能使⽤⼀种访问模式,多种访问模 式是不会⽣效的。
persistentVolumeReclaimPolicy(回收策略)
我这⾥指定的 PV 的回收策略为 Recycle,⽬前 PV ⽀持的策略有三种:
Retain(保留)
保留数据,需要管理员⼿⼯清理数据Recycle(回收)
清除 PV 中的数据,效果相当于执⾏rm -rf /thevoluem/*
Delete(删除)
与 PV 相连的后端存储完成 volume 的删除操作,当然这常⻅于云服务商的存储 服务,⽐如 ASW EBS。- 不过需要注意的是,⽬前只有 NFS 和 HostPath 两种类型⽀持回收策略。当然⼀般来说还是设置为 Retain 这种策略保险⼀点。
PV的生命周期状态
⼀个 PV 的⽣命周期中,可能会处于4中不同的阶段:
Available(可⽤):
表示可⽤状态,还未被任何 PVC 绑定Bound(已绑定):
表示 PVC 已经被 PVC 绑定Released(已释放):
PVC 被删除,但是资源还未被集群重新声明Failed(失败):
表示该 PV 的⾃动回收失败
# PV与PVC使用流程
Pv的使用
有了上⾯的 NFS 共享存储,下⾯我们就可以来使⽤ PV 和 PVC 了。
PV 作为存储资源,主要包括存储 能⼒、访问模式、存储类型、回收策略等关键信息,下⾯我们来新建⼀个PV对象,使⽤nfs类型的后 端存储,5G 的存储空间,访问模式为 ReadWriteOnce,回收策略为 Recyle
[root@master ~]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pvc
spec:
capacity: #存储空间
storage: 5Gi
accessModes: #访问模式
- ReadWriteOnce #单个节点挂载的读写权限
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /nfs/kubernetes
server: 192.168.1.88
[root@master ~]# kubectl apply -f pv.yaml
persistentvolume/my-pvc created
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pvc 5Gi RWO Recycle Available 4s
PVC的使用
但是在我们真正使⽤的时候是使⽤的 PVC,就类似于我们的服务是通 过 Pod 来运⾏的,⽽不是 Node,只是 Pod 跑在 Node 上⽽已,所以这节课我们就来给⼤家讲解下 PVC 的使⽤⽅法。
- 我们来新建⼀个数据卷声明,我们来请求 5Gi 的存储容量,访问模式也是 ReadWriteOnce
[root@master ~]# cat pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteOnce
resources: #设置资源
requests: #请求的资源为5Gi
storage: 5Gi
此时的pv和pvc都将变成bound
因为pv的存储空间被pvc绑定所有显示bound被使用
[root@master ~]# kubectl apply -f pvc.yaml
persistentvolumeclaim/my-pvc created
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound my-pvc 5Gi RWO 6s
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pvc 5Gi RWO Recycle Bound default/my-pvc 42m
创建一个Pod使用PVC持久化
指定容器挂载目录是/usr/share/nginx/html
[root@master ~]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumes:
- name: www
persistentVolumeClaim:
claimName: my-pvc
[root@master ~]# kubectl apply -f pod.yaml
pod/my-pod created
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod 1/1 Running 0 63s
验证pvc是否能正常使用
进入my-pod的容器,创建index.html的文件
[root@master ~]# kubectl exec -it my-pod -- bash
root@my-pod:/# cd /usr/share/nginx/html/
root@my-pod:/usr/share/nginx/html# ls
root@my-pod:/usr/share/nginx/html# echo 3333 >> index.html
root@my-pod:/usr/share/nginx/html# exit
exit
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 1/1 Running 0 3m7s 10.244.167.140 node <none> <none>
[root@master ~]# curl 10.244.167.140
3333
在nfs的服务端上查看
验证成功, pvc的持久化能够正常使用
[root@node ~]# cd /nfs/kubernetes/
[root@node kubernetes]# ls
index.html
[root@node kubernetes]# cat index.html
3333
# PV与PVC常见问题
pv与pvc关系?
- 一对一的关系互联
pv与pvc怎么匹配的?
存储空间
访问模式
容器匹配策略是怎么样的?
- 匹配最接近的pv容量
- 如果满足不了,pod处于pending
存储空间的字段是限制使用容量?
这个字段主要用于做匹配的,实际使用限制取决于后端存储
简而言之,无状态可以理解为:不用考虑多副本时之间的关系、是否有独立存储等
# PV 动态供给(StorageClass)
K8s默认不支持NFS动态供给,需要单独部署社区开发的插件。
项目地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
[root@master nfs-external-provisioner]# ll
total 12
-rw-r--r-- 1 root root 255 Mar 27 2021 class.yaml
-rw-r--r-- 1 root root 1054 Nov 5 23:16 deployment.yaml
-rw-r--r-- 1 root root 1819 Mar 27 2021 rbac.yaml
部署NFS动态供应
- 创建授权访问apiserver
[root@master nfs-external-provisioner]# cat rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
[root@master nfs-external-provisioner]# kubectl apply -f rbac.yaml
- 部署插件,需修改里面NFS服务器地址与共享目录
[root@master nfs-external-provisioner]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: default
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: lizhenliang/nfs-subdir-external-provisioner:v4.0.1
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 192.168.1.88 #修改NFS的服务端的地址
- name: NFS_PATH
value: /nfs/kubernetes #NFS服务端的存储路径
volumes:
- name: nfs-client-root
nfs:
server: 192.168.1.88 #NFS服务端地址
path: /nfs/kubernetes #NFS服务端的存储路径
[root@master nfs-external-provisioner]# kubectl apply -f deployment.yaml
- 创建存储类
[root@master nfs-external-provisioner]# cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
[root@master nfs-external-provisioner]# kubectl apply -f class.yaml
- 查看存储类
[root@master ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 18m
使用动态供应
- ⾸先创建⼀ 个PVC对象
[root@master ~]# cat test-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
我们这⾥声明了⼀个 PVC 对象,采⽤ ReadWriteMany 的访问模式,请求 1Mi 的空间
,但是我们可以 看到上⾯的 PVC ⽂件我们没有标识出任何和 StorageClass 相关联的信息,那么如果我们现在直接创 建这个 PVC 对象能够⾃动绑定上合适的 PV 对象吗?显然是不能的(前提是没有合适的 PV),我们这⾥ 有两种⽅法可以来利⽤上⾯我们创建的 StorageClass 对象来⾃动帮我们创建⼀个合适的 PV:
- 第⼀种⽅法:
在这个 PVC 对象中添加⼀个声明storageName对象的标识,这⾥我们可以利⽤⼀ 个storageClassName属性来标识,如下:
[root@master ~]# cat test-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-pvc
spec:
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
[root@master ~]# kubectl apply -f test-pvc.yaml
persistentvolumeclaim/test-pvc created
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound my-pvc 5Gi RWO 82m
test-pvc Bound pvc-24df2e95-4eb9-4872-9aab-e78a098054cd 1Mi RWX managed-nfs-storage 4s
- 第⼆种⽅法:
我们可以设置这个 course-nfs-storage 的 StorageClass 为 Kubernetes 的默认存储后端,我们可以⽤ kubectl patch 命令来更新:
[root@master ~]# kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
[root@master ~]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage (default) k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 58m
[root@master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pvc 5Gi RWO Recycle Bound default/my-pvc 112m
pvc-24df2e95-4eb9-4872-9aab-e78a098054cd 1Mi RWX Delete Bound default/test-pvc managed-nfs-storage 30m
测试动态供应
还是继续用nginx测试pvc动态供应
末尾指定claimName的pvc是test-pvc
[root@master ~]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- name: nfs-pvc
mountPath: /usr/share/nginx/html
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-pvc
[root@master ~]# kubectl apply -f pod.yaml
pod/my-pod created
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 1/1 Running 0 52s 10.244.167.142 node <none> <none>
nfs-client-provisioner-75549d8b58-vldpm 1/1 Running 0 63m 10.244.167.141 node <none> <none>
在nfs的服务端验证动态供应是否生效
创建一个index.html测试
[root@node ~]# cd /nfs/kubernetes/
[root@node kubernetes]# ls
default-test-pvc-pvc-24df2e95-4eb9-4872-9aab-e78a098054cd
[root@node kubernetes]# cd default-test-pvc-pvc-24df2e95-4eb9-4872-9aab-e78a098054cd/
[root@node default-test-pvc-pvc-24df2e95-4eb9-4872-9aab-e78a098054cd]# echo 6666 > index.html
- 回到master节点再次curl查看pod是否成功访问index.html
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 1/1 Running 0 96s 10.244.167.142 node <none> <none>
nfs-client-provisioner-75549d8b58-vldpm 1/1 Running 0 64m 10.244.167.141 node <none> <none>
[root@master ~]# curl 10.244.167.142
6666
# 有状态应用部署:StatefulSet控制器
- 无状态与有状态:
Deployment控制器设计原则:管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运 行,可随意扩容和缩容。这种应用称为“无状态”,例如Web服务
在实际的场景中,这并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有 依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群
# StatefulSet:
StatefulSet
是用来管理有状态应用的工作负载 API 对象。StatefulSet
用来管理 Deployment 和扩展一组 Pod,并且能为这些 Pod 提供序号和唯一性保证。- 和 Deployment 相同的是,StatefulSet 管理了基于相同容器定义的一组 Pod。但和 Deployment 不同的是,StatefulSet 为它们的每个 Pod 维护了一个固定的 ID。这些 Pod 是基于相同的声明来创建的,但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
部署有状态应用
解决Pod独立生命周期,保持Pod启动顺序和唯一性
- 稳定,唯一的网络标识符,持久存储
- 有序,优雅的部署和扩展、删除和终止
- 有序,自动的滚动更新
- 应用场景:分布式应用、数据库集群
- 删除或者收缩 StatefulSet 并不会删除它关联的存储卷。这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
- StatefulSet 当前需要 headless 服务 来负责 Pod 的网络标识。您需要负责创建此服务。
StatefulSet将应用状态抽象成了两种情况:
拓扑状态
:应用实例必须按照某种顺序启动。新创建的Pod必须和原来Pod的网络标识一样存储状态
:应用的多个实例分别绑定了不同存储数据。
StatefulSet的使用
StatefulSet通过Headless Service维持Pod的拓扑状态
首先创建无头服务Headless service
[root@master statefulset]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
labels:
app: nginx
name: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
[root@master statefulset]# kubectl apply -f service.yaml
service/nginx created
[root@master statefulset]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 11h
nginx ClusterIP None <none> 80/TCP 73s
- 创建使用StatefulSet控制器的pod
[root@master statefulset]# kubectl create deployment web --image=nginx --port=80 --dry-run -o yaml > statefulset.yaml
[root@master statefulset]# cat statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet #修改个类型
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: web
[root@master statefulset]# kubectl apply -f statefulset.yaml
statefulset.apps/web created
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-75549d8b58-vldpm 1/1 Running 0 109m
web-0 1/1 Running 0 72s #有序
web-1 1/1 Running 0 55s
- 可以看出创建了两个名为
web-0
和web-1
的pod,service也会由两个endpoint:
[root@master statefulset]# kubectl describe service nginx
Name: nginx
Namespace: default
Labels: app=nginx
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Families: <none>
IP: None
IPs: None
Port: web 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.167.145:80,10.244.167.146:80
Session Affinity: None
Events: <none>
创建时是先创建web-0再创建web-1,而删除的时候是先删除web-1,再删除web-0.
现在可以直接访问服务
[root@master statefulset]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 1/1 Running 0 49m 10.244.167.142 node <none> <none>
nfs-client-provisioner-75549d8b58-vldpm 1/1 Running 0 112m 10.244.167.141 node <none> <none>
web-0 1/1 Running 0 4m15s 10.244.167.145 node <none> <none>
web-1 1/1 Running 0 3m58s 10.244.167.146 node <none> <none>
[root@master statefulset]# curl 10.244.167.145
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
PV和PVC的设计,使得StatefulSet对存储状态的管理成为了可能,现在为以上这些pod添加存储
StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用 VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。
[root@master statefulset]# cat statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: #专门的申请模板 只对于Statefulset
- metadata: #为Pod申请一个独立的PV
name: www
spec:
storageClassName: managed-nfs-storage
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
[root@master statefulset]# kubectl apply -f statefulset.yaml
statefulset.apps/web created
查看pods的状态和pv以及pvc的状态
StatefulSet还会为每一个Pod分配并创建一个同样编号的PVC。
这样,kubernetes就可以通过Persistent Volume机制为这个PVC绑定对应的PV,从而保证每一个Pod都拥有一个独立的Volume:
[root@master statefulset]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod 1/1 Running 0 62m
nfs-client-provisioner-75549d8b58-vldpm 1/1 Running 0 125m
web-0 1/1 Running 0 42s
web-1 1/1 Running 0 23s
[root@master statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound my-pvc 5Gi RWO 3h3m
test-pvc Bound pvc-24df2e95-4eb9-4872-9aab-e78a098054cd 1Mi RWX managed-nfs-storage 101m
www-web-0 Bound pvc-66727b03-b4f4-47b1-a681-9a9c406ee732 100Mi RWO managed-nfs-storage 55s
www-web-1 Bound pvc-d9df0901-79be-47d2-b8a1-4552213e09d3 100Mi RWO managed-nfs-storage 36s
[root@master statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pvc 5Gi RWO Recycle Bound default/my-pvc 3h5m
pvc-24df2e95-4eb9-4872-9aab-e78a098054cd 1Mi RWX Delete Bound default/test-pvc managed-nfs-storage 103m
pvc-66727b03-b4f4-47b1-a681-9a9c406ee732 100Mi RWO Delete Bound default/www-web-0 managed-nfs-storage 2m46s
pvc-d9df0901-79be-47d2-b8a1-4552213e09d3 100Mi RWO Delete Bound default/www-web-1 managed-nfs-storage 2m27s
在nfs的服务端写入index.html进行访问测试
我已经将nfs的服务端挂载在master节点上
[root@master ~]# cd /mnt/default-www-web-0-pvc-66727b03-b4f4-47b1-a681-9a9c406ee732/
[root@master default-www-web-0-pvc-66727b03-b4f4-47b1-a681-9a9c406ee732]# echo web1 > index.html
[root@master default-www-web-0-pvc-66727b03-b4f4-47b1-a681-9a9c406ee732]# cd ..
[root@master default-www-web-1-pvc-d9df0901-79be-47d2-b8a1-4552213e09d3]# echo web2 > index.html
[root@master ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod 1/1 Running 0 68m 10.244.167.142 node <none> <none>
nfs-client-provisioner-75549d8b58-vldpm 1/1 Running 0 131m 10.244.167.141 node <none> <none>
web-0 1/1 Running 0 6m31s 10.244.167.147 node <none> <none>
web-1 1/1 Running 0 6m12s 10.244.167.148 node <none> <none>
[root@master ~]# curl 10.244.167.147
web1
[root@master ~]# curl 10.244.167.148
web2
# 应用程序配置文件存储:ConfigMap
资源对象: ConfigMap
- 我们知道许多应⽤经常会有从配置⽂件、命令⾏参数或者环境变量中读取 ⼀些配置信息,这些配置信息我们肯定不会直接写死到应⽤程序中去的,⽐如你⼀个应⽤连接⼀ 个 redis 服务,下⼀次想更换⼀个了的,还得重新去修改代码,重新制作⼀个镜像,这肯定是不可取 的,
⽽ ConfigMap 就给我们提供了向容器中注⼊配置信息的能⼒,不仅可以⽤来保存单个属性,也可 以⽤来保存整个配置⽂件,⽐如我们可以⽤来配置⼀个 redis 服务的访问地址,也可以⽤来保存整 个 redis 的配置⽂件。
- ConfigMap 资源对象使⽤ key-value 形式的键值对来配置数据,这些数据可以在 Pod ⾥⾯使 ⽤, ConfigMap 和我们后⾯要讲到的 Secrets ⽐较类似,⼀个⽐较⼤的区别是 ConfigMap 可以⽐较⽅ 便的处理⼀些⾮敏感的数据,
⽐如密码之类的还是需要使⽤ Secrets 来进⾏管理。
- 我们知道许多应⽤经常会有从配置⽂件、命令⾏参数或者环境变量中读取 ⼀些配置信息,这些配置信息我们肯定不会直接写死到应⽤程序中去的,⽐如你⼀个应⽤连接⼀ 个 redis 服务,下⼀次想更换⼀个了的,还得重新去修改代码,重新制作⼀个镜像,这肯定是不可取 的,
可以使⽤ kubectl create configmap -h 来查看关于创建 ConfigMap 的帮助信息
Examples:
# Create a new configmap named my-config based on folder bar
kubectl create configmap my-config --from-file=path/to/bar
# Create a new configmap named my-config with specified keys instead of file basenames on disk
kubectl create configmap my-config --from-file=key1=/path/to/bar/file1.txt --from-file=key2=/path/to/bar/file2.txt
# Create a new configmap named my-config with key1=config1 and key2=config2ConfigMap
kubectl create configmap my-config --from-literal=key1=config1 --from-literal=key2=config2
创建含有变量的文件
我们可以看到可以从⼀个给定的⽬录来创建⼀个 ConfigMap 对象,⽐如我们有⼀个 testcm 的⽬录, 该⽬录下⾯包含⼀些配置⽂件, redis 和 mysql 的连接信息
[root@master work]# ls testcm/
mysql.conf redis.conf
[root@master work]# cat testcm/mysql.conf
host=127.0.0.1
port=3306
[root@master work]# cat testcm/redis.conf
host=127.0.0.1
port=6379
使⽤ from-file 关键字来创建包含这个⽬录下⾯所以配置⽂件的 ConfigMap
其中 from-file 参数指定在该⽬录下⾯的所有⽂件都会被⽤在 ConfigMap ⾥⾯创建⼀个键值对,键的 名字就是⽂件名,值就是⽂件的内容
[root@master work]# kubectl create configmap cm-demo1 --from-file=testcm
configmap/cm-demo1 created
[root@master work]# kubectl get configmaps
NAME DATA AGE
cm-demo1 2 6s
kube-root-ca.crt 1 27s
- 使⽤ describe 命令查看详细信息
[root@master work]# kubectl describe configmaps cm-demo1
Name: cm-demo1
Namespace: default
Labels: <none>
Annotations: <none>
Data
====
mysql.conf:
----
host=127.0.0.1
port=3306
redis.conf:
----
host=127.0.0.1
port=6379
Events: <none>
- 使用yaml的方式查看更详细的键值
[root@master work]# kubectl get configmaps cm-demo1 -o yaml
apiVersion: v1
data:
mysql.conf: |
host=127.0.0.1
port=3306
redis.conf: |
host=127.0.0.1
port=6379
kind: ConfigMap
metadata:
creationTimestamp: "2021-11-06T05:52:14Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:mysql.conf: {}
f:redis.conf: {}
manager: kubectl-create
operation: Update
time: "2021-11-06T05:52:14Z"
name: cm-demo1
namespace: default
resourceVersion: "10516"
uid: 9a07b954-c61a-4b4d-a0ec-5214c3c317ee
除了通过⽂件⽬录进⾏创建,我们也可以使⽤指定的⽂件进⾏创建 ConfigMap
我们创建⼀个 redis 的配置的⼀个单独 ConfigMap 对象:
[root@master work]# kubectl create configmap cm-demo2 --from-file=testcm/redis.conf
configmap/cm-demo2 created
[root@master work]# kubectl get configmaps cm-demo2 -o yaml
apiVersion: v1
data:
redis.conf: |
host=127.0.0.1
port=6379
kind: ConfigMap
metadata:
creationTimestamp: "2021-11-06T05:55:37Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:redis.conf: {}
manager: kubectl-create
operation: Update
time: "2021-11-06T05:55:37Z"
name: cm-demo2
namespace: default
resourceVersion: "10782"
uid: 71c2a77a-fb21-44c3-a293-064e3d1a2826
注:
我们可以看到⼀个关联 redis.conf ⽂件配置信息的 ConfigMap 对象创建成功了,另外值得注意的是 --from-file 这个参数可以使⽤多次,⽐如我们这⾥使⽤两次分别指定 redis.conf 和 mysql.conf ⽂件,就和直接指定整个⽬录是⼀样的效果了。
我们还可以直接使⽤字符串进⾏创建,通过 --from-literal 参数传递配置信息
同样的,这个参数可以使⽤多次
[root@master work]# kubectl create configmap cm-demo3 --from-literal=db.host=localhost --from-literal=db.port=3306
configmap/cm-demo3 created
[root@master work]# kubectl get configmaps cm-demo3 -o yaml
apiVersion: v1
data:
db.host: localhost
db.port: "3306"
kind: ConfigMap
metadata:
creationTimestamp: "2021-11-06T05:58:06Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:db.host: {}
f:db.port: {}
manager: kubectl-create
operation: Update
time: "2021-11-06T05:58:06Z"
name: cm-demo3
namespace: default
resourceVersion: "10979"
uid: 36bf5bbb-b43a-402d-92c2-af5f48ae0172
# configMap的使用
ConfigMap 这些配置数据可以 通过很多种⽅式在 Pod ⾥使⽤,主要有以下⼏种⽅式:
设置环境变量的值
在容器⾥设置命令⾏参数
在数据卷⾥⾯创建config⽂件
使⽤ ConfigMap 来填充我们的环境变量:
使用busybox镜像打印我们所注入的变量
[root@master work]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: testcm1-pod
spec:
restartPolicy: Never
containers:
- image: busybox
name: testcm1
command: ["/bin/sh", "-c", "env"]
env: #第一种注入变量的方式
- name: DB_HOST
valueFrom: #变量的来源
configMapKeyRef: #从configMap来
name: cm-demo3 #从configMap的cm-demo3来
key: db.host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.port
envFrom: #第二种注入变量的方式
- configMapRef: #来源于configMap
name: cm-demo1 #configMap的cm-demo1
[root@master work]# kubectl apply -f pod.yaml
pod/testcm1-pod created
[root@master work]# kubectl get pods
NAME READY STATUS RESTARTS AGE
testcm1-pod 0/1 Completed 0 3m21s
- 查看日志看busybox是否打印出来变量
[root@master work]# kubectl logs testcm1-pod
KUBERNETES_SERVICE_PORT=443
KUBERNETES_PORT=tcp://10.96.0.1:443
HOSTNAME=testcm1-pod
DB_PORT=3306
SHLVL=1
HOME=/root
mysql.conf=host=127.0.0.1
port=3306
redis.conf=host=127.0.0.1
port=6379
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_SERVICE_HOST=10.96.0.1
PWD=/
DB_HOST=localhost
注:
可以看到 DB_HOST 和 DB_PORT 都已经正常输出了,另外的环境变量是因为我们这⾥直接把 cm-demo1 给注⼊进来了,所以把他们的整个键值给输出出来了,这也是符合预期的。
- 使⽤ ConfigMap 来设置命令⾏参数, ConfigMap 也可以被⽤来设置容器中的命令或者参数值
[root@master work]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: testcm2-pod
spec:
restartPolicy: Never
containers:
- image: busybox
name: testcm2
command: ["/bin/sh", "-c", "echo $(DB_HOST) $(DB_PORT)"]
env:
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: cm-demo3
key: db.port
[root@master work]# kubectl apply -f pod.yaml
pod/testcm2-pod created
[root@master work]# kubectl logs testcm2-pod
localhost 3306
- 另外⼀种是⾮常常⻅的使⽤ ConfigMap 的⽅式:
- 通过数据卷使⽤,在数据卷⾥⾯使⽤ ConfigMap
- 就是将⽂件填⼊数据卷,在这个⽂件中,键就是⽂件名,键值就是⽂件内容:
[root@master work]# cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: testcm3-pod
spec:
restartPolicy: Never
containers:
- image: busybox
name: testcm3
command: ["/bin/sh", "-c", "cat /etc/config/redis.conf"]
volumeMounts:
- name: config-volume
mountPath: /etc/config
volumes:
- name: config-volume
configMap:
name: cm-demo2
[root@master work]# kubectl apply -f pod.yaml
pod/testcm3-pod created
[root@master work]# kubectl get pods
NAME READY STATUS RESTARTS AGE
testcm3-pod 0/1 Completed 0 5s
注:
另外需要注意的是,当 ConfigMap 以数据卷的形式挂载进 Pod 的时,这时更新 ConfigMap (或删掉重建 ConfigMap ), Pod 内挂载的配置信息会热更新。这时可以增加⼀些监测配置⽂件变更的脚本,然
后 reload 对应服务。
[root@master work]# kubectl logs testcm3-pod
host=127.0.0.1
port=6379
# 敏感数据存储:Secret
资源对象: Secret
- 如果涉及到⼀些安全相关的 数据的话⽤ ConfigMap 就⾮常不妥了,因为 ConfigMap 是名为存储的,我们说这个时候我们就需要⽤ 到另外⼀个资源对象了:
Secret , Secret ⽤来保存敏感信息,例如密码、OAuth 令牌和 ssh key等 等,将这些信息放在 Secret 中⽐放在 Pod 的定义中或者 docker 镜像中来说更加安全和灵活。
- 如果涉及到⼀些安全相关的 数据的话⽤ ConfigMap 就⾮常不妥了,因为 ConfigMap 是名为存储的,我们说这个时候我们就需要⽤ 到另外⼀个资源对象了:
Secret 有三种类型:
kubectl create secret 支持三种数据类型:
- generic:从文件、目录或者字符串创建,例如存储用户名密码*
- docker-registry:存储镜像仓库认证信息*
- tls:存储证书,例如HTTPS证书
Opaque
: base64 编码格式的 Secret,⽤来存储密码、密钥等;但数据也可以通过base64 – decode解码得到原始数据,所有加密性很弱。kubernetes.io/dockerconfigjson
: ⽤来存储私有docker registry的认证信息。kubernetes.io/service-account-token
: ⽤于被 serviceaccount 引⽤,serviceaccout 创建时 Kubernetes会默认创建对应的secret。Pod如果使⽤了serviceaccount,对应的secret会⾃动挂载到Pod⽬录 /run/secrets/kubernetes.io/serviceaccount 中。
Opaque Secret Opaque 类型的数据是⼀个 map 类型,要求value是 base64 编码格式,⽐如我们来创建⼀个⽤户名为 admin,密码为 admin321 的 Secret 对象,⾸先我们先把这⽤户名和密码做 base64 编码
[root@master ~]# echo -n "admin" | base64
YWRtaW4=
[root@master ~]# echo -n "admin123" | base64
YWRtaW4xMjM=
Secret的generic使用
- 使用快速部署yaml创建一个secret
[root@master ~]# kubectl create secret generic mysecret --type Opaque --from-literal=username=YWRtaW4= --from-literal=password=YWRtaW4xMjM= --dry-run -o yaml > secret.yaml
[root@master ~]# cat secret.yaml
apiVersion: v1
data:
password: YWRtaW4xMjM=
username: YWRtaW4=
kind: Secret
metadata:
creationTimestamp: null
name: mysecret
type: Opaque
- 利⽤ get secret 命令查看:
- 其中 default-token-cty7pdefault-token-n9w2d 为创建集群时默认创建的 secret,被 serviceacount/default 引⽤。
[root@master ~]# kubectl apply -f secret.yaml
secret/mysecret created
[root@master ~]# kubectl get secrets
NAME TYPE DATA AGE
default-token-rmwg8 kubernetes.io/service-account-token 3 170m
mysecret Opaque 2 11s
- 使⽤ describe 命令,查看详情:
[root@master ~]# kubectl describe secrets mysecret
Name: mysecret
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
password: 12 bytes
username: 8 bytes
创建好 Secret 对象后,有两种⽅式来使⽤它:
以环境变量的形式
以Volume的形式挂载
环境变量
⾸先我们来测试下环境变量的⽅式,同样的,我们来使⽤⼀个简单的 busybox 镜像来测试下:(secret1- pod.yaml)
[root@master ~]# cat secret1.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret1-pod
spec:
restartPolicy: Never
containers:
- image: busybox
name: secret1
command: ["/bin/sh","-c","env"]
env: #注入变量
- name: USERNAME
valueFrom: #变量的来源
secretKeyRef: #来源于secret的mysecret
name: mysecret #名称是mysecret
key: username #变量是username=admin 输出admin
- name: PASSWORD
valueFrom:
secretKeyRef:
name: mysecret
key: password
[root@master ~]# kubectl apply -f secret1.yaml
pod/secret1-pod created
[root@master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
secret1-pod 0/1 Completed 0 18s
- 查看日志状态
[root@master ~]# kubectl logs secret1-pod
USERNAME=admin
···
PASSWORD=admin123
Volume 挂载
同样的我们⽤⼀个 Pod 来验证下 Volume 挂载,创建⼀个 Pod ⽂件:(secret2-pod.yaml)
[root@master ~]# cat secret2.yaml
apiVersion: v1
kind: Pod
metadata:
name: secret1-pod
spec:
restartPolicy: Never
containers:
- image: busybox
name: secret1
command: ["/bin/sh","-c","ls /etc/secrets"]
volumeMounts:
- name: secrets
mountPath: /etc/secrets
volumes:
- name: secrets
secret:
secretName: mysecret
[root@master ~]# kubectl apply -f secret2.yaml
pod/secret1-pod created
- 查看日志
[root@master ~]# kubectl logs secret1-pod
password
username
# Secret的docker-registry使用
kubernetes.io/dockerconfigjson
除了上⾯的 Opaque 这种类型外,我们还可以来创建⽤户 docker registry 认证的 Secret ,直接使 ⽤ kubectl create 命令创建即可
[root@master ~]# kubectl create secret docker-registry myregistry --docker-server=DOCKER_SERVER --docker-username=DOCKER_USER --docker-password=DOCKER_PASSWORD --docker-email=DOCKER_EMAIL
secret/myregistry created
[root@master ~]# kubectl get secret
NAME TYPE DATA AGE
default-token-4wt8g kubernetes.io/service-account-token 3 12h
myregistry kubernetes.io/dockerconfigjson 1 7s
注:
注意看上⾯的 TYPE 类型, myregistry 是不是对应的 kubernetes.io/dockerconfigjson
- 查看yaml的详细
[root@master ~]# kubectl get secrets myregistry -o yaml
apiVersion: v1
data:
.dockerconfigjson: eyJhdXRocyI6eyJET0NLRVJfU0VSVkVSIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fX0=
kind: Secret
metadata:
creationTimestamp: "2021-11-07T10:24:34Z"
managedFields:
- apiVersion: v1
fieldsType: FieldsV1
fieldsV1:
f:data:
.: {}
f:.dockerconfigjson: {}
f:type: {}
manager: kubectl
operation: Update
time: "2021-11-07T10:24:34Z"
name: myregistry
namespace: default
resourceVersion: "104658"
selfLink: /api/v1/namespaces/default/secrets/myregistry
uid: f57dffb2-4c1a-41e7-9d9f-a5316b343771
type: kubernetes.io/dockerconfigjson
- 可以把上⾯的 data.dockerconfigjson 下⾯的数据做⼀个 base64 解码,看看⾥⾯的数据是怎样的
[root@master ~]# echo eyJhdXRocyI6eyJET0NLRVJfU0VSVkVSIjp7InVzZXJuYW1lIjoiRE9DS0VSX1VTRVIiLCJwYXNzd29yZCI6IkRPQ0tFUl9QQVNTV09SRCIsImVtYWlsIjoiRE9DS0VSX0VNQUlMIiwiYXV0aCI6IlJFOURTMFZTWDFWVFJWSTZSRTlEUzBWU1gxQkJVMU5YVDFKRSJ9fX0= | base64 -d
{"auths":{"DOCKER_SERVER":{"username":"DOCKER_USER","password":"DOCKER_PASSWORD","email":"DOCKER_EMAIL","auth":"RE9DS0VSX1VTRVI6RE9DS0VSX1BBU1NXT1JE"}}}
- 如果我们需要拉取私有仓库中的 docker 镜像的话就需要使⽤到上⾯的 myregistry 这个 Secret
[root@master ~]# vim pod.yaml
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
spec:
containers:
- image: 172.25.253.19/library/nginx
name: nginx
imagePullSecrets:
- name: myregistrykey
# Secret 与 ConfigMap 对⽐
最后我们来对⽐下 Secret 和 ConfigMap 这两种资源对象的异同点:
相同点:
- key/value的形式
- 属于某个特定的namespace
- 可以导出到环境变量
- 可以通过⽬录/⽂件形式挂载
- 通过 volume 挂载的配置信息均可热更新
不同点:
- Secret 可以被 ServerAccount 关联
- Secret 可以存储 docker register 的鉴权信息,⽤在 ImagePullSecret 参数中,⽤于拉取私有仓库的镜像
- Secret ⽀持 Base64 加密
- Secret 分为 kubernetes.io/service-account-token、kubernetes.io/dockerconfigjson、Opaque 三种类型,⽽ Configmap 不区分类型