我们以MySQL数据库为例,在kubernetes集群中运行一个有状态的应用。
部署数据库几乎覆盖了kubernetes中常见的对象和概念:
- 配置文件--ConfigMap
- 保存密码--Secret
- 数据存储--持久卷(PV)和持久卷声明(PVC)
- 动态创建卷--存储类(StorageClass)
- 部署多个实例--StatefulSet
- 数据库访问--Headless Service
- 主从复制--初始化容器和sidecar
- 数据库调试--port-forward
- 部署Mysql集群--helm
目录
创建MySQL数据库
ConfigMap与Secret
ConfigMap
1.ConfigMap用法
Secret
Secret用法
卷(Volume)
1.临时卷(EV)
2.持久卷(PV)与持久卷声明(PVC)
创建持久卷声明(PVC)
使用PVC作为卷
绑定
存储类(StorageClass)
StatefulSet(有状态应用集)
稳定的存储
Pod 标识
部署和扩缩保证
创建MySQL数据库
●配置环境变量
○使用MySQL镜像创建Pod,需要使用环境变量设置MySQL的初始密码。
○环境变量配置示例
●挂载卷
○将数据存储在容器中,一旦容器被删除,数据也会被删除。
○将数据存储到卷(Volume)中,删除容器时,卷不会被删除。
hostPath卷
hostPath 卷将主机节点上的文件或目录挂载到 Pod 中。
hostPath配置示例 hostPath的type值:
DirectoryOrCreate | 目录不存在则自动创建。 |
Directory | 挂载已存在目录。不存在会报错。 |
FileOrCreate | 文件不存在则自动创建。 |
File | 挂载已存在的文件。不存在会报错。 |
Socket | 挂载 UNIX 套接字。例如挂载/var/run/docker.sock进程 |
# 创建mysql目录
mkdir /home/mysql
# 创建mysql-pod.yaml
cd /home/mysql
vi mysql-pod.yaml
mysql-pod.yaml内容如下
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: data-volume
volumes:
- name: data-volume
hostPath:
# 宿主机上目录位置
path: /home/mysql/data
type: DirectoryOrCreate
注意:hostPath 仅用于在单节点集群上进行开发和测试,不适用于多节点集群;
例如,当Pod被重新创建时,可能会被调度到与原先不同的节点上,导致新的Pod没有数据。
在多节点集群使用本地存储,可以使用
local
卷。
使用 kubectl apply -f mysql-pod.yaml 再 kubectl get pod -owide 查看是否创建成功
ConfigMap与Secret
在Docker中,我们一般通过绑定挂载的方式将配置文件挂载到容器里。
在Kubernetes集群中,容器可能被调度到任意节点,配置文件需要能在集群任意节点上访问、分发和更新。
ConfigMap
ConfigMap 用来在键值对数据库(etcd)中保存非加密数据。一般用来保存配置文件。
ConfigMap 可以用作环境变量、命令行参数或者存储卷。
ConfigMap 将环境配置信息与 容器镜像 解耦,便于配置的修改。
ConfigMap 在设计上不是用来保存大量数据的。
在 ConfigMap 中保存的数据不可超过 1 MiB。
超出此限制,需要考虑挂载存储卷或者访问文件存储服务。
1.ConfigMap用法
●ConfigMap配置示例 ●Pod中使用ConfigMap
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
volumeMounts:
- mountPath: /var/lib/mysql
name: data-volume
# 镜像里的配置文件路径
- mountPath: /etc/mysql/conf.d
name: conf-volume
readOnly: true
volumes:
- name: conf-volume
configMap:
name: mysql-config
- name: data-volume
hostPath:
# directory location on host
path: /home/mysql/data
# this field is optional
type: DirectoryOrCreate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
mysql.cnf: |
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
init-connect='SET NAMES utf8mb4'
[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
# 修改configMap,配置文件会被自动更新
kubectl edit cm mysql-config
Secret
Secret 用于保存机密数据的对象。一般由于保存密码、令牌或密钥等。
data
字段用来存储 base64 编码数据。
stringData
存储未编码的字符串。
Secret 意味着你不需要在应用程序代码中包含机密数据,减少机密数据(如密码)泄露的风险。
Secret 可以用作环境变量、命令行参数或者存储卷文件。
Secret用法
- Secret配置示例
- 将Secret用作环境变量
# 加密
echo -n '123456' | base64
# 解密
echo 'MTIzNDU2' | base64 --decode
apiVersion: v1
kind: Secret
metadata:
name: mysql-password
type: Opaque
data:
#加密后的密码
PASSWORD: MTIzNDU2Cg==
---
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-password
key: PASSWORD
optional: false # 此值为默认值;表示secret已经存在了
volumeMounts:
- mountPath: /var/lib/mysql
name: data-volume
- mountPath: /etc/mysql/conf.d
name: conf-volume
readOnly: true
volumes:
- name: conf-volume
configMap:
name: mysql-config
- name: data-volume
hostPath:
# directory location on host
path: /home/mysql/data
# this field is optional
type: DirectoryOrCreate
---
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql-config
data:
mysql.cnf: |
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_general_ci
init-connect='SET NAMES utf8mb4'
[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
环境变量中使用secret并且secret修改后 环境变量不会自动更新 需要重启pod
卷(Volume)
将数据存储在容器中,一旦容器被删除,数据也会被删除。
卷是独立于容器之外的一块存储区域,通过挂载(Mount)的方式供Pod中的容器使用。
●使用场景
○卷可以在多个容器之间共享数据。
○卷可以将容器数据存储在外部存储或云存储上。
○卷更容易备份或迁移。
常见的卷类型
●临时卷(Ephemeral Volume):与 Pod 一起创建和删除,生命周期与 Pod 相同
○emptyDir - 作为缓存或存储日志
○configMap 、secret、 downwardAPI - 给Pod注入数据
●持久卷(Persistent Volume):删除Pod后,持久卷不会被删除
○本地存储 - hostPath、 local ○网络存储 - NFS ○分布式存储 - Ceph(cephfs文件存储、rbd块存储)
●投射卷(Projected Volumes):projected 卷可以将多个卷映射到同一个目录上
后端存储
一个集群中可以包含多种存储(如local、NFS、Ceph或云存储)。
每种存储都对应一个存储类(StorageClass) ,存储类用来创建和管理持久卷,是集群与存储服务之间的桥梁。
管理员创建持久卷(PV)时,通过设置不同的StorageClass来创建不同类型的持久卷。
1.临时卷(EV)
●临时卷(Ephemeral Volume)
○与 Pod 一起创建和删除,生命周期与 Pod 相同
○emptyDir - 初始内容为空的本地临时目录
○configMap - 为Pod注入配置文件
○secret - 为Pod注入加密数据
emptyDir
emptyDir会创建一个初始状态为空的目录,存储空间来自本地的 kubelet 根目录或内存(需要将emptyDir.medium设置为"Memory")。
通常使用本地临时存储来设置缓存、保存日志等。
例如,将redis的存储目录设置为emptyDir
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
spec:
containers:
- name: redis
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
configMap卷和secret卷
注意:这里的configMap和secret代表的是卷的类型,不是configMap和secret对象。
删除Pod并不会删除ConfigMap对象和secret对象。
configMap卷和Secret卷是一种特殊类型的卷,kubelet引用configMap和Secret中定义的内容,在Pod所在节点上生成一个临时卷,将数据注入到Pod中。删除Pod,临时卷也会被删除。
临时卷位于Pod所在节点的/var/lib/kubelet/pods目录下。
2.持久卷(PV)与持久卷声明(PVC)
●持久卷(Persistent Volume):删除Pod后,卷不会被删除
○本地存储
■hostPath - 节点主机上的目录或文件
(仅供单节点测试使用;多节点集群请用 local 卷代替)
■local - 节点上挂载的本地存储设备(不支持动态创建卷)
○网络存储
■NFS - 网络文件系统 (NFS)
○分布式存储
■Ceph(cephfs文件存储、rbd块存储)
持久卷(PersistentVolume,PV) 是集群中的一块存储。可以理解为一块虚拟硬盘。
持久卷可以由管理员事先创建, 或者使用存储类(Storage Class)根据用户请求来动态创建。
持久卷属于集群的公共资源,并不属于某个namespace
;
持久卷声明(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。
PVC声明好比申请单,它更贴近云服务的使用场景,使用资源先申请,便于统计和计费。
Pod 将 PVC 声明当做存储卷来使用,PVC 可以请求指定容量的存储空间和访问模式 。PVC对象是带有namespace
的。
创建持久卷(PV)
创建持久卷(PV)是服务端的行为,通常集群管理员会提前创建一些常用规格的持久卷以备使用。
hostPath仅供单节点测试使用,当Pod被重新创建时,可能会被调度到与原先不同的节点上,导致新的Pod没有数据。多节点集群使用本地存储,可以使用local卷
创建local类型的持久卷,需要先创建存储类(StorageClass)。
本地存储类示例
# 创建本地存储类
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: Immediate
local卷不支持动态创建,必须手动创建持久卷(PV)。
创建local类型的持久卷,必须设置nodeAffinity(节点亲和性)。
调度器使用nodeAffinity信息来将使用local卷的 Pod 调度到持久卷所在的节点上,不会出现Pod被调度到别的节点上的情况。
注意:local卷也存在自身的问题,当Pod所在节点上的存储出现故障或者整个节点不可用时,Pod和卷都会失效,仍然会丢失数据,因此最安全的做法还是将数据存储到集群之外的存储或云存储上。
●创建PV
PV示例/local卷示例
local-storage.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv-1
spec:
capacity:
storage: 4Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage #通过指定存储类来设置卷的类型
local:
path: /mnt/disks/ssd1
# 节点亲和性 避免pod和存储调度到不同节点
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- k8s-worker1
去worker1节点创建 /mnt/disks/ssd1 目录
主节点使用 kubectl apply -f local-storage.yaml
使用 kubectl get pv前后对比
创建持久卷声明(PVC)
持久卷声明(PVC)是用户端的行为,用户在创建Pod时,无法知道集群中PV的状态(名称、容量、是否可用等),用户也无需关心这些内容,只需要在声明中提出申请,集群会自动匹配符合需求的持久卷(PV)。
Pod使用持久卷声明(PVC)作为存储卷。
PVC示例
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-pv-claim
spec:
storageClassName: local-storage # 与PV中的storageClassName一致
# 访问模式
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
使用PVC作为卷
Pod 的配置文件指定了 PersistentVolumeClaim,但没有指定 PersistentVolume。
对 Pod 而言,PersistentVolumeClaim 就是一个存储卷。
PVC卷示例
修改mysql-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: local-mysql-data
volumes:
- name: local-mysql-data
# hostpath 改为下面的
persistentVolumeClaim:
claimName: local-pv-claim
重新创建mysql-pod 使用kubectl describe pod mysql-pod查看持久卷声明
绑定
创建持久卷声明(PVC)之后,集群会查找满足要求的持久卷(PV),将 PVC 绑定到该 PV上。
PVC与PV之间的绑定是一对一的映射关系,绑定具有排他性,一旦绑定关系建立,该PV无法被其他PVC使用。
PVC可能会匹配到比声明容量大的持久卷,但是不会匹配比声明容量小的持久卷。
例如,即使集群上存在多个 50 G大小的 PV ,他们加起来的容量大于100G,也无法匹配100 G大小的 PVC。
找不到满足要求的 PV ,PVC会无限期地处于未绑定状态(Pending) , 直到出现了满足要求的 PV时,PVC才会被绑定。
访问模式
- ReadWriteOnce
- 卷可以被一个节点以读写方式挂载,并允许同一节点上的多个 Pod 访问。
- ReadOnlyMany
- 卷可以被多个节点以只读方式挂载。
- ReadWriteMany
- 卷可以被多个节点以读写方式挂载。
- ReadWriteOncePod
- 卷可以被单个 Pod 以读写方式挂载。 集群中只有一个 Pod 可以读取或写入该 PVC。
- 只支持 CSI 卷以及需要 Kubernetes 1.22 以上版本。
卷的状态
- Available(可用)-- 卷是一个空闲资源,尚未绑定到任何;
- Bound(已绑定)-- 该卷已经绑定到某个持久卷声明上;
- Released(已释放)-- 所绑定的声明已被删除,但是资源尚未被集群回收;
- Failed(失败)-- 卷的自动回收操作失败。
卷模式
卷模式(volumeMode)是一个可选参数。
针对 PV 持久卷,Kubernetes 支持两种卷模式(volumeModes):
- Filesystem(文件系统)
默认的卷模式。
- Block(块)
将卷作为原始块设备来使用。
存储类(StorageClass)
创建持久卷(PV)
●静态创建
○管理员预先手动创建
○手动创建麻烦、不够灵活(local卷不支持动态创建,必须手动创建PV)
○资源浪费(例如一个PVC可能匹配到比声明容量大的卷)
○对自动化工具不够友好
●动态创建
○根据用户请求按需创建持久卷,在用户请求时自动创建
○动态创建需要使用存储类(StorageClass)
○用户需要在持久卷声明(PVC)中指定存储类来自动创建声明中的卷。
○如果没有指定存储类,使用集群中默认的存储类。
存储类(StorageClass)
一个集群可以存在多个存储类(StorageClass)来创建和管理不同类型的存储。
每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件创建持久卷。 该字段必须指定。
Local Path Provisioner
K3s自带了一个名为local-path的存储类(StorageClass),它支持动态创建基于hostPath或local的持久卷。
创建PVC后,会自动创建PV,不需要再去手动的创建PV。
删除PVC,PV也会被自动删除。
创建local-path-pvc.yaml文件
当文件中内容为
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-pvc-1
spec:
storageClassName: local-path # 与PV中的storageClassName一致
# 访问模式
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 2Gi
kubectl apply -f ocal-path-pvc.yaml时kubectl get pvc 状态为pending, kubectl get pv 为空
这是因为local-path的卷的绑定模式为WaitForFirstConsumer只有被挂载到pod中创建pod时才会被创建
卷绑定模式
volumeBindingMode用于控制什么时候动态创建卷和绑定卷。
●Immediate立即创建
创建PVC后,立即创建PV并完成绑定。
●WaitForFirstConsumer 延迟创建
当使用该PVC的 Pod 被创建时,才会自动创建PV并完成绑定。
将mysql-pod.yaml里的 volumes下持久卷声明改为刚才创建的lcoal-pvc-1
volumes:
- name: conf-volume
configMap:
name: mysql-config
- name: data-volume
persistentVolumeClaim:
# 将下面改为刚刚创建的local-pvc-1
claimName: local-pvc-1
再次创建mysql-pod查看pvc与pv 自动创建了pv绑定成功
注意删除pod后pvc与pv不会自动删除 当手动删除pvc后pv会自动释放删除(默认情况下删除声明也会自动删除声明关联的持久卷, 可以在回收策略中设置是否自动删除)
回收策略(Reclaim Policy)
回收策略告诉集群,当用户删除PVC 对象时, 从PVC中释放出来的PV将被如何处理。
- 删除(Delete)
如果没有指定,默认为Delete
当PVC被删除时,关联的PV 对象也会被自动删除。
- 保留(Retain)
当 PVC 对象被删除时,PV 卷仍然存在,数据卷状态变为"已释放(Released
)"。
此时卷上仍保留有数据,该卷还不能用于其他PVC。需要手动删除PV。
StatefulSet(有状态应用集)
StatefulSet
如果我们需要部署多个MySQL实例,就需要用到StatefulSet。
StatefulSet 是用来管理有状态的应用。一般用于管理数据库、缓存等。
与 Deployment 类似, StatefulSet用来管理 Pod 集合的部署和扩缩。
Deployment用来部署无状态应用。StatefulSet用来有状态应用。
创建StatefulSet
StatefulSet配置模版c
创建statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql-sts
spec:
selector:
matchLabels:
app: mysql # 必须匹配 .spec.template.metadata.labels
serviceName: mysql-svc
replicas: 3 # 默认值是 1
minReadySeconds: 10 # 默认值是 0
template:
metadata:
labels:
app: mysql # 必须匹配 .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql #容器中的目录
name: data-volume
volumeClaimTemplates:
- metadata:
name: data-volume
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 2Gi
用两个窗口查看pod创建过程statefulset是依次创建的只有当第一个创建完毕才会创建第二个,删除和缩容则是逆序进行
稳定的存储
在 StatefulSet 中使用 VolumeClaimTemplate,为每个 Pod 创建持久卷声明(PVC)。
每个 Pod 将会得到基于local-path 存储类动态创建的持久卷(PV)。 Pod 创建(或重新调度)时,会挂载与其声明相关联的持久卷。
请注意,当 Pod 或者 StatefulSet 被删除时,持久卷声明和关联的持久卷不会被删除。
Pod 标识
在具有 N 个副本的 StatefulSet中,每个 Pod 会被分配一个从 0 到 N-1 的整数序号,该序号在此 StatefulSet 上是唯一的。
StatefulSet 中的每个 Pod 主机名的格式为StatefulSet名称-序号
。
上例将会创建三个名称分别为 mysql-sts-0、mysql-sts-1、mysql-sts-2 的 Pod。
部署和扩缩保证
- 对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0..N-1。
- 当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0。
- 在将扩缩操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。
- 在一个 Pod 终止之前,所有的继任者必须完全关闭。
在上面的mysql示例被创建后,会按照 mysql-0、mysql-1、mysql-2 的顺序部署三个 Pod。
在 mysql-0 进入 Running 和 Ready 状态前不会部署 mysql-1。
在 mysql-1 进入 Running 和 Ready 状态前不会部署 mysql-2。
如果 mysql-1 已经处于 Running 和 Ready 状态,而 mysql-2 尚未部署,在此期间发生了 mysql-0 运行失败,那么 mysql-2 将不会被部署,要等到 mysql-0 部署完成并进入 Running 和 Ready 状态后,才会部署 mysql-2。
如果用户想将示例中的 StatefulSet 扩缩为 replicas=1,首先被终止的是 mysql-2。
在 mysql-2 没有被完全停止和删除前,mysql-1 不会被终止。
当 mysql-2 已被终止和删除、mysql-1 尚未被终止,如果在此期间发生 mysql-0 运行失败, 那么就不会终止 mysql-1,必须等到 mysql-0 进入 Running 和 Ready 状态后才会终止 web-1。