找到任务插件中的k8s那一块,并创建一个项目,将与之相关的类都添加至自己代码中:
大概有这么多,最下面那两个测试类是我自己创建的,用来辅助的
K8s类是让传来的参数进行加工处理,通过BuildCommand方法转换成String类型,用于AbstractK8sTask类中handle方法
@Override protected String buildCommand() { K8sTaskMainParameters k8sTaskMainParameters = new K8sTaskMainParameters(); Map<String, Property> paramsMap = taskExecutionContext.getPrepareParamsMap(); Map<String, String> namespace = JSONUtils.toMap(k8sTaskParameters.getNamespace()); String namespaceName = namespace.get(NAMESPACE_NAME); String clusterName = namespace.get(CLUSTER); k8sTaskMainParameters.setImage(k8sTaskParameters.getImage()); k8sTaskMainParameters.setNamespaceName(namespaceName); k8sTaskMainParameters.setClusterName(clusterName); k8sTaskMainParameters.setMinCpuCores(k8sTaskParameters.getMinCpuCores()); k8sTaskMainParameters.setMinMemorySpace(k8sTaskParameters.getMinMemorySpace()); k8sTaskMainParameters.setParamsMap(ParameterUtils.convert(paramsMap)); k8sTaskMainParameters.setLabelMap(convertToLabelMap(k8sTaskParameters.getCustomizedLabels())); k8sTaskMainParameters .setNodeSelectorRequirements(convertToNodeSelectorRequirements(k8sTaskParameters.getNodeSelectors())); k8sTaskMainParameters.setCommand(k8sTaskParameters.getCommand()); k8sTaskMainParameters.setArgs(k8sTaskParameters.getArgs()); return JSONUtils.toJsonString(k8sTaskMainParameters); }
handler方法里使用了abstractK8sTaskExecutor.run方法,这是另外一个抽象类中的方法,这个抽象类在AbstractK8sTask中以私有变量的形式将这两个类连接起来。
@Override public void handle(TaskCallBack taskCallBack) throws TaskException { try { TaskResponse response = abstractK8sTaskExecutor.run(buildCommand()); setExitStatusCode(response.getExitStatusCode()); setAppIds(response.getAppIds()); } catch (Exception e) { log.error("k8s task submit failed with error", e); exitStatusCode = -1; throw new TaskException("Execute k8s task error", e); } }
而K8sTaskExecutor继承AbstractK8sTaskExecutor重写其中的方法,并新增创建job等方法
在其重写的run方法中,
if (null == TaskExecutionContextCacheManager.getByTaskInstanceId(taskInstanceId)) { result; result.setExitStatusCode(EXIT_CODE_KILL); return result; }
这一段代码是在dolphin的ui页面创建好工作流后,任务通过Master分配到Worker中进行运行。并且任务运行的状态也会实时的通知到Master。两者之间的交互是通过netty实现的,TaskDispatchProcessor会接收从Master发送的task dispatch消息,并加入到worker任务队列waitSubmitQueue中
取自:Dolphinscheduler 任务运行流程 - 掘金 (juejin.cn)
WorkerTaskDispatchProcessor的process方法中会将taskIntancedID缓存至TaskExecutionContextCacheManager的taskRequestContextCache中,这样就可以在其重写的run方法里获取而不是返回,可以将k8s插件单独的提取出来,将这一段代码注释掉。
在run方法中有
k8sUtils.buildClient(configYaml); submitJob2k8s(k8sParameterStr); registerBatchJobWatcher(job, Integer.toString(taskInstanceId), result, k8STaskMainParameters);
这三个方法,buildClient分别是用来创建k8s集群的客户端,可以操作k8s集群。
submitJob2k8s:提交k8s任务,而在这个方法中
job = buildK8sJob(k8STaskMainParameters); stopJobOnK8s(k8sParameterStr); String namespaceName = k8STaskMainParameters.getNamespaceName(); k8sUtils.createJob(namespaceName, job);
先build,创建一个k8s任务,再将与任务名称相同的任务删除,再创建任务
再在运行时,注册观察,这时代码会进入执行状态,一直运行,直到任务完成或失败会将其结束。
接下来是实验:
在test里设置参数:
参数1:image: 镜像 这里我随便写了一个java将其打包至虚拟机的docker的registry中。
public class CountA { public static void main( String[] args ) { for ( int i = 0; i < args.length; i++ ) { System.out.println( "args[" + i + "]=" + args[ i ] ); } } }
参数2:namespace 命名空间,是你k8s集群的命名空间,如果不知道,可以通过
NonNamespaceOperation< Namespace, NamespaceList, Resource< Namespace > > namespaces = client.namespaces(); NamespaceList list = namespaces.list(); System.out.println("----------this is namespace-----------------"); for (Namespace namespace : list.getItems()) { System.out.println("Name: " + namespace.getMetadata().getName()); System.out.println("Labels: " + namespace.getMetadata().getLabels()); System.out.println("----------------------------------"); } System.out.println("namespace is over =============================");
代码查询,前提:先用java客户端连接上k8s集群
参数3:configYaml java客户端连接k8s集群的配置,找到k8s集群的admin.conf,由于我的是虚拟机中安装的,所以我的是在 /etc/kubernetes/文件夹下,使用finalshell(虚拟机与本机交互的一大利器)下载到电脑后,将clusters下的cluster的server改为https://xxxxxxx:6443这里xxxx改为k8s集群master的ip地址。
然后将其使用json转换工具转换为json格式。
参数4:minCpuCores参数5:minMemorySpace 最小cpu与磁盘设置
参数6:taskInstanceId任务实例id(因为是将关于k8s插件从总源码中拿去出来的,所以这个要手动设置),随便写个int类型的数值
参数7:taskName任务名称,这个参数例子中给的有问题,不可以使用_,将其改为-,同时将K8sTaskExecutor下的buildK8sJob中的
String k8sJobName = String.format("%s-%s-job", taskName, taskInstanceId);
修改,这是我修改后的,原本的是以id结尾,这种是k8s集群不允许的jobname
参数8:
private final String PARAM1 = "param1"; private final String PARAM2 = "param2"; private final String param1Value = "cdtsaygbxhunjivdysabhunvdwysbaaaaaaaaaaun"; private final String param2Value = "a";
这四个可以视为一个参数集合,设置为本此job的环境变量
参数9和10:
private final String command = "[\"java\", \"-jar\",\"app.jar\"]"; private final String args = "[\"param1=aaaaaaaabbbbbbbb\",\"param2=a\"]";
这两个是命令行command和args参数 这是在拉取镜像的时候,镜像是通过Dockerfile打成的镜像,
# command、args两项实现覆盖Dockerfile中ENTRYPOINT的功能,具体的command命令代替ENTRYPOINT的命令行,args代表集体的参数。
# 以下是使用场景
1. 如果command和args均没有指定,那么则使用Dockerfile的配置。
2. 如果command没有指定,但指定了args,那么Dockerfile中配置的ENTRYPOINT的命令行会被执行,并且将args中填写的参数追加到ENTRYPOINT中。
3. 如果command指定了,但args没有写,那么Dockerfile默认的配置会被忽略,执行输入的command(不带任何参数,当然command中可自带参数)。
4. 如果command和args都指定了,那么Dockerfile的配置被忽略,执行command并追加上args参数。
内容取自:https://blog.csdn.net/a13568hki/article/details/124268380
参数11与12:
private final List< Label > labels = Arrays.asList( new Label( "test", "1234" ) ); private final List< NodeSelectorExpression > nodeSelectorExpressions = Arrays.asList( new NodeSelectorExpression( "kubernetes.io/hostname", "In", "master" ) );
labels是标签,被用来与k8s的其他功能连用时匹配的,
nodeSelectorExpressions是节点选择器,而节点怎么选可以通过代码:
System.out.println("this is nodes ------------------------"); NodeList nodeList = client.nodes().list(); for ( Node node : nodeList.getItems()) { System.out.println("Node: " + node.getMetadata().getName()); System.out.println("Labels: " + node.getMetadata().getLabels()); System.out.println("----------------------------------"); }
System.out.println("nodes is over ===========================");
new NodeSelectorExpression( "kubernetes.io/hostname", "In", "master" )的三个参数可以通过刚刚选择节点的代码中的label选择一个label,三个参数的意思分别为下:
key
(键):一个字符串,表示要用于选择节点的标签键(Label Key)。
operator
(运算符):一个字符串,表示进行节点选择时采用的运算符。常见的运算符有 “In”、“NotIn”、“Exists”、“DoesNotExist” 等。在这个例子中,使用的运算符是 “In”,表示节点的标签键值必须在给定的值列表中。
values
(值):一个字符串数组,表示用于节点选择的标签值列表。在这个例子中,标签键为 “kubernetes.io/hostname”,值列表中只有一个元素 “master”。
而operator的这几个数值中:
“In”:表示节点的标签值必须包含在给定的值列表中。只有当节点的标签值在指定的值列表中时,选择器才会匹配该节点。
“NotIn”:表示节点的标签值不能在给定的值列表中。只有当节点的标签值不在指定的值列表中时,选择器才会匹配该节点。
“Exists”:表示节点必须具有指定的标签键,而不考虑该标签的值是什么。只要节点具有该标签键,选择器就会匹配该节点。
“DoesNotExist”:表示节点不能具有指定的标签键。只有当节点不具有指定的标签键时,选择器才会匹配该节点。
ok,所有的参数介绍完毕
然后将测试类进行修改一下:
直接使用handle方法,这个参数是因为父类有所以必须存在,而在k8s重写的方法里面又没有用处,所以赋予null
执行结果:
可以在k8s集群中的容器组内看到执行成功的容器:点击进去查看日志: