之前在做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来进行任务分片;
即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
即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());
}
}
}
参考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的访问是有权限控制的,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(减少延迟);