概念
无状态服务(Stateless Service):该服务运行的实例不会在本地存储数据,多个实例对于同一个请求响应的结果是完全一致。
有状态服务(Stateful Service):该服务运行的实例需要在本地存储持久化数据。
Pod的管理对象Deployment、DaemonSet和Job都面向无状态的服务。但现实中有很多服务是有状态的,例如MySQL集群、Kfaka集群、ZooKeeper集群等,共同点:
(1)每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。
(2)集群的规模是比较固定的,不能随意变动。
(3)集群中的每个节点都是有状态的,会持久化数据。
StatefulSet特性:
- 每个Pod都有唯一标识。如kafka第1个Pod叫kafka-0,第2个叫kafka-1,以此类推。
- Pod的启停顺序是受控的。
- Pod采用持久化存储卷,删除Pod时默认不会删除其相关的存储卷。
Headless Service
Service 是应用服务的抽象,通过 Labels 为应用提供负载均衡和服务发现。当资源对象 Deployment 定义了对应的 Service,有两种方式来访问应用:
- cluster IP 的方式
- 当访问 cluster ip 时,请求会转发到 SVC 对应的 Endpoints 列表中的某一个 Pod 上。
- Service 的 DNS 方式
- 当访问 “mysvc.mynamespace.svc.cluster.local” DNS 记录时,就可以访问到命名空间 mynamespace 下名为 mysvc 的 Service 所代理的某一个 Pod。
DNS 方式分两种情况:
- 普通的 Service:访问域名时是通过集群内部 DNS 解析到对应的 SVC 的 cluster IP
- Headless Service:访问域名时是直接解析到 SVC 代理的某一个Pod 的 IP 地址,中间少了 cluster IP 的转发。
示例 Headless Service(headless-svc.yaml)
apiVersion: v1kind: Servicemetadata:name: nginxnamespace: defaultlabels:app: nginxspec:ports:- name: httpport: 80clusterIP: Noneselector:app: nginx
定义 Headless Service 只需设置 clusterIP=None,即 SVC 创建后不会被分配 cluster IP,会以 DNS 记录的方式暴露出其代理的 Pod。
StatefulSet
特性说明
StatefulSet 在 Headless Service 的基础上又为 StatefulSet 控制的每个Pod实例都创建了一个DNS域名,这个域名的格式为:
比如一个3节点的Kafka的StatefulSet集群对应的Headless Service的名称为kafka,则StatefulSet里的3个Pod的DNS名称分别为kafka-0.kafka、kafka-1.kafka、kafka-3.kafka。
资源清单 pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv001
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv002
创建 pv
$ kubectl apply -f pv.yaml
$ kubectl get pv
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 1Gi RWO Recycle Available 12s
pv002 1Gi RWO Recycle Available 11s
StatefulSet 资源清单(nginx-sts.yaml)
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: default
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.7.9
ports:
- name: web
containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: #该属性会自动创建一个PVC对象,并自动和当前可用且匹配的 PV 进行绑定
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
引入了 PV 和 PVC 对象来持久存储数据,当服务被杀掉或者重启,应用的数据不会丢失。
应用
$ kubectl apply -f headless-svc.yaml
$ kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 9s
$ kubectl apply -f nginx-sts.yaml
$ kubectl get pvc # 可以看到通过 Volume 模板自动生成了两个 PVC 对象,也自动和 PV 进行了绑定
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pv001 1Gi RWO 10m
www-web-1 Bound pv002 1Gi RWO 6m26s
# 查看pod创建过程,web-0 和 web-1 两个 Pod 是按照顺序进行创建的,web-0 启动起来后 web-1 才开始创建
$ kubectl get pods -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 6s
# 查看它们的 hostname
$ kubectl exec web-0 -- hostname
web-0
$ kubectl exec web-1 -- hostname
web-1
volumeClaimTemplates声明的模板是挂载点的方式,会覆盖掉容器中的数据。所以在容器启动完成后进入到容器里新建 index.html 文件来保证容器的正常访问。
$ for i in 0 1; do kubectl exec web-$i — sh -c ‘echo hello $(hostname) > /usr/share/nginx/html/index.html’; done
验证
# 最新版本busybox会出现nslookup无法解析,故指定版本
$ kubectl run -it --image busybox:1.28.3 test --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx
Address 1: 10.244.1.175 web-1.nginx.default.svc.cluster.local
Address 2: 10.244.4.83 web-0.nginx.default.svc.cluster.local
/ # ping nginx
PING nginx (10.244.1.175): 56 data bytes
64 bytes from 10.244.1.175: seq=0 ttl=62 time=1.076 ms
64 bytes from 10.244.1.175: seq=1 ttl=62 time=1.029 ms
64 bytes from 10.244.1.175: seq=2 ttl=62 time=1.075 ms
#解析Headless Service(nginx)得到的是两个Pod解析记录。
#通过Headless Service(nginx)访问服务,不会随机或轮询到2个Pod,而是访问到其中1个pod,且是固定的。所以不能代替普通的Service。
# 分别解析对应 Pod
$ / # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.4.83 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.1.175 web-1.nginx.default.svc.cluster.local
# 访问
/ # wget -O - -q web-0.nginx
hello web-0
/ # wget -O - -q web-1.nginx
hello web-1
删除 Pod 自动重新创建,Pod 内容保持不变
$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
$ kubectl run -it --image busybox:1.28.3 test --restart=Never --rm /bin/sh
/ # wget -O - -q web-0.nginx
hello web-0
/ # wget -O - -q web-1.nginx
hello web-1
更新策略
StatefulSet 中支持两种升级策略:onDelete 和 RollingUpdate,可通过设置 .spec.updateStrategy.type 指定。
OnDelete: 表示当更新 StatefulSet 模板后,只有手动删除旧的 Pod 才会创建新的 Pod。
RollingUpdate:表示当更新 StatefulSet 模板后会自动删除旧的 Pod 并创建新的Pod。
