目录
Deployment 是一种无状态的应用,群集中pod都是一样的,所以适合当负载均衡,但是实际中还有不少是有状态的应用,群集中的pod并不一样的,所有讲到今天的主角StatefulSets
一、StatefulSets介绍
Deployment 认为,一个应用的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。需要的时候,Deployment 就可以通过 Pod 模板创建新的 Pod;不需要的时候,Deployment 就可以“杀掉”任意一个 Pod。
在实际的场景中,并不是所有的应用都可以满足这样的要求。尤其是分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。
还有就是数据存储类应用,它的多个实例,往往都会在本地磁盘上保存一份数据。而这些实例一旦被杀掉,即便重建出来,实例与数据之间的对应关系也已经丢失,从而导致应用失败。
所以,这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”(Stateful Application)。
Kubernetes 有状态与无状态介绍
无状态:deployment
- 认为所有pod都是一样的,不具备与其他实例有不同的关系。
- 没有顺序的要求。
- 不用考虑再哪个Node运行。
- 随意扩容缩容。有状态:SatefulSet
- 集群节点之间的关系。
- 数据不完全一致。
- 实例之间不对等的关系。
- 依靠外部存储的应用。
- 通过dns维持身份
StatefulSets更多的相关的知识,可以看它的官方中文,我这里不多介绍
二、StatefulSet 状态分类
2.1 拓扑状态
拓扑状态。这种情况意味着,应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。
2.2 存储状态
存储状态。这种情况意味着,应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。
所以,StatefulSet 的核心功能,就是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。
三、建立一个简单的拓扑状态的StatefulSet
说了那么多,我先建立一个简单的StatefulSet,绑定节点hostpath的,我在《hualinux 进阶 1.7:kubeadm1.18搭建k8s群集 》的基础上建立的
3.1 编写statefulSet的YAML文件
#节点上操作
#建立相关目录,我这里只有一个节点是vm821
mkdir -p /disk1/www/hualinux.com
echo 'vm821 index.html'>/disk1/www/hualinux.com/index.html
#在master上操作
mkdir -pv /disk1/myk8s
cd /disk1/myk8s/
cat>nginx-statefulset.yaml<<EOF
apiVersion: v1
kind: Service
metadata:
name: nginx-ser-none
labels:
web: nginx18
spec:
selector:
#查找匹配的标签的pod
web: nginx18
ports:
- protocol: TCP
#services对外端口
port: 80
#statefulSet一定要使用handless services
clusterIP: None
#多个Yaml文件可以用 --- 分隔
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx-ser-none"
replicas: 2
selector:
matchLabels:
web: nginx18
template:
metadata:
labels:
web: nginx18
spec:
containers:
- name: nginx
image: nginx:1.18
ports:
- containerPort: 80
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: hualinux
volumes:
- name: hualinux
hostPath:
# directory location on host
path: /disk1/www/hualinux.com
EOF
kubectl apply -f nginx-statefulset.yaml
查看状态得
#和Deployment不同的时pod是带有序列的
[root@vm82 myk8s]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 4m31s 10.44.0.1 vm821 <none> <none>
web-1 1/1 Running 0 2m38s 10.44.0.2 vm821 <none> <none>
handless无头服务,是没有群集IP的
[root@vm82 myk8s]# kubectl get svc -o wide
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 7d23h <none>
nginx-ser-none ClusterIP None <none> 80/TCP 4m34s web=nginx18
[root@vm82 myk8s]#
[root@vm82 myk8s]# kubectl get statefulSet -o wide
NAME READY AGE CONTAINERS IMAGES
web 2/2 4m39s nginx nginx:1.18
3.2 pod对应的域名
看到没状态和有状态的区别的吗,有状态它是有序列的,我们再看一下它的hosts
[root@vm82 myk8s]# kubectl exec web-0 -c nginx -- grep web /etc/hosts
#这个有域名的域名格式为<pod名>.<services名>.<namespaces>.svc.cluster.local
10.44.0.1 web-0.nginx-ser-none.default.svc.cluster.local web-0
[root@vm82 myk8s]# kubectl exec web-1 -c nginx -- grep web /etc/hosts
10.44.0.2 web-1.nginx-ser-none.default.svc.cluster.local web-
上面中可以看到域名绑定了本地docker的hosts文件,格式如下:
这个有域名的域名格式为<pod名>.<services名>.<namespaces>.svc.cluster.local
具体的可以看官网的staefulSet中的稳定的网络 ID
StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。组合主机名的格式为$(StatefulSet 名称)-$(序号)
。上例将会创建三个名称分别为 web-0、web-1、web-2
的 Pod。 StatefulSet 可以使用 headless 服务 控制它的 Pod 的网络域。管理域的这个服务的格式为: $(服务名称).$(命名空间).svc.cluster.local
,其中 cluster.local
是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:$(pod 名称).$(所属服务的 DNS 域名)
,其中所属服务由 StatefulSet 的 serviceName
域来设定。
下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例:
Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
---|---|---|---|---|---|
cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0..N-1}.nginx.foo.svc.cluster.local | web-{0..N-1} |
kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0..N-1}.nginx.foo.svc.kube.local | web-{0..N-1} |
说明: 集群域会被设置为
cluster.local
,除非有其他配置。
3.3 访问测试
3.3.1 安装centos pod
如果想访问它,那么可以再安装一个pod,然后通过pod之间进行访问,宿主机是不能直接访问的哈
我这里再安装一个centos7的docker看一下
# 使用 kubectl explain Pod.spec.containers 得倒docker exec -it命令在k8s pod的表达方式
# stdin <boolean>
# tty <boolean>
cat>centos7.yaml<<EOF
apiVersion: v1
kind: Pod
metadata:
name: centos7
labels:
sys: centos7
spec:
containers:
- name: centos7
image: centos:7
stdin: true
tty: true
args: ["/bin/bash"]
EOF
kubectl apply -f centos7.yaml
#因为没有安装centos7会去docker的hub镜像下载,我之前修改为华为云了,速度会快很多
[root@vm82 myk8s]# kubectl get po centos7
NAME READY STATUS RESTARTS AGE
centos7 1/1 Running 0 8m27s
[root@vm82 myk8s]#
这样就安装完了,安装完之后,那怎么访问呢,因为没有群IP,所以只能在容器间,通过域名访问,我们在安装kubeadm的时候默认就安装了dns服务,所以通过域名是可以访问的。
3.3.2 测试
上面的pod运行正常后,可以登录centos7容器中,进行测试,操作如下:
#进入容器 不懂可以 使用 kubectl exec --help
kubectl exec -it centos7 -c centos7 -- bash
#在容器中执行域名解析
yum install bind-utils telnet -y
nslookup web-0
#查看web-0的域名,<pod名>.<services名>
[root@centos7 /]# nslookup web-0.nginx-ser-none
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-0.nginx-ser-none.default.svc.cluster.local
Address: 10.44.0.1
[root@centos7 /]# nslookup web-1.nginx-ser-none
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-1.nginx-ser-none.default.svc.cluster.local
Address: 10.44.0.2
[root@centos7 /]#
#发现使用hostpath,卷都成共享的了,指向中一个目录
[root@centos7 /]# curl web-0.nginx-ser-none
vm821 index.html
[root@centos7 /]# curl web-1.nginx-ser-none
vm821 index.html
四、稳定的存储
上面的例子中创建的卷使用的是hostPath直接在节点上创建的,而且变成了共享目录,我们知道statefulSet是有状态的,如果变成有状态的,比如mysql主从,现在共享一个目录,没意义啊
更致命的是hostPath只是绑定所在节点上,比如你node1 pod挂了,从另一个节点起来,那么hostPath指定的目录就是空的啊!!数据还在node1上了,万一服务器磁盘坏了,那怎搞?!
再者术业有专攻,如果你并不知道有哪些 Volume 类型可以用,要怎么办呢?作为运维只些只懂一部分,也不能兼做存储专家吧,对开发者更是,不仅超越了开发者的知识储备,还会有暴露公司基础设施秘密的风险。因为会把用户名,验证字符串等敏感信息写进去。
这也是为什么,在后来的演化中,Kubernetes 项目引入了一组叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 对象,大大降低了用户声明和使用持久化 Volume 的门槛。
4.1 pv和pvc的关系
PVC 和 PV 的设计,实际上类似于“接口”和“实现”的思想。开发者只要知道并会使用“接口”,即:PVC;而运维人员则负责给“接口”绑定具体的实现即:PV
ps:一般主流的k8s都会集成相关的pv,直接拿来用就行。不少公司的相关产品也会出现相关的pv,运维编写的时候可以参照一下,这样也降低的门槛了。
这种解耦,就避免了因为向开发者暴露过多的存储系统细节而带来的隐患。此外,这种职责的分离,往往也意味着出现事故时可以更容易定位问题和明确责任,从而避免“扯皮”现象的出现。
而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。
Volume 的类型有一堆可以看官网的Volume 的类型
下面是一个带有PVC的statefulSet例子
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql8
spec:
serviceName: "mysql8"
replicas: 2
selector:
matchLabels:
db: mysql8
template:
metadata:
labels:
db: mysql8
spec:
containers:
- name: mysql8
image: mysql:8.0.21
env:
- name: MYSQL_ROOT_PASSWORD
value: hua123
- name: MYSQL_DATABASE
value: /var/lib/mysql
args: ["--default-authentication-plugin=mysql_native_password","--character-set-server=utf8mb4","--collation-server=utf8mb4_unicode_ci"]
ports:
- containerPort: 3306
name: mysql8
volumeMounts:
- mountPath: /var/lib/mysql
name: data
#和普通的statefulSet差不多,只不过多了一个pvc模板
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes:
- ReadWriteOnce
storageClassName: data-mysql8
resources:
requests:
storage: 30Gi
凡是被这个 StatefulSet 管理的 Pod,都会声明一个对应的 PVC;而这个 PVC 的定义,就来自于 volumeClaimTemplates 这个模板字段。更重要的是,这个 PVC 的名字,会被分配一个与这个 Pod 完全一致的编号。
这个自动创建的 PVC,与 PV 绑定成功后,就会进入 Bound 状态,这就意味着这个 Pod 可以挂载并使用这个 PV 了。
当然,PVC 与 PV 的绑定得以实现的前提是,运维人员已经在系统里创建好了符合条件的 PV(比如,我们在前面用到的 pv-volume);或者,你的 Kubernetes 集群运行在公有云上,这样 Kubernetes 就会通过 Dynamic Provisioning 的方式,自动为你创建与 PVC 匹配的 PV。
4.1.1 pv生命周期
pv (持久卷)和pod资源-样,拥有生命周期,共分为以下四种:
Provisioning :正在申明
Binding :正在绑定
using :正在使用
Reclaiming :正在回收
4.1.2 pv回收策略
当pod资源被删除时,其相关pv和数据如何操作?该删除还是保留呢?
kubernetes通过persistentVolumeReclaimPolicy字段进行设置:
Delete :数据和pv都会删除
Recyle : ( 已废弃)
Retain :数据和pv都不动
4.1.3 pv的声明类型
PV的申明类型可分为以下两种:
Static (静态) :
管理员根据使用情况,人为预先进行配置
Dynamic (动态) :
基于已创建的StorageClasses (简称SC )存储类,起到动态申请和创建的作用
API server需要增加一个参数配置: - enable amission-plugins,具体类型参考: storage-classes
4.2 storageClassName
StorageClass(SC)作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,一方面减少了用户对于存储资源细节的关注,另一方面减轻了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定,实现了动态的资源供应。基StorageClass的动态资源供应模式将逐步成为云平台的标准存储配置模式。
StorageClass的定义主要包括名称、后端存储的提供者(provisioner)和后端存储的相关参数配置。StorageClass一旦被创建出来,则将无法修改。如需更改,则只能删除原StorageClass的定义重建。
4.3 pv pvc sc之间的关系
简单来说
pv是用来定义持久卷的,好比接口的实现
pvc是用来使用pv的,好像调用接口
sc则是实现自动完成pv的创建和绑定的,实现动态绑定功能,如果k8s有相关的sc,那么你就可以不用写pv了,是不是很爽