之前在做K8s环境下分布式任务调度方案时,采用的Elastic-job-lite,但是Elastic-job-lite需要依赖Zookeeper来实现分布式程序协调,由于K8s平台提供API支持,所以一直有使用K8s API来代替zookeeper实现分布式程序协调功能的想法。

相关参考文档如下:

K8s API官方文档:https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.13/

Kubernetes-client Java:https://github.com/kubernetes-client/java

Downward API(https://kubernetes.io/docs/tasks/inject-data-application/environment-variable-expose-pod-information/

K8s RBAC:https://kubernetes.io/docs/reference/access-authn-authz/rbac/

最近对K8s API进行了研究,初步实现通过调用kubernetes API来实现动态的实例发现(客户端多实例pod动态发现),即在Pod中通过K8s API获取到pod实例列表,由K8s API代替Zookeeper的分布式任务调度初步方案如下:

(1)通过K8s Downward API将pod name、namespace以环境变量的形式注入到容器中;

(2)在程序中通过Kubernetes-client调用K8s API获取当前程序所属的namespace, labelSelector="app=xxx"的多个pod实例列表;

(3)封装K8sContext对象,记录pod相关信息(副本数podTotalCount、当前podIndex、clusterIp、hostIp、pod副本列表等);

(4)Quartz Job可以利用K8sContext来代替ElasticJob中的ShardingContext,即通过K8sContext来进行任务分片;

Downward API

即k8s提供的通过环境变量的形式将pod的信息注入到容器中,在容器中通过对环境变量的访问即可获取到pod(name、namespace、podIp等)、container(requests.cpu、requests.memory等)的相关信息,示例如下:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: luohq-tomcat-v1
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: luohq-tomcat
        version: v1
    spec:
      containers:
      - name: tomcat
        image: tomcat
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace

Kubernetes-client

即K8s官方提供的客户端lib,用来在pod中访问K8s API的,官方推荐的lib参考链接:https://kubernetes.io/docs/reference/using-api/client-libraries/,本方案中选用的官方维护的Kubernetes-client/Java(参见链接:https://github.com/kubernetes-client/java)。

在pod容器运行环境下访问K8s API方式如下:

curl -v 
--cacert /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-H "Authorization: Bearer $(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" 
https://kubernetes.default:443/api/v1/namespaces/youNamespace/pods?labelSelector=app=youAppLabel

在pod容器中通过编程方式访问K8s API方式如下:

 

import io.kubernetes.client.ApiClient;
import io.kubernetes.client.ApiException;
import io.kubernetes.client.Configuration;
import io.kubernetes.client.apis.CoreV1Api;
import io.kubernetes.client.models.V1Pod;
import io.kubernetes.client.models.V1PodList;
import io.kubernetes.client.util.Config;

import java.io.IOException;

public class Example {

    public static final String POD_NAME = System.getenv("POD_NAME");
    public static final String DEPLOYMENT_NAME = convertDeploymentName(POD_NAME);
    public static final String POD_NAMESPACE = System.getenv("POD_NAMESPACE");


    public static void main(String[] args) throws IOException, ApiException{

        /** 程序自动识别cacert路径、token路径,并自动封装请求 */
        ApiClient client = Config.defaultClient();
        Configuration.setDefaultApiClient(client);

        CoreV1Api api = new CoreV1Api();

       //简单示例,具体参见官方API文档
        V1PodList list = api.listNamespacedPod(POD_NAMESPACE, null, null, null, null,                                                                                                                                  "app=".concat(DEPLOYMENT_NAME), null, null, null, null);
        for (V1Pod item : list.getItems()) {
            System.out.println(item.getMetadata().getName());
        }
    }
}

 

Quartz Job结合K8sContext

参考Elastic-job,同样使用quartz框架来实现定时任务调度功能,每次定时任务触发时,可通过调用K8s API来获取当前pod的所有处在运行状态(status.phase=Running, namespace=yourNamespace, labelSelector="app=youAppSelelctor")的副本列表,由此列表(可对该pod列表按照name排序,保证在不同pod中查询到的列表元素顺序一致)可以得到当前pod的所有副本总数K8sContext.podTotalCount(对应Elastic-job中ShardingContext.shardingTotalCount)、当前pod的在列表中的顺序K8sContext.podIndex(对应Elastic-job中ShardingContext.shardingIntem),通过调用K8s API并对K8sContext进行封装,可由K8sContext代替原ShardingContext,即最终可去除对Zookeeper的依赖;

K8s API权限(RBAC)

在K8s环境中对API的访问是有权限控制的,K8s采用RBAC(Role-based access control)机制对权限进行控制,

Role=resources+verbs(角色=资源+操作)

RoleBinding=role+subject(角色绑定=角色+目标用户)

想要在pod中对K8s API进行访问,需要指定当前pod具有相应的权限,

本方案采用namespace serviceAccount整体赋予权限的方案,每个namespace默认对应一个名字为default的serviceAccount,可对此serviceAccount进行角色绑定,以namespace=test示例如下:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: luohq-read-all
rules:
- apiGroups:
  - '*'
  resources:
  - '*'
  verbs:
  - get
  - list
  - watch
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: test-read-all
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: luohq-read-all
subjects:
- kind: ServiceAccount
  name: default
  namespace: test
---

ClusterRole定义了角色luohq-read-all,该角色可以对所有资源进行get, list, watch查询操作,

ClusterRoleBinding将角色luohq-read-all绑定到命名空间test,绑定后则命名空间test内的pod可以调用K8s API中所有资源的读操作(get, list, watch)

补充:

上述方案仅仅是在任务触发时才去调用K8s API来获得pod副本列表,因此每次任务触发时会有一定延迟,

该方案可以实现分布式任务的动态发现、任务分片,可以满足基本的分布式任务调度,

后续可以考虑使用K8s API watch机制来动态监听pod副本变化,而无需每次在任务触发时才去调用K8s API(减少延迟);

 

 

 

 

Logo

开源、云原生的融合云平台

更多推荐