使用kubectl api在K8S集群内动态指定主节点实现故障转移

前言

我们在部署服务的时候,有很多服务是master-slave模式,这些服务可能有自己的故障切换逻辑。
我们知道,我们可以通过一个负载均衡器(或额外的router服务)能够指向master服务,当故障发生的时候,在slave切换到master以后,负载均衡也能能切换到新的master上面。但是,这无疑会增加部署成本

那么,在K8S(或K3S)里面怎么最低成本的实现呢?

假设我有一个服务叫“OrgManager”服务,这是一个典型的master-slave主备模式的服务,它有一个能够获取当前集群主备信息的接口
今天我们就以动态切换OrgManager主备节点为案例,讲解如何设置NodePort始终指向主节点的方案。

方案

我们知道,K8S的各种服务(Service),比如ClusterIP、NodePort,是通过标签选择器来将流量导入到POD里面,那么,我们就可以通过修改POD的特定标签的形式,来让服务只指向该特定POD,来达成这个目标。

实现路线

  1. 构建一个主备的OrgManager服务
  2. 通过一个SideCar容器的脚本(不是一定要这样,反正得有个脚本来运行),定时的检测OrgManager服务的主备信息
  3. 当上述脚本检查到当前POD是master时,通过kubectl api给当前POD打上role: master标签,相应的,如果是slave,则打上role: slave标签
  4. Nodeport服务,其selector上增加role: master选择器

具体样例(所有命名空间都在myspace下)

1. 创建Headless服务

这个很重要,我们需要通过无头服务,即clusterIP: None,让每一个StatefulSet POD能获取到稳定的集群地址。这个没得说,参考官网说明即可

apiVersion: v1
kind: Service
metadata:
  name: OrgManager-headless-service
  namespace: myspace
  labels:
  	app: OrgManager
spec:
  publishNotReadyAddresses: false
  clusterIP: None
  ports:
  - port: 80
    targetPort: webport
    protocol: TCP
    name: webport
  selector:
    app: OrgManager

2. 创建ServiceAccount

因为我们要在POD的SideCar容器里面调用kubectl api,所以我们需要配置一个具有特定权限的账户。关于ServiceAccount,自行参考官网学习即可。这里只是告诉大家,官网说明告诉了我们如何在一个POD内部访问kubectl api的服务。

在这个案例中,我们在POD中创建一个ServiceAccount,并且绑定ClusterRole,让其具备pod的查询和更新能力

apiVersion: v1
kind: ServiceAccount
metadata:
  name: apiuser
  namespace: myspace
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: cluster-role
  namespace: myspace
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: cluster-role-binding
  namespace: myspace
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-role
subjects:
- kind: ServiceAccount
  name: apiuser
  namespace: myspace

3. 构建OrgManager主备服务,采用StatefuleSet模式

注意:下面的内容不是一个完整的部署文件
这里因为是自己的服务,我就不提供部署文件了。很多部分都用省略号代替了

这个服务有一个REST接口:/api/cluster/info
能够获取到集群信息。

信息如下:

{"master": "master-ip", "slaves": ["slave-ip-1", "slave-ip-2"......]}

下面的内容只是截取一段,说明它是“StatefuleSet”方式部署。因为StatefuleSet能够通过Headless Service获得稳定的集群IP地址,所以可以看到spec.serviceName指定了Headless Service的服务命名。

  • 我们通过环境变量,将一些重要信息传入到POD的容器中

  • 可以看到,我们指定了serviceAccountName

  • 同时,我们可以看到,这里设置了一个名叫“side-car”的容器,这个容器可以是任意一个具备curl能力的容器,在这个案例中,我使用的是nginx,这个容器里面执行了一段脚本,这脚本就是这次的重点:通过获取主备信息,来动态设置POD的标签

...
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: OrgManager
  namespace: myspace
  labels:
    app: OrgManager
spec:
  replicas: 2
  serviceName: OrgManager-headless-service
  template:
  ...
  	spec:
      serviceAccountName: "apiuser"
      containers:
      - name: "OrgManager"
        image: "OrgManager:1.3.6.14"
        imagePullPolicy: "IfNotPresent"
        ....
      - name: "side-car"
      	image: "nginx:1.6"  
      	.......
      	env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.name
        - name: POD_HEADLESS_NAME
          value: OrgManager-headless-service
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              apiVersion: v1
              fieldPath: metadata.namespace
        command: 
        - bash
        - -c
        - |-
          while true; do
	        value=$(curl http://127.0.0.1/api/cluster/info)
	        echo "----------集群信息---------"
	        echo ${value}
	        echo "--------------------------"
	        # 从返回信息中,截取master对应的ip地址
	        value=${value#*\"master\"\: \"}
	        value=${value%%\", \"*}
	        if [ "${value}" == "${POD_NAME}.${POD_HEADLESS_NAME}" ]; then
	           echo "当前POD为master,更新POD标签role: master"
	           echo -e '[{"op": "replace", "path": "/metadata/labels/role", "value": "master"}]' > /root/patch_lable.json
	        else
	           echo "当前POD为slave,更新POD标签role: slave"
	           echo -e '[{"op": "replace", "path": "/metadata/labels/role", "value": "slave"}]' > /root/patch_lable.json
	        fi
	        
			export APISERVER=https://kubernetes.default.svc
			export SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
			export TOKEN=$(cat ${SERVICEACCOUNT}/token)
			export CACERT=${SERVICEACCOUNT}/ca.crt
	        curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api/v1/namespaces/${POD_NAMESPACE}/pods/${POD_NAME} --data "$(cat /root/patch_lable.json)"  --request PATCH -H "Content-Type:application/json-patch+json"
	        
	        sleep 10
	     done    
... 

脚本解释:

  1. 通过api,获取到当前的集群信息
  2. 从信息中,通过字符串处理的方式,得到master的地址。(由于是statefulset,并且指定了headless服务,因此集群构建的时候能得到一个稳定的可声明的地址,脚本里也能获取到集群中这个地址)
  3. 将这个获取到的地址和当前POD的headless地址进行比较
  4. 根据比较结果,设置更新标签的jsonpatch请求体
  5. 根据官网说明的方式,调用接口更新POD的标签

通过上面的处理,就做到了给pod打role: master或role: slave标签的目标


4. 创建指向master的Nodeport

我们注意到,这个Nodeport的selector里面,设置了role: master这个选择器

apiVersion: v1
kind: Service
metadata:
  name: OrgManager-nodeport
  namespace: myspace
  labels:
	app: OrgManager
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: webport
    protocol: TCP
    name: webport
    nodePort: {{ .Values.nodePort.port }}
  selector:
    app: OrgManager
    role: master

至此,我们就可以通过主机的80端口,访问OrgManager服务特定的master了。由于OrgManager具备主从切换的能力,当master挂掉以后,/api/cluster/info会获取到新的master的地址,这时候,脚本就会获取到这个变化,然后更新POD的标签,这样就对外无感的实现了主节点故障转移

最重要的是,我们没有额外部署其他负载均衡器或VIP服务,就做到了指定master的目标


总结

  1. 使用StatefulSet + HeadlessService的方式,在K8S中构建地址稳定的集群服务。且该服务具备接口能获取到master-slave信息
  2. 创建能够在POD中访问kubectl api的ServiceAccount
  3. 通过在POD中的容器执行检测集群状态的脚本,获取到master-slave信息,决定给当前POD打上什么标签
  4. K8S的服务,通过selector指定上述特定标签的方式,指向特定的POD



延生思路

上面我们是以OrgManager服务,和Nodeport来举例。

实际上,通过上述方法,我们就有了很灵活的方案。

比如:

Mysql主从模式,我们可以通过脚本监控Mysql主从的信息,获取到master pod或slave pod,然后给master或slave打上合适的标签。
在集群内部的其他服务,通过ClusterIP Service来访问Mysql,那么这个ClusterIP Service的selector只要指定了特定标签,就能够访问到特定的POD,比如Mysql的主节点。
由于每个脚本都在自己的POD内,因此该脚本实现了自动故障转移打标签,那么相应的Service也就是能动态指定到不同的POD了

最关键的是,这种方法没有引入任何第三方的额外部署,比如负载均衡、Router之类的东西。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值