StatefulSet认识headless-service的作用sts

4次阅读

共计 2538 个字符,预计需要花费 7 分钟才能阅读完成。

参考文章

  1. Kubernetes 通过 statefulset 部署 redis cluster 集群

    • statefulset+NFS 存储部署 redis-cluster 集群示例
  2. 官方文档 Headless Services
  3. kubernetes 之 StatefulSet 详解

    • 为什么需要 headless service 无头服务?
    • 为什么需要 volumeClaimTemplate?
    • ss 的几个重点: Pod 管理策略, 更新策略.

20200620 更新

今天终于想到 headlesss service 有什么用了 …

像 elasticsearch, etcd 这种分布式服务, 在集群初期 setup 时, 配置文件中就要写上集群中所有节点的 IP(或是域名).

比如 es

node.name: es-01
network.host: 0.0.0.0
## 对客户端提供服务的端口
http.port: 9200
## 集群内与其他节点交互的端口
transport.tcp.port: 9300
## 这里的数组成员为各节点的 node.name 值.
cluster.initial_master_nodes: 
  - es-01
  - es-02
  - es-03
## 配置该节点会与哪些候选地址进行通信.
discovery.seed_hosts:
  - 192.168.80.1:9300
  - 192.168.80.2:9300
  - 192.168.80.3:9300

再像etcd

listen-peer-urls: https://172.16.43.101:2380
initial-advertise-peer-urls: https://172.16.43.101:2380
initial-cluster: k8s-master-43-101=https://172.16.43.101:2380,k8s-master-43-102=https://172.16.43.102:2380,k8s-master-43-103=https://172.16.43.103:2380

但是由于 kuber 集群的特性, Pod 是没有固定 IP 的, 所以配置文件里不能写 IP. 但是用 Service 也不合适, 因为 Service 作为 Pod 前置的 LB, 一般是为一组后端 Pod 提供访问入口的, 而且 Service 的 selector 也没有办法区别同一组 Pod, 没有办法为每个 Pod 创建单独的 Serivce.

于是有了 Statefulset. ta 为每个 Pod 做一个编号, 就是为了能在这一组服务内部区别各个 Pod, 各个节点的角色不会变得混乱.

同时创建所谓的 headless service 资源, 这个 headless service 不分配 ClusterIP, 因为根本不会用到. 集群内的节点是通过 Pod 名称 + 序号.Service 名称 确定彼此进行通信的, 只要序号不变, 访问就不会出错.

可以通过 env 将 Pod 的 IP 注入到 Pod 内部, 但是每一次变动都需要修改配置文件, 实在太麻烦, 且容易出错.


对于 redis, mysql 这种有状态的服务, 我们使用 statefulset 方式为首选. 我们这边主要就是介绍 statefulset 这种方式, statefulset的设计原理模型:

  1. 拓扑状态: 应用的多个实例之间不是完全对等的关系, 这个应用实例的启动必须按照某些顺序启动. 比如应用的主节点 A 要先于从节点 B 启动. 而如果你把 A 和 B 两个 Pod 删除掉, 他们再次被创建出来是也必须严格按照这个顺序才行. 并且, 新创建出来的 Pod, 必须和原来的 Pod 的网络标识一样, 这样原先的访问者才能使用同样的方法, 访问到这个新的 Pod.
  2. 存储状态: 应用的多个实例分别绑定了不同的存储数据. 对于这些应用实例来说, Pod A 第一次读取到的数据, 和隔了十分钟之后再次读取到的数据, 应该是同一份, 哪怕在此期间 Pod A 被重新创建过. 一个数据库应用的多个存储实例.

关于 headless service

在 statefulset 中, headless service 也是非常重要的一个点. 其实 headless service 就是普通的 Service 资源, 且类型为 ClusterIP, 只不过把clusterIP 字段显示地设置为了None.

$ kubectl get svc redis-service
NAME            TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
redis-service   ClusterIP   None         <none>        6379/TCP   53s

可以看到, 服务名称为 redis-service, 其CLUSTER-IPNone, 表示这是一个 ” 无头 ” 服务.

headless service不具有普通 Service 资源的负载均衡能力, 因为没有 ClusterIP, 所以kube-proxy 组件不处理此类服务, 在访问此类服务的时候返回的是所有后端 Pod 的 Endpoints 列表.

访问一个普通的Service, kube-proxy 会将请求重定向到后端的某个Pod, 多次请求虽然发送到的后端可能不同, 但是前端是无感知的, 因为 Service 本身有固定 IP.

但是访问一个headless service, 其实是随机且直接访问到后端Pod, 比如多次ping redis-service, 你会发现解析出来的地址是不同的, 而这些地址都是 Pod 的地址.

$ ping redis-service
PING redis-service.default.svc.cluster.local (10.254.0.215) 56(84) bytes of data.
64 bytes from redis-app-5.redis-service.default.svc.cluster.local (10.254.0.215): icmp_seq=3 ttl=64 time=0.081 ms
...

$ ping redis-service
PING redis-service.default.svc.cluster.local (10.254.0.213) 56(84) bytes of data.
64 bytes from redis-app-3.redis-service.default.svc.cluster.local (10.254.0.213): icmp_seq=2 ttl=64 time=0.085 ms
正文完
 0