Kubernetes 实战——有状态应用(StatefulSet)
程序员文章站
2022-06-24 21:18:47
一、简介 有状态实例:新实例和旧实例需要有相同的名称、网络标识和状态 无状态实例:可随时被替换 1. ReplicaSet 和有状态 Pod ReplicaSet 通过 Pod 模板创建多个 Pod 副本,这些副本除了名字和 IP 地址不同,没有其他差异。若 Pod 模板指定了 PVC,则其创建的所 ......
一、简介
- 有状态实例:新实例和旧实例需要有相同的名称、网络标识和状态
- 无状态实例:可随时被替换
1. replicaset 和有状态 pod
replicaset 通过 pod 模板创建多个 pod 副本,这些副本除了名字和 ip 地址不同,没有其他差异。若 pod 模板指定了 pvc,则其创建的所有 pod 共享相同的 pvc 和 pv
集群应用可能要求实例具有唯一的网络标识。可针对每个实例创建一个独立的 service 来提供稳定的网络地址(因为服务 ip 固定)。但 pod 无法获取该 ip,不能在别的 pod 里通过 ip 自行注册
2. 了解 statefulset
- 每一个实例不可替代,都拥有稳定的名字(从零开始的顺序索引)和状态(独立的数据卷)
- 有状态的 pod 有时需要通过其主机名来定位。因为彼此状态不同,通常希望操作的是指定的那个
- 一个 statefulset 常要求创建一个用来记录每个 pod 网络标记的 headless service。通过该 service,每个 pod 将拥有独立的 dns 记录,这样集群中的 pod 或客户端可以通过主机名来定位
- 如一个 default 命名空间,名为 foo 的服务,它的一个 pod 名为 a-0,就可以通过
a-0.foo.default.svc.cluster.local
来定位该 pod - 也可以通过 dns 服务查找域名
foo.default.svc.cluster.local
对应的所有 srv 记录,获取一个 statefulset 所有 pod 的信息
- 当 statefulset 管理的 pod 消失后,会重启一个标识完全一致的 pod 替换(不一定在同一个节点)
-
扩容用下一个索引值,缩容先删除最高索引值,扩/缩容都是逐步进行的(k8s 保证两个拥有相同标记和绑定相同 pvc 的有状态 pod 不会同时运行)
- 若有不健康实例,则不允许做缩容操作(避免一次删除两个)
- 缩容只删除 pod,保留创建的持久卷声明(pvc 被删除后,与之绑定的 pv 也会被回收或删除),需要手动删除。再扩容会重新挂载上
3. 专属存储
- 有状态的 pod 存储必须是持久的,且与 pod 解耦。即 statefulset 的 pod 需要关联到不同的持久卷声明,且与独立的持久卷对应
- 持久卷可以预先创建,也可以由持久卷的动态供应机制实时创建
卷声明模板
statefulset 可以有一个或多个卷声明模板,会在创建 pod 前创建持久卷声明,并绑定到 pod 实例上
二、使用 statefulset
1. 创建
① 容器准备
docker.io/luksa/kubia-pet
- post 请求将 body 中的数据存储到 /var/data/kubia.txt
- get 请求返回主机名和存储的数据
② 手动创建存储卷
apiversion: v1 kind: list items: - apiversion: v1 kind: persistentvolume metadata: name: pv-a # 持久卷名称 pv-a、pv-b、pv-c spec: capacity: storage: 1mi # 持久卷大小 accessmodes: - readwriteonce persistentvolumereclaimpolicy: recycle # 卷被声明释放后,空间被回收再利用 nfs: # 卷使用 nfs 持久磁盘。见 https://www.cnblogs.com/lb477/p/14713883.html server: 192.168.11.210 path: "/nfs/pv-a" ...
③ 创建控制 service
apiversion: v1 kind: service metadata: name: kubia spec: clusterip: none # statefulset 的控制 service 必须是 headless 模式 selector: app: kubia ports: - name: http port: 80
④ 创建 statefulset
apiversion: apps/v1 kind: statefulset metadata: name: kubia spec: selector: matchlabels: app: kubia servicename: kubia replicas: 2 template: metadata: labels: app: kubia spec: containers: - name: kubia image: luksa/kubia-pet ports: - name: http containerport: 8080 volumemounts: - name: data mountpath: /var/data # pod 中的容器会把 pvc 数据卷嵌入指定目录 volumeclaimtemplates: # 创建持久卷声明的模板,会为每个 pod 创建并关联一个 pvc - metadata: name: data spec: resources: requests: storage: 1mi accessmodes: - readwriteonce
⑤ 查看创建结果
$ kubectl get pod -w name ready status restarts age kubia-0 0/1 containercreating 0 35s kubia-0 1/1 running 0 53s kubia-1 0/1 pending 0 0s kubia-1 0/1 containercreating 0 3s kubia-1 1/1 running 0 20s $ kubectl get pv name capacity access modes reclaim policy status claim storageclass reason age pv-a 1mi rwo recycle bound default/data-kubia-0 18m pv-b 1mi rwo recycle bound default/data-kubia-1 18m pv-c 1mi rwo recycle available 18m $ kubectl get pvc name status volume capacity access modes storageclass age data-kubia-0 bound pv-a 1mi rwo 2m3s data-kubia-1 bound pv-b 1mi rwo 70s
2. 测试
- 直连 pod 来访问:借助另一个 pod,在其内部运行 curl 命令或使用端口转发
-
通过 api 服务器与 pod 通信:api 服务器可通过代理直接连接到指定 pod:可通过访问
<apiserverhost>:<port>/api/v1/namespaces/default/pods/kubia-0/proxy/<path>
请求 pod,但 api 服务器有安全保障,需要在每次请求中添加授权令牌。因此可使用 kubectl 代理和 api 服务器代理与 pod 通信:
$ kubectl proxy starting to serve on 127.0.0.1:8001 $ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/ you've hit kubia-0 data stored on this pod: no data posted yet
测试
# 1. 应用的状态独立 $ curl -x post -d "hello kubia-0" localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/ data stored on pod kubia-0 $ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/ you've hit kubia-0 data stored on this pod: hello kubia-0 $ curl localhost:8001/api/v1/namespaces/default/pods/kubia-1/proxy/ you've hit kubia-1 data stored on this pod: no data posted yet # 2. 重新启动一个完全相同的 pod(新的 pod 可能被调度到其他节点) $ kubectl delete pod kubia-0 pod "kubia-0" deleted $ kubectl get pod name ready status restarts age kubia-0 0/1 containercreating 0 1s kubia-1 1/1 running 0 106m $ curl localhost:8001/api/v1/namespaces/default/pods/kubia-0/proxy/ you've hit kubia-0 data stored on this pod: hello kubia-0
暴露 statefulset 的 pod
# 一个常规的 clusterip service,只能在集群内部访问 apiversion: v1 kind: service metadata: name: kubia-public spec: selector: app: kubia ports: - port: 80 targetport: 8080
$ curl localhost:8001/api/v1/namespaces/default/services/kubia-public/proxy/ you've hit kubia-1 / 0
3. 发现伙伴节点
srv 记录:指向提供指定服务的服务器的主机名和端口号
获取 statefulset 里的所有 pod 信息
# 运行一个名为 srvlookup 的一次性 pod,关联控制台并在终止后立即删除 $ kubectl run -it srvlookup --image=tutum/dnsutils --rm --restart=never -- dig srv kubia.default.svc.cluster.local ;; answer section: kubia.default.svc.cluster.local. 30 in srv 0 50 80 kubia-0.kubia.default.svc.cluster.local. kubia.default.svc.cluster.local. 30 in srv 0 50 80 kubia-1.kubia.default.svc.cluster.local. ;; additional section: kubia-0.kubia.default.svc.cluster.local. 30 in a 10.244.0.15 kubia-1.kubia.default.svc.cluster.local. 30 in a 10.244.0.16 ... # 返回的 srv 记录顺序随机
让节点返回所有集群节点的数据
4. 处理节点失效
可通过关闭节点的 eth0 网络接口模拟节点的网络断开
- 当一个节点失效,运行在该节点上的 kubelet 服务就无法与 k8s api 服务器通信,即无法汇报节点及其 pod 的状态
- statefulset 在明确知道一个 pod 不再运行之前,不会创建一个替换的 pod
- 一段时间后,该节点状态变为 notready,pod 状态变为 unknown
- 若节点恢复,汇报状态后 pod 会被重新标记为 running
- 若 pod 的 unknown 状态持续几分钟(可配置)后,主节点就会将 pod 从节点驱逐(删除 pod 资源)
- 若此时 describe pod,可看到其状态为 terminating,即已经被标记为删除。但由于节点不能通信,该 pod 仍会一直运行
- 可强制删除:
kubectl delete pod kubia-0 --force --grace-period 0
(除非确定节点不再运行,否则不要强制删除有状态的 pod)