一、前言

      今天介绍一个小众工具,peer-finder, 这是一个简单的守护进程,golang写的,总代码量不到150行,但对StatefulSet非常有用。github 地址:https://github.com/kmodules/peer-finder/tree/master 。介绍这个之前,先给大家补充一下知识点:

      在Kubernetes中,大多数的Pod管理都是基于无状态、一次性的理念。例如Replication Controller,它只是简单的保证可提供服务的Pod数量。如果一个Pod被认定为不健康的,Kubernetes就会以对待牲畜的态度对待这个Pod——删掉、重建。相比于牲畜应用,PetSet(宠物应用),是由一组有状态的Pod组成,每个Pod有自己特殊且不可改变的ID,且每个Pod中都有自己独一无二、不能删除的数据。

  众所周知,相比于无状态应用的管理,有状态应用的管理是非常困难的。有状态的应用需要固定的ID、有自己内部不可见的通信逻辑、特别容器出现剧烈波动等。传统意义上,对有状态应用的管理一般思路都是:固定机器、静态IP、持久化存储等。Kubernetes利用PetSet这个资源,弱化有状态Pet与具体物理设施之间的关联。一个PetSet能够保证在任意时刻,都有固定数量的Pet在运行,且每个Pet都有自己唯一的身份。一个“有身份”的Pet指的是该Pet中的Pod包含如下特性:

    1)        持久化存储;

    2)        有固定的主机名,且DNS可寻址(稳定的网络身份,这是通过一种叫 Headless Service 的特殊Service来实现的。 和普通Service相比,Headless Service没有Cluster IP,用于为一个集群内部的每个成员提供一个唯一的DNS名字,用于集群内部成员之间通信 。);

    3)        一个有序的index(比如PetSet的名字叫mysql,那么第一个启起来的Pet就叫mysql-0,第二个叫mysql-1,如此下去。当一个Pet down掉后,新创建的Pet会被赋予跟原来Pet一样的名字,通过这个名字就能匹配到原来的存储,实现状态保存。)

  应用举例:

    1)        数据库应用,如Mysql、PostgreSQL,需要一个固定的ID(用于数据同步)以及外挂一块PVC(持久化存储)。

    2)        集群软件,如zookeeper、Etcd,需要固定的成员关系。

二、peer-finder原理

       通常,Pets需要互相知道对方的存在,最傻瓜式的方式就是操作员“告诉”一个Pet,它有多少个同伴,但这显然是不够的。一种方法是,在Pod内部调用Kubectl的api来获取该Pet对应的PetSet中的其他成员,但并不推荐这么做。这样做的话,就会使得你的Pod可以反过来操控外部的组件。另一种方法是通过DNS解析,利用工具peer-finder把对应的无头服务(headless service)中的所有endPoint都查找出来。它所做的只是监视DNS中作为PetSet的管理服务的endpoint的更改。它定期查找与Kubernetes服务相对应的DNS条目的SRV记录,好让其下的各个pod能够找到彼此。核心代码如下:

	for newPeers, peers := set.New(), set.New(); script != ""; time.Sleep(pollPeriod) {

		newPeers, err = lookup(*svc)
		if err != nil {
			log.Printf("%v", err)
			continue
		}

		if newPeers.Equal(peers) || !newPeers.Has(myName) {
			continue
        }

        peerList := newPeers.SortList()
        log.Printf("Peer list updated\niam %v\nwas %v\nnow %v", myName, peers.SortList(), peerList)
        shellOut(strings.Join(peerList, "\n"), script)
		peers = newPeers
		script = *onChange
	}

peer-finder轮询(1秒)指定的k8s service ,如果service下面的pod地址列表发生变化则触发你的执行脚本。

三、使用方法

使用方式也很简单,peer-finder直接传参运行,需要传的参数:

 

1、 -on-change and/or -on-start  (取决于你是想在pod启动时候监控dns的变化还是改变时监控,也可以两者都要,但不能两样都不要,毕竟两样都不要干嘛用它)

2、-service (无头服务的地址,基于这个做解析,你肯定得告诉人家地址吧)

3、-ns or an env var for POD_NAMESPACE. (名称空间,这个你可以直接传参或者环境变量设置都行。)

4、domain(可以省略,一般走/etc/resolv.conf,这个里边存储了svc的解析地址)

$ peer-finder -h
Usage of peer-finder:
  -domain string
        The Cluster Domain which is used by the Cluster, if not set tries to determine it from /etc/resolv.conf file.
  -ns string
        The namespace this pod is running in. If unspecified, the POD_NAMESPACE env var is used.
  -on-change string
        Script to run on change, must accept a new line separated list of peers via stdin.
  -on-start string
        Script to run on start, must accept a new line separated list of peers via stdin.
  -service string
        Governing service responsible for the DNS records of the domain this pod is in.

四、使用场景

1、在初始化的容器中使用,帮助pod在第一次启动时候就能找到它的伙伴,一般用于生成配置文件用,这种场景下直接就用--on start选项就行了,因为init容器执行完就结束了,-- on change选项也用不上

2、与主应用位于同一个pod的第二个容器中使用,这种情况下就可以用--on change选项了,不过此时用-- on strat选项可能就有坑,因为同一个pod下的多个container是不保证顺序的,有依赖关系就不行了。

3、同一个pod的同一个container中使用,此时peer-finder作为pid为1的主进程,-- on change或者on start作为可以传脚本,变成其子进程即可。比如修改配置文件向主进程发个信号

 

五、使用案例

nacos实例有一个配置文件cluster.cof ,实例会轮询这个文件,以保证有新的实例加入时能自动发现,那在k8s集群中我们肯定不能每次手动去改这个配置文件,这也太傻瓜了,必须例用k8s的方式来完成自动扩缩容,nacos用peer-finder来完成自动扩缩容。

通过Helm使用官方的chart包部署的nacos集群,每一个pod有两个容器:

  • peer-finder-plugin-install : 是一个initContainers ,这个镜像唯一的作用是安装 peer-finder插件相关的脚本到指定目录(plugins/peer-finder/),结束后就死亡。
  • nacos-cluster:nacos本体镜像,镜像启动时,除了启动自身的服务外,还会同时调用peer-finder-plugin-install 安装好的脚本启动peer-finder

peer-finder轮询(1秒)指定的k8s service ,如果service下面的pod地址列表发生变化,则重新写入Cluster.conf文件。这里的k8s service就必须是headless类型的了,因为只有解析headless提供的域名,才能获取所有pod的地址列表

六、参考文章

1、https://github.com/kmodules/peer-finder/tree/master

2、https://www.cnblogs.com/zhenyuyaodidiao/p/6654481.html