概念

无状态服务(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)

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx
  5. namespace: default
  6. labels:
  7. app: nginx
  8. spec:
  9. ports:
  10. - name: http
  11. port: 80
  12. clusterIP: None
  13. selector:
  14. app: nginx

定义 Headless Service 只需设置 clusterIP=None,即 SVC 创建后不会被分配 cluster IP,会以 DNS 记录的方式暴露出其代理的 Pod。

StatefulSet

特性说明

StatefulSet 在 Headless Service 的基础上又为 StatefulSet 控制的每个Pod实例都创建了一个DNS域名,这个域名的格式为:...svc.cluster.local

比如一个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。