云原生 云原生系列之:StatefulSet(七)

少年 · 2022年08月25日 · 2657 次阅读

专题合集

《云原生系列专题分享合集》

目录

StatefulSet

除了 Deployment 以外,Kubernetes 里面还有很多其他的 Controller,比如 StatefulSet。

Deployment 更多是用来管理无状态的应用,StatefulSet 更多是用来管理有状态的应用。

Service 对于 Deployment 所管理的 Pod,是不会去特意标记每个 Pod,统一采取 iptables 的方式进行转发。

要注意这里的转发不是均衡分配,而是随机分配。

Service 对于 StatefulSet 所管理的 Pod,会给每个 Pod 做一个特殊的标记,并对 Pod 的启动顺序做了记录。

所以对于需要持久化标识 Pod 的场景,或者对于应用需要按主从顺序启动 Pod 的场景,StatefulSet 才是最佳的选择。

现在我们部署一个 StatefulSet 应用,yaml 配置如下。

apiVersion: v1
kind: Service
metadata:
  name: jmeter
  namespace: default
  labels:
    app: jmeter
spec:
  ports:
  - port: 80
    name: jmeter
  clusterIP: None
  selector:
    app: jmeter
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: jmeter
  namespace: default
spec:
  selector:
    matchLabels:
      app: jmeter
  serviceName: "jmeter"
  replicas: 2
  template:
    metadata:
      labels:
        app: jmeter
    spec:
      containers:
      - name: jmeter
        image: shaonianyr/jmeter:demo
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: log
          mountPath: /jmeter/jmeter.log
  volumeClaimTemplates:
  - metadata:
      name: log
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

可以看到,StatefulSet 绑定了一个 Headless Service 提供访问的入口。

Service 主要通过两种方式提供访问。

  1. 通过 Service VIP ,比如 10.0.0.10,将流量转发给代理的 Pod。
  2. 通过 Service DNS ,比如 jmeter.default.svc.cluster.local,将流量转发给代理的 Pod。

而 StatefulSet 不需要给 Service 分配 VIP,即 Headless Service,指定 clusterIP: None

然后,利用对 Pod 的标识,直接把 Pod IP 跟 Service DNS 做绑定,能指定访问到具体的某个 Pod,不会做负载均衡。

这时我们去查看 StatefulSet Pod 创建情况,会看到类似下面的结果。

$ kubectl get pods -w -l app=jmeter
NAME        READY      STATUS    RESTARTS   AGE
jmeter-0     0/1       Pending   0         0s
jmeter-0     0/1       ContainerCreating   0         5s
jmeter-0     1/1       Running   0         10s
jmeter-1     0/1       Pending   0         0s
jmeter-1     0/1       ContainerCreating   0         5s
jmeter-1     1/1       Running   0         10s

StatefulSet 对第一个 Pod 的启动标记为 jmeter-0,第二个 Pod 的启动标记为 jmeter-1

所以,当我们访问 jmeter-0.default.svc.cluster.local,流量只会代理转发到第一个 Pod。

除了提供 稳定的网络标识,StatefulSet 也提供 稳定的存储标识

如果 Pod 绑定了 PVC,StatefulSet 会给每个 Pod PVC 都绑定一个特殊的编号。

当 Pod 被删除之后,PVC 和 PV 是不会被删除的,等待 Pod 再度被创建出来,会自动绑上原来的 PVC。

Practice

现在我们来看下,云上的 Jmeter 分布式,基于 Deployment 和 StatefulSet 的部署方式。

  • Deployment

先部署 slave。

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: jmeter-slave
  namespace: default
  labels:
    k8s-app: jmeter-slave
spec:
  selector:
    matchLabels:
      k8s-app: jmeter-slave
  replicas: 2
  template:
    metadata:
      labels:
        k8s-app: jmeter-slave
        name: jmeter-slave
    spec:
      serviceAccountName: jmeter-slave
      containers:
        - name: jmeter-slave
          image: shaonianyr/jmeter-slave:demo
          imagePullPolicy: Always
          tty: true
          ports:
            - name: port1
              containerPort: 1099
              protocol: TCP
            - name: port2
              containerPort: 50000
              protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
  name: jmeter-slave
  labels:
    app: jmeter-slave
spec:
  selector:
    k8s-app: jmeter-slave
  ports:
    - name: port1
      protocol: TCP
      port: 1099
      targetPort: 1099
    - name: port2
      protocol: TCP
      port: 50000
      targetPort: 50000
  type: ClusterIP
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jmeter-slave

然后部署 master。

---
kind: Deployment
apiVersion: apps/v1
metadata:
  name: jmeter-master
  namespace: default
  labels:
    k8s-app: jmeter-master
spec:
  selector:
    matchLabels:
      k8s-app: jmeter-master
  replicas: 1
  template:
    metadata:
      labels:
        k8s-app: jmeter-master
        name: jmeter-master
    spec:
      serviceAccountName: jmeter-master
      containers:
        - name: jmeter-master
          image: shaonianyr/jmeter-master:demo
          imagePullPolicy: Always
          tty: true
          command: [ "/bin/sh","-c" ]
          args: [ "jmeter -n -t baidu.jmx -Rjmeter-slave.default.svc.cluster.local" ]
          ports:
            - name: port
              containerPort: 60000
              protocol: TCP
---
kind: Service
apiVersion: v1
metadata:
  name: jmeter-master
  namespace: default
spec:
  selector:
    k8s-app: jmeter-master
  ports:
    - name: port
      protocol: TCP
      port: 60000
      targetPort: 60000
  type: ClusterIP
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: jmeter-master

这里,我们通过 -Rjmeter-slave.default.svc.cluster.local,以 Service 提供 master 对 slave 的链接。

这样虽然也能压,但是会发现,在高并发的情况下, slave 两个 Pod 的资源使用情况,一个极高一个极低,压出来的 QPS 不如两个单副本的 Deployment。

前面讲到,Service 对 Pod 的转发,并不是均衡转发的,而是随机转发,且在高并发的时候,会存在性能损耗。

现在,我们再把上面的 -Rjmeter-slave.default.svc.cluster.local 换成 -Rslave-pod-ip1,slave-pod-ip2

这样配置,相当于绕开了 Service VIP 的代理,直接访问相应的 Pod IP,在高并发的情况下,压出来的 QPS 远大于上面一种配置的 QPS。

但是 Pod IP 会随着容器的重启发生变化,而 Service 却不会,所以其实我们想要后者的稳定配置,前者的稳定转发,答案便呼之欲出。

通过 StatefulSet 部署 slave,利用 Pod 的标识,便可以这样配置:-Rjmeter-slave-0.default.svc.cluster.local,jmeter-slave-1.default.svc.cluster.local

......

未完待续,有空就开更。

如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
共收到 0 条回复 时间 点赞
少年 云原生系列专题分享合集 中提及了此贴 08月25日 10:19
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册