# Kubernetes存储
# 为什么需要存储卷
容器部署过程中一般有以下三种数据:
- 启动时需要的初始数据,例如配置文件
- 启动过程中产生的临时数据,该临时数据需要多个容器间共享
- 启动过程中产生的持久化数据,例如MySQL的data
# 数据卷概述
Kubernetes中的Volume提供了在容器中挂载外部存储的能力
Pod需要设置卷来源(spec.volume)和挂载点(spec.containers.volumeMounts)两个信息后才可以使用相应的Volume
# 常用的数据卷
- 本地(hostPath,emptyDir)
- 网络(NFS,Ceph,GlusterFS)
- 公有云(AWS EBS)
- K8S资源(configmap,secret)
# 使用数据卷的流程
镜像里程序写的目录 -> mountPath 挂载到写的目录 -> 引用的volumes对应的name -> 相关卷的配置
# 临时存储卷: emptyDir
# emptyDir卷
是一个临时存储卷,与Pod生命周期绑定一起,如果Pod删除了卷也会被删除。
# 应用场景
Pod中容器之间数据共享
# 一个简单的yaml示例
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- name: wirte
image: centos
command: ["bash","-c","for i in {1..100}; do echo $i >> /data/hello; sleep 1; done"]
volumeMounts:
- name: data
mountPath: /data
- name: read
image: centos
command: ["bash","-c","tail -f /data/hello"]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
emptyDir: {}
# 简单说明
- 创建一个名为my-pod的pod
- pod中有两个容器,一个名为write,用于写数据,一个名为read,用于读取数据
- 其中write使用镜像centos作为基础镜像,将1-100写入/data目录的hello文件中,通过名称
name: data
挂载到共享存储volume下 - read也是用centos作为基础镜像,读取/data目录的hello文件中的内容,也是通过
name: data
挂载到共享存储volume下 - volumes配置用于创建名为data的emptyDir卷
# 执行一下
$ kubectl apply -f emptydirep.yaml
pod/my-pod created
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
my-pod 2/2 Running 0 63s
......
$ kubectl logs -f my-pod -c write
$ kubectl logs -f my-pod -c write --tail /data/hello
1
......
100
$ kubectl logs -f my-pod -c read
1
......
100
# 执行结果说明
-c
命令用于指定pod中查看的容器名称- write容器不会有任何输出
- 查看write容器中,/data/hello的内容,可以正常输出1-100
- read容器正常输出1-100
# 节点存储卷: hostPath
# hostPath卷
挂载Node文件系统(Pod所在节点)上文件或者目录到Pod中的容器。
# 一个简单的yaml示例
apiVersion: v1
kind: Pod
metadata:
name: my-pod-host-path
spec:
containers:
- name: busybox
image: busybox
args:
- bin/sh
- -c
- sleep 3600
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
hostPath:
path: /tmp
type: Directory
# yaml说明
- volumeMounts指定数据卷来源,通过
name: data
挂载到存储卷 - volumes用于创建名为data的数据卷,hostPath用于指定数据持久化的目录
# 执行一下
$ kubectl apply -f volume-host-path.yaml
pod/my-pod-host-path created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-pod-host-path 1/1 Running 0 10m 10.244.17.181 liuxiaolu-node <none> <none>
......
$ cd /tmp
$ ls
vmware-tools-distrib
$ kubectl exec -it my-pod-host-path -- sh
/ # cd /data
/data # ls
vmware-tools-distrib
/data # touch create-in-pod
/data # ls
create-in-pod vmware-tools-distrib
$ cd /tmp
$ ls
create-in-pod vmware-tools-distrib
$ touch create-in-host
$ kubectl exec -it my-pod-host-path -- sh
/ # cd /data
/data # ls
create-in-host create-in-pod vmware-tools-distrib
# 执行结果说明
- 创建pod
- 查看pod是否创建完成,同时查看pod所在的节点,我们发现pod在liuxiaolu-node这个节点
- 验证pod中/data目录中的内容是否和liuxiaolu-node中/tmp目录的内容一致
- 登录到liuxiaolu-node节点,并打开/tmp目录,ls查看这个目录下的文件
- 进入pod,打开/data目录,ls查看这个目录下的文件
- 验证结果是一致的,都只有vmware-tools-distrib这个文件夹
- 验证在pod的/data目录中创建的文件,在liuxiaolu—node节点的/tmp是否能查询到
- 在pod中使用touch命令创建文件touch create-in-pod,并使用ls命令查看是否创建成功
- 在liuxiaolu-node节点打开tmp目录,使用ls命令查询这个目录下的文件
- 验证通过,在pod中创建的文件,,在liuxiaolu—node节点的/tmp能查询到
- 验证在liuxiaolu—node节点的/tmp目录中创建的文件,在pod的/data目录中是否能查询到
- 在liuxiaolu—node节点的/tmp目录中使用touch命令创建文件create-in-host
- 在pod的/data目录下,使用ls命令查看
- 验证通过,在host中创建的文件,在pod中能够查询到
- 结论是挂载成功
# 应用场景
Pod中容器需要访问宿主机文件
- 比如一个监控节点,收集宿主机的信息
# 网络存储卷: NFS
NFS卷
提供对NFS挂载支持,可以自动将NFS共享路径 挂载到Pod中
NFS
是一个主流的文件共享服务器。
# 安装
一般来说,为了追求性能,都会在一台新的机器上去安装nfs共享服务器,但我们这里为了学习,就在liuxiaolu-node这个节点上去安装nfs服务器。
注意,每个节点都需要安装nfs-utils包。我们这里学习的时候,是两台服务器,一个master(liuxiaolu-master)节点,一个node(liuxiaolu-node)节点。
这个场景下,需要如下步骤:
- 在liuxiaolu-node这个节点执行下面的安装命令
- 在其他节点(liuxiaolu-master)执行
yum install nfs-utils
即可
# 安装命令
# 安装nfs-utils
$ yum install nfs-utils
# 创建共享目录/infs/kubernetes
$ mkdir -p /infs/kubernetes
# 设置nfs共享目录,*代表所有ip可访问,如果写一个ip,或者网段,那么只有指定的ip/网段可访问,括号中内容为读写权限
# rw: 可读写
# no_root_squash: 当登录NFS主机使用共享目录的使用者是root时,其权限将被转换成为匿名使用者
$ vi /etc/exports
/infs/kubernetes *(rw,no_root_squash)
# 启动nfs
$ systemctl start nfs
# 设置nfs开机启动
$ systemctl enable nfs
# 安装测试
在liuxiaolu-master节点上执行命令
# 在本机创建需要使用nfs共享服务器的目录
$ mkdir /liuxiaolu-test-mount
# 将本机的liuxiaolu-test-mount目录挂载到liuxiaolu-node节点的/infs/kubernetes目录
# 注意,liuxiaolu-node是通过本地hosts域名映射到node节点上,如果没有配置域名银映射,这里需要替换为ip地址
$ mount -t nfs liuxiaolu-node: /infs/kubernetes /liuxiaolu-test-mount
# 打开本机的liuxiaolu-test-mount目录
$ cd /liuxiaolu-test-mount
# 任意创建一个测试文件
$ touch liuxiaolu-mount-test.text
在liuxiaolu-node节点上执行命令
# 打开共享目录
$ cd /infs/kubernetes
# 若能看到我们在master节点创建的测试文件liuxiaolu-mount-test.text即为挂载成功
$ ls
# 卸载
在挂载共享服务器的节点(liuxiaolu-master)中执行命令
# 注意,如果是待卸载的目录再进行卸载操作
$ cd ~
# 卸载
$ umount /liuxiaolu-test-mount
# 再次打开目录liuxiaolu-test-mount
$ cd /liuxiaolu-test-mount
# 查看当前文件夹下的内容,如果查询不到之前创建的liuxiaolu-mount-test.text文件,即为挂载成功。
# 我们也可以在nfs共享服务器查看共享目录下是否还有文件liuxiaolu-mount-test.text,如果有,也证明了,我们之前上传的文件确实上传到了共享服务器,而不是本地。
# 若执行命令重新挂载,也可以重新看到我们之前创建的文件liuxiaolu-mount-test.text
$ ls
说明 NFS卷使用过程中和本地文件夹使用没有任何区别,只是通过网络传输将文件存到了共享服务器中
# 使用k8s的NFS存储卷
# 使用命令创建一个标准的deployment的yaml
$ kubectl create deployment deploy-nfs --image=nginx --dry-run=client -o yaml > deploy-nfs.yaml
# 修改yaml文件,修改后的内容参考后面的给出的yaml
$ vi deploy-nfs.yaml
# 创建deploy-nfs
$ kubectl apply -f deploy-nfs.yaml
deployment.apps/deploy-nfs created
# 查看pod信息,可以看到三个节点都被拉起来了,我们随便找一个pod进行curl访问测试
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deploy-nfs-5fc65d6768-45cvg 1/1 Running 0 3m24s 10.244.17.184 liuxiaolu-node <none> <none>
deploy-nfs-5fc65d6768-rffwv 1/1 Running 0 3m24s 10.244.17.182 liuxiaolu-node <none> <none>
deploy-nfs-5fc65d6768-zqkbr 1/1 Running 0 3m24s 10.244.17.183 liuxiaolu-node <none> <none>
...
# 这里的访问结果是403无法访问
$ curl 10.244.17.184
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.21.0</center>
</body>
</html>
# 接下来我们进入这个pod的网站根目录,看下里面的内容会发现这是我们之前在master节点随便创建的文件
$ kubectl exec -it deploy-nfs-5fc65d6768-45cvg -- bash
$ cd /usr/share/nginx/html
$ ls
liuxiaolu-mount-test.text
# 在nfs服务器节点(liuxiaolu-node)执行命令删除之前创建的文件liuxiaolu-mount-test.text
[root@liuxiaolu-node kubernetes]# ls
liuxiaolu-mount-test.text
[root@liuxiaolu-node kubernetes]# rm liuxiaolu-mount-test.text
rm: remove regular empty file 'liuxiaolu-mount-test.text'? y
[root@liuxiaolu-node kubernetes]# ls
# 再次在容器内部执行,就会发现文件没有了,这也从侧面印证我们挂载成功了
$ ls
# 接下来我们再nfs服务器上创建一个index.html
[root@liuxiaolu-node kubernetes] vi index.html
hello liuxiaolu
# 再次对之前的pod进行curl,会发现,可以正常响应结果了。这就是,我们在nfs服务器上中的任何操作,都会被同步到使用共享目录的节点中。
$ curl 10.244.17.184
hello liuxiaolu
# yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: deploy-nfs
name: deploy-nfs
spec:
replicas: 3
selector:
matchLabels:
app: deploy-nfs
template:
metadata:
labels:
app: deploy-nfs
spec:
containers:
- image: nginx
name: nginx
resources: {}
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
nfs:
server: liuxiaolu-node
path: /infs/kubernetes
# 使用k8s的NFS存储卷的目标
- 将pod启动后的数据持久化到远程存储
- 可以实现pod多副本共享数据
# NFS的缺点
需要定义服务器相关的信息和目录,现在一般部署是由开发者去进行安装部署应用,开发者一般不知道k8s的相关安装部署信息,如果将这部分信息同步给开发者也不利于k8s的安全运维。
# 持久卷概述
# PersistentVolume(PV)
对存储资源创建和使用的抽象,使得存储作为集群中的资源管理
# PersistentVolumeClaim(PVC)
让用户不需要关心具体的Volume实现细节
# PV与PVC使用流程
# 简单搭建
# 准备deploy-pvc.yaml
# 复制之前的yaml文件
$ cp deploy-nfs.yaml deploy-pvc.yaml
# 编辑,将存储卷改为使用pvc,内容参考后面给出的yaml
$ vi deploy-pvc.yaml
# 创建
$ kubectl apply -f deploy-pvc.yaml
deployment.apps/deploy-pvc created
persistentvolumeclaim/my-pvc created
# 会发现三个pod一直处于pending状态,这是由于没有正确绑定pv导致,通过describe查看某一个pod也可以看到错误原因是
# 2 pod has unbound immediate PersistentVolumeClaims.
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
deploy-pvc-6994b7ff4b-7p4lz 0/1 Pending 0 2m58s
deploy-pvc-6994b7ff4b-h78j4 0/1 Pending 0 2m58s
deploy-pvc-6994b7ff4b-zqq2p 0/1 Pending 0 2m58s
# 内容参考后面给出的yaml
$ vi deploy-pv.yaml
# 再次查看pod,会发现已经在运行中了,pv和pvc绑定成功
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
deploy-pvc-6994b7ff4b-7p4lz 1/1 Running 0 9m46s
deploy-pvc-6994b7ff4b-h78j4 1/1 Running 0 9m46s
deploy-pvc-6994b7ff4b-zqq2p 1/1 Running 0 9m46s
# YAML
# PVC
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: deploy-pvc
name: deploy-pvc
spec:
replicas: 3
selector:
matchLabels:
app: deploy-pvc
template:
metadata:
labels:
app: deploy-pvc
spec:
containers:
- image: nginx
name: nginx
resources: {}
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
# PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: my-pv0000001
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteMany
nfs:
path: /infs/kubernetes
# PV与PVC的绑定
不通过name绑定,所以pv任意命名即可
pv与pvc一对一绑定,pv在绑定的情况下,不可再分配
pv与pvc绑是怎么绑定的?
- 存储空间
- 访问模式
pv与pvc通过存储空间匹配的匹配策略是怎么样的?
- 匹配最接近的pv容量,如果没有匹配的容量,寻找超出容量需求的pv中,最接近的pv
- 如果无法满足容量需求,pod会处于pending状态
eg: 比如有四个pv分别是1号(5Gi),2号(20Gi),3号(30Gi),4号(50Gi)
- 首先来了一个需求5Gi的pvc,会绑定1号pv
- 然后来了一个需求35Gi的pvc,会绑定4号pv
- 然后来了一个需求18Gi的pvc,会绑定2号pv
存储空间的字段是限制使用容量?
- 主要用于做匹配用的,实际使用限制取决于后端存储
# PV生命周期
# AccessModes(访问模式)
AccessModes是用来对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限
访问权限包括下面几种方式
- ReadWriteOnce(RWO): 读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX): 只读权限,可以被多个节点挂载
- ReadWriteMany(RWX): 读写权限,可以被多个节点挂载
# RECLAIM POLICY(回收策略)
目前 PV 支持的策略有三种
- Retain(保留): 保留数据,需要管理员手工清理数据
- Recycle(回收): 清除PV中的数据,效果相当于执行
rm -rf /infs/kuberneres/*
- Delete(删除): 与PV相连的后端存储同时删除
# STATUS(状态)
一个PV的生命周期中,可能会处于4中不同的阶段
- Available(可用): 表示可用状态,还未被任何PVC绑定
- Bound(已绑定): 表示PV已经被PVC绑定
- Released(已释放): PVC被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该PV的自动回收失败
# 静态供给
现在PV使用方式称为静态供给,需要K8s运维工程师提前创建一堆PV,供开发者使用,模式比较僵化。
# PV动态供给
# 图示
# NFS动态供给流程图
PV静态供给明显的缺点是维护成本太高了! 因此,K8s开始支持PV动态供给,使用StorageClass对象实现。
查看支持动态供给的StorageClass (opens new window)
K8s默认不支持NFS动态供给,需要单独部署社区开发的插件。
# 部署
# 创建并进入动态供给目录
$ mkdir nfs-subdir-external-provisioner && cd nfs-subdir-external-provisioner
# 创建本次所需的三个yaml,内容参考后面给出的yaml
$ vi class.yaml
$ vi deployment.yaml
$ vi rbac.yaml
# 授权访问apiserver
$ kubectl apply -f rbac.yaml
# 创建存储类
$ kubectl apply -f class.yaml
# 部署插件,需修改里面NFS服务器地址与共享目录
$ kubectl apply -f deployment.yaml
# 查看存储类
$ kubectl get sc
# 查看是否创建完成
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-86d547fbb-tlgqr 1/1 Running 0 2m21s
...
# YAML
# 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"
# 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: liuxiaolu-node
- name: NFS_PATH
value: /infs/kubernetes
volumes:
- name: nfs-client-root
nfs:
server: liuxiaolu-node
path: /infs/kubernetes
# 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
# 验证
# 复制之前静态供给的yaml
$ cp deploy-pvc.yaml deploy-pv-sl.yaml
# 修改yaml,内容在后面
$ vi deploy-pv-sl.yaml
$ kubectl apply -f deploy-pv.sl.yaml
# 查看pv,pvc发现已经动态生成了一个pv,pvc
$ kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/my-pv0000001 5Gi RWX Retain Bound default/my-pvc 102m
persistentvolume/pvc-e52e7a5d-3675-41d8-b9a4-adb7fd6de3bb 5Gi RWX Delete Bound default/my-pvc-sl managed-nfs-storage 92s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/my-pvc Bound my-pv0000001 5Gi RWX 110m
persistentvolumeclaim/my-pvc-sl Bound pvc-e52e7a5d-3675-41d8-b9a4-adb7fd6de3bb 5Gi RWX managed-nfs-storage 92s
# 再次查看共享目录,发现生成了一个默认路径,这个路径就是新的deploy所使用的共享目录
[root@liuxiaolu-node kubernetes]# ls
default-my-pvc-sl-pvc-e52e7a5d-3675-41d8-b9a4-adb7fd6de3bb index.html
# YAML
# deploy-pv.sl.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: deploy-pvc-sl
name: deploy-pvc-sl
spec:
replicas: 3
selector:
matchLabels:
app: deploy-pvc-sl
template:
metadata:
labels:
app: deploy-pvc-sl
spec:
containers:
- image: nginx
name: nginx
resources: {}
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumes:
- name: data
persistentVolumeClaim:
claimName: my-pvc-sl
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: my-pvc-sl
spec:
# kubectl get sc to describe sc name exists
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteMany
resources:
requests:
storage: 5Gi
# 有状态应用部署: StatefulSet工作负载控制器
# 无状态与有状态
# Deployment控制器设计原则
管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运行,可随意扩容和缩容,这种应用称为“无状态”。
例如Web服务,在实际的场景中,这并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群
# StatefulSet
# 解决的问题
- 部署有状态应用
- 解决Pod独立生命周期,保持Pod启动顺序和唯一性
- 稳定,唯一的网络标识符,持久存储
- 有序,优雅的部署和扩展、删除和终止
- 有序,滚动更新
# 应用场景
- 分布式应用
- 数据库集群
- 比如部署一个mysql主从,可能要考虑以下问题
- 主和从实例启动顺序
- 主和从实例数据目录必须唯一
- 从连接主实例需要指定IP或者主机名
# 优点
稳定的网络ID
使用Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份。 并且添加serviceName: “nginx”字段指定StatefulSet控制器要使用这个Headless Service。 DNS解析名称:
<statefulsetName-index>.<service-name> .<namespace-name>.svc.cluster.local
稳定的存储
StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用 VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC。
# 部署
# 预生成两个文件,基于文件进行修改创建StatefulSet的yaml
$ kubectl create deployment deploy-sts --image=nginx --dry-run=client -o yaml > deploy-sts.yaml
$ kubectl expose deployment deploy-pvc --port=80 --target-port=80 --dry-run=client -o yaml > expose-sts.yaml
# 将预生成的expose-sts.yaml合并到deploy-sts.yaml,并修改yaml
$ vi deploy-sts.yaml
# 执行创建命令
$ kubectl apply -f deploy-sts.yaml
statefulset.apps/deploy-sts created
service/deploy-sts created
# 查看age可以看到,pod启动的时间不一致,这是由于是有序启动的,0启动完成之后,才会启动1,1启动完成之后,才会启动2,与pod编号相同
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
deploy-sts-0 1/1 Running 0 35s
deploy-sts-1 1/1 Running 0 16s
deploy-sts-2 1/1 Running 0 11s
...
# 查看nfs服务器的地址会发现,每个pod使用的不同的存储,这也印证了独立的存储这一点
[root@liuxiaolu-node kubernetes]# ls
default-data-deploy-sts-0-pvc-12eea52c-5c2d-4d41-afbc-81047e1d4ed7 default-data-deploy-sts-2-pvc-ff971b3f-ccf9-4e86-a9b3-6b9b4230c6de
default-data-deploy-sts-1-pvc-4c1b4a3b-960e-4686-b174-2d90c0ba9a7e
...
$ kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-12eea52c-5c2d-4d41-afbc-81047e1d4ed7 1Gi RWO Delete Bound default/data-deploy-sts-0 managed-nfs-storage 10m
persistentvolume/pvc-4c1b4a3b-960e-4686-b174-2d90c0ba9a7e 1Gi RWO Delete Bound default/data-deploy-sts-1 managed-nfs-storage 10m
persistentvolume/pvc-ff971b3f-ccf9-4e86-a9b3-6b9b4230c6de 1Gi RWO Delete Bound default/data-deploy-sts-2 managed-nfs-storage 9m55s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-deploy-sts-0 Bound pvc-12eea52c-5c2d-4d41-afbc-81047e1d4ed7 1Gi RWO managed-nfs-storage 10m
persistentvolumeclaim/data-deploy-sts-1 Bound pvc-4c1b4a3b-960e-4686-b174-2d90c0ba9a7e 1Gi RWO managed-nfs-storage 10m
persistentvolumeclaim/data-deploy-sts-2 Bound pvc-ff971b3f-ccf9-4e86-a9b3-6b9b4230c6de 1Gi RWO managed-nfs-storage 9m55s
# YMAL
# deploy-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app: deploy-sts
name: deploy-sts
spec:
serviceName: "deploy-sts"
replicas: 3
selector:
matchLabels:
app: deploy-sts
template:
metadata:
labels:
app: deploy-sts
spec:
containers:
- image: nginx
name: nginx
resources: {}
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "managed-nfs-storage"
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
labels:
app: deploy-sts
name: deploy-sts
spec:
clusterIP: None
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: deploy-sts
# StatefulSet与Deployment区别:有身份的!
身份三要素:
- 域名
- 主机名
- 存储(PVC)
# 应用程序配置文件存储:ConfigMap
创建ConfigMap后,数据实际会存储在K8s中Etcd,然后通过创建Pod时引用该数据。
# 应用场景
应用程序配置
# Pod使用configmap数据有两种方式
- 变量注入
- 数据卷挂载
# 简单使用
# 创建configmap
apiVersion: v1
kind: ConfigMap
metadata:
name: config-demo
data:
# 类属性键;每一个键都映射到一个简单的值
name: "test"
breif: "xx"
# 类文件键
redis.properties: |
port: 6379
host: 127.0.0.1
# 创建pod使用configmap的配置
apiVersion: v1
kind: Pod
metadata:
name: configmap-demo-pod
spec:
containers:
- name: demo
image: nginx
env:
# 这里是直接读取configmap
- name: NAME
valueFrom:
configMapKeyRef:
name: config-demo
key: name
- name: BREIF
valueFrom:
configMapKeyRef:
name: config-demo
key: breif
# 这后面的部分是通过数据卷挂载的方式读取configmap
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
- name: config
configMap:
name: config-demo
items:
- key: "redis.properties"
path: "redis.properties"
# 验证
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
configmap-demo-pod 1/1 Running 0 39s
$ kubectl exec -it configmap-demo-pod -- bash
$ echo $BREIF
xx
$ echo $NAME
test
$ cat /config/redis.properties
port: 6379
host: 127.0.0.1
# 生效
如果需要修改configmap,重启pod之后会生效
# 应用程序配置文件存储:Secret
与ConfigMap类似,区别在于Secret主要存储敏感数据,所有的数据要经过base64编码。
# 应用场景
凭据
# kubectl create secret 支持三种数据类型
- docker-registry:存储镜像仓库认证信息
- generic:从文件、目录或者字符串创建,例如存储用户名密码
- tls:存储证书,例如HTTPS证书
# 简单使用
# 创建一个用户名密码的凭据
# 将用户名密码进行编码:
$ echo -n 'admin' | base64
$ echo -n '1f2d1e2e67df' | base64
apiVersion: v1
kind: Secret
metadata:
name: db-user-pass
type: Opaque
data:
username: YWRtaW4=
password: MWYyZDFlMmU2N2Rm
# 读取
apiVersion: v1
kind: Pod
metadata:
name: secret-demo-pod
spec:
containers:
- name: demo
image: nginx
env:
# 这里是直接读取configmap
- name: username
valueFrom:
secretKeyRef:
name: db-user-pass
key: username
- name: password
valueFrom:
secretKeyRef:
name: db-user-pass
key: password
# 这后面的部分是通过数据卷挂载的方式读取configmap
volumeMounts:
- name: config
mountPath: "/config"
readOnly: true
volumes:
- name: config
secret:
secretName: db-user-pass
items:
- key: "username"
path: "username"
- key: "password"
path: "password"
# 验证
$ kubectl get pod
NAME READY STATUS RESTARTS AGE
secret-demo-pod 1/1 Running 0 23s
$ kubectl exec -it secret-demo-pod -- bash
$ echo $username
admin
$ echo $password
1f2d1e2e67df
$ cd /config
$ ls
password username
$ cat password
1f2d1e2e67df
$ cat username
admin