K8S | StatefulSet基本原理
背景
用户通过 Deployment、ReplicationController 可以方便地在 kubernetes 中部署一套高可用、可扩展的分布式无状态服务。这类应用不在本地存储数据,通过简单的负载均衡策略可实现请求分发。随着 k8s 的普及和云原生架构的兴起,越来越多的人希望把数据库这类有状态服务也通过 k8s 进行编排。但因为有状态服务的复杂性,这一过程并不容易。本文将以最流行的开源数据库 MySQL 为例,介绍如何在 k8s 上部署运维有状态服务。本文所作的调研基于k8s 1.18
。
StatefulSet 简介
Deployment、ReplicationController是为无状态服务而设计的,它们中 pod 的名称、主机名、存储都是不稳定的,且 pod 的启动、销毁顺序随机,并不适合数据库这样的有状态应用。为此,k8s 推出了面向有状态服务的工作负载StatefulSet。其管理的 pod 具有如下特点:
- 唯一性 - 对于包含 N 个副本的 StatefulSet,每个 pod 会被分配一个 [0,N)范围内的唯一序号。1. 顺序性 - StatefulSet 中 pod 的启动、更新、销毁默认都是按顺序进行的。1. 稳定的网络身份标识 - pod 的主机名、DNS 地址不会随着 pod 被重新调度而发生变化。1. 稳定的持久化存储 - 当 pod 被重新调度后,仍然能挂载原有的 PersistentVolume,保证了数据的完整性和一致性。 StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为:
$(podname).(headless server name)
FQDN: $(podname).(headless server name).namespace.svc.cluster.local
创建 StatefulSet
作为开始,使用如下示例创建一个 StatefulSet。它和StatefulSets概念中的示例相似。它创建了一个Headless Servicenginx
用来发布 StatefulSetweb
中的 Pod 的 IP 地址。
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: "www-data-pv"
labels:
name: www-data-pv
release: stable
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
nfs:
path: /nfs/www/data
server: 192.168.84.75
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: www-data-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
selector:
matchLabels:
name: www-data-pv
release: stable
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumes:
- name: www
persistentVolumeClaim:
claimName: www-data-pvc
通过该配置文件,可看出StatefulSet的三个组成部分:
- Headless Service:名为nginx,用来定义Pod网络标识( DNS domain)。- StatefulSet:定义具体应用,名为Nginx,有三个Pod副本,并为每个Pod定义了一个域名。- persistentVolumeClaim: 是由用户进行存储的请求。它类似于pod。Pod消耗节点资源,PVC消耗PV资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。【persistentVolumeClaim的细节请看k8s的持久化存储PVC】
为什么需要 headless service 无头服务?
在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在statefulset中要求必须是有序 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。而pod IP是变化的,所以是以Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。
使用kubectl apply
中的 Headless Service 和 StatefulSet。
kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
最后三个Pod全部running且ready, 且顺序是web-0.web-1,web-2
测试pod间是否相通
每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用kubectl exec
在每个 Pod 中执行hostname
。
$ for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1
使用kubectl run
运行一个提供nslookup
命令的容器,该命令来自于dnsutils
包。通过对 Pod 的主机名执行nslookup
,你可以检查他们在集群内部的 DNS 地址。
$ kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
nslookup web-0.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.1.6
nslookup web-1.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.2.6
另外一种方法是可以直接在web-0 pod中执行 nslookup + 域名 指令。
kubectl exec web-0 -- nslookup web-1.nginx.svc.cluster.local
格式是:${podname}.${servername}.svc.cluster.local
扩容/缩容 StatefulSet
扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新replicas
字段完成。你可以使用kubectl scale
或者kubectl patch
来扩容/缩容一个 StatefulSet。
扩容
在一个终端窗口观察 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一个终端窗口使用kubectl scale
扩展副本数为 5。
kubectl scale sts web --replicas=5
statefulset.apps/web scaled
在第一个 终端中检查kubectl get
命令的输出,等待增加的 3 个 Pod 的状态变为 Running 和 Ready。
StatefulSet 控制器扩展了副本的数量。StatefulSet 按序号索引顺序的创建每个 Pod,并且会等待前一个 Pod 变为 Running 和 Ready 才会启动下一个 Pod。
缩容
在一个终端观察 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一个终端使用kubectl patch
将 StatefulSet 缩容回三个副本。
kubectl patch sts web -p '{"spec":{"replicas":3}}'
statefulset.apps/web patched
顺序终止 Pod
控制器会按照与 Pod 序号索引相反的顺序每次删除一个 Pod。在删除下一个 Pod 前会等待上一个被完全关闭。
规律总结:
(
s
t
a
t
e
f
u
l
s
e
t
名
称
)
−
(statefulset名称)-
(statefulset名称)−(序号)</strong>,比如上面的示例:web-0,web-1,web-2。</li>- StatefulSet为每个Pod副本创建了一个DNS域名,这个域名的格式为:**$(podname).(headless server name)**,也就意味着服务间是通过Pod域名来通信而非Pod IP,因为当Pod所在Node发生故障时,Pod会被飘移到其它Node上,Pod IP会发生变化,但是Pod域名不会有变化。<li>StatefulSet使用Headless服务来控制Pod的域名,这个域名的FQDN为:<strong>
(
s
e
r
v
i
c
e
n
a
m
e
)
.
(service name).
(servicename).(namespace).svc.cluster.local</strong>,其中,“cluster.local”指的是集群的域名。</li>- 根据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。
域名示例
关于Cluster Domain、headless service名称、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}
更新策略
在Kubernetes 1.7及更高版本中,通过.spec.updateStrategy字段允许配置或禁用Pod、labels、source request/limits、annotations自动滚动更新功能。
**OnDelete:**通过.spec.updateStrategy.type 字段设置为OnDelete,StatefulSet控制器不会自动更新StatefulSet中的Pod。用户必须手动删除Pod,以使控制器创建新的Pod。
**RollingUpdate:**通过.spec.updateStrategy.type 字段设置为RollingUpdate,实现了Pod的自动滚动更新,如果.spec.updateStrategy未指定,则此为默认策略。
StatefulSet控制器将删除并重新创建StatefulSet中的每个Pod。它将以Pod终止(从最大序数到最小序数)的顺序进行,一次更新每个Pod。在更新下一个Pod之前,必须等待这个Pod Running and Ready。
**Partitions:**通过指定 .spec.updateStrategy.rollingUpdate.partition 来对 RollingUpdate 更新策略进行分区,如果指定了分区,则当 StatefulSet 的 .spec.template 更新时,具有大于或等于分区序数的所有 Pod 将被更新。
具有小于分区的序数的所有 Pod 将不会被更新,即使删除它们也将被重新创建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition 大于其 .spec.replicas,则其 .spec.template 的更新将不会传播到 Pod。在大多数情况下,不需要使用分区。
外部访问集群内部
土方法
对pod打标签,然后映射成服务
kubectl label pod web-0 webInst=0 -n sy-platform-demo
kubectl expose pod web-0 --port=80--target-port=80--name=web-0 --selector=web=0 --type=NodePort -n sy-platform-demo
修改web-0 的nodeport 端口号
kubectl edit service web-0 -n sy-platform-demo
--完--
- 原文作者: 留白
- 原文链接: https://zfunnily.github.io/2020/08/statefulset/
- 更新时间:2024-04-16 01:01:05
- 本文声明:转载请标记原文作者及链接