大数跨境

一文给你整明白多租户在 Apache DolphinScheduler 中的作用

一文给你整明白多租户在 Apache DolphinScheduler 中的作用 海豚调度
2021-12-27
0
导读:多租户是一种经典的架构,目的是为了让多用户环境下使用同一套程序,且保证用户间数据隔离。

点击上方 蓝字关注我们




作者 | 小钻风

编辑 | 小哲




✎ 编 者 按 

刚接触 DolphinScheduler 的小伙伴,总是会被这租户整得不明不白,这是啥呀?为何要配置租户呀?执行任务的时候,为哈脚本运行日志中提示说没有执行权限(Permission denied)呢?带着这样的疑问,咱们继续往下看。

本文为 姚应哲 编辑排版,感谢贡献





01


背景


初入梦。


小啜一口大王刚送的卡布奇洛,咱们书接上回《在 Apache DolphinScheduler 上调试 LDAP 登录,亲测有效!》

听说,小伙伴们看完这篇文章后,对此是好评如潮,花生瓜子八宝粥,雪碧可乐矿泉水备齐了,端个小板凳就过来跟着学习,不时有丢电池,有丢硬币,还有直呼简单易懂、666 的,让人实在是受宠若惊呢!🤤


(停,请停止你那万恶的幻想,没有,没有,都没有)


额……咱们言归正传。


刚接触 DolphinScheduler 的小伙伴,总是会被这租户整得不明不白,这是啥呀?为何要配置租户呀?执行任务的时候,为哈脚本运行日志中提示说没有执行权限(Permission denied)呢?带着这样的疑问,咱们继续往下看。



02


租户是什么?



度娘屁巅屁巅地跑过来告诉您,租户,其基本意思是租用物品的人,本质上还是人的概念。


DolphinScheduler 总是要部署在服务器上,方能帮助咱们稳定地实现任务调度等功能。那 DolphinScheduler 中的租户在服务器上具体是怎么使用的,咱先不表。且先说道说道 DolphinScheduler 中的租户,其实际上对应的是服务器上的用户,不同租户的作用范围由服务器上指定的为准。


  • DolphinScheduler 中用户、租户、角色关系:

  • DolphinScheduler 用户有两种角色:管理员、普通用户,管理员为 DolphinScheduler 初始账号给定,普通用户由管理员创建;

  • DolphinScheduler 用户在创建资源时,会使用创建或修改该用户时指定的唯一租户创建;

  • DolphinScheduler 用户在执行任务时,可以指定多个租户中的某个租户执行。



好,天气冷,屁股凉,话不多说,让咱们进入 DolphinScheduler 源码,一探租户如何工作的究竟。




03


准备运行环境




(若已有,请忽略,直接进入下个环节)


1、下载 DolphinScheduler 最新 2.0.1 版本的安装包


https://dlcdn.apache.org/dolphinscheduler/2.0.1/apache-dolphinscheduler-2.0.1-bin.tar.gz


2、按照官方提供的安装教程来启动


比如单机部署:

https://dolphinscheduler.apache.org/zh-cn/docs/latest/user_doc/guide/installation/standalone.html





04


创建资源示例



1、页面创建文件夹



2、进入文件夹创建流程



咱们从源码扒一扒上述流程中有以下几点与 租户 相关的方法


  • ResourcesServiceImpl 实现类的 verifyResourceName 方法中:

@Overridepublic Result<Object> verifyResourceName(String fullName, ResourceType type, User loginUser) {    Result<Object> result = new Result<>();    putMsg(result, Status.SUCCESS);    if (checkResourceExists(fullName, 0, type.ordinal())) {        logger.error("resource type:{} name:{} has exist, can't create again.", type, RegexUtils.escapeNRT(fullName));        putMsg(result, Status.RESOURCE_EXIST);    } else {        // query tenant        // 判断当前登录用户是否设置了 租户        Tenant tenant = tenantMapper.queryById(loginUser.getTenantId());        if (tenant != null) {            String tenantCode = tenant.getTenantCode();            try {                // 判断当前登录用户的 租户 是否已经创建过同名文件夹                String hdfsFilename = HadoopUtils.getHdfsFileName(type,tenantCode,fullName);                if (HadoopUtils.getInstance().exists(hdfsFilename)) {                    logger.error("resource type:{} name:{} has exist in hdfs {}, can't create again.", type, RegexUtils.escapeNRT(fullName), hdfsFilename);                    putMsg(result, Status.RESOURCE_FILE_EXIST,hdfsFilename);                }            } catch (Exception e) {                logger.error(e.getMessage(),e);                putMsg(result,Status.HDFS_OPERATION_ERROR);            }        } else {            putMsg(result,Status.TENANT_NOT_EXIST);        }    }    return result;}
  • ResourcesServiceImpl 实现类的 createDirectory 方法中:

private void createDirectory(User loginUser,String fullName,ResourceType type,Result<Object> result) {    // 获取当前登录用户的 租户     String tenantCode = tenantMapper.queryById(loginUser.getTenantId()).getTenantCode();    String directoryName = HadoopUtils.getHdfsFileName(type,tenantCode,fullName);    String resourceRootPath = HadoopUtils.getHdfsDir(type,tenantCode);    try {        // 判断 租户 是否在文件存储系统上有根目录        if (!HadoopUtils.getInstance().exists(resourceRootPath)) {            createTenantDirIfNotExists(tenantCode);        }        // 用 租户 的身份创建文件夹        if (!HadoopUtils.getInstance().mkdir(directoryName)) {            logger.error("create resource directory {} of hdfs failed",directoryName);            putMsg(result,Status.HDFS_OPERATION_ERROR);            throw new ServiceException(String.format("create resource directory: %s failed.", directoryName));        }    } catch (Exception e) {        logger.error("create resource directory {} of hdfs failed",directoryName);        putMsg(result,Status.HDFS_OPERATION_ERROR);        throw new ServiceException(String.format("create resource directory: %s failed.", directoryName));    }}


3、创建成功后,页面返回





05


执行任务示例



DolphinScheduler支持多种类型的任务,下面将shell任务为例讲解。这过程有点长,咱们先平抚一下胸腹再继续。


1、点击 创建项目



2、输入 项目名称 与 描述 后点击提交



3、点击创建好的项目 test_task


将自动进入该项目的工作流监控页面。


4、点击工作流定义页面 并点击 创建工作流



5、拖拽 SHELL 组件,设置参数,点击 确认添加



为了查看其过程中是什么样的,咱先让它睡个 1000 秒。



6、点击 保存,设置 DAG 图 名称 与 描述,选择租户(如 root,生产环境不建议配置 root 租户)



7、添加后,点击上线



8、点击运行,并运行



9、查看执行效果


  • 执行日志



从日志上咱们看到 2 个有用信息。切换到worker 节点 对应的路径,可以看到/tmp/dolphinscheduler/exec/process/3931179920032/3931236986272_1/11/21 目录下生成了两个文件:



11_21.command 内容:


11_21_node.sh 内容:


  • 使用租户执行 11_21.command 文件


sudo -u root sh /tmp/dolphinscheduler/exec/process/3931179920032/3931236986272_1/11/21/11_21.command


10、执行流程(盗一波官网用图)



(图片来自https://dolphinscheduler.apache.org/zhcn/docs/latest/user_doc/architecture/design.html)



11、以上图涉及到 租户 有 2 处:


其一:持久化 command 前,即 ExecutorServiceImpl 实现类中的 checkTenantSuitable 方法。


// 当前工作流的租户是否存在if (!checkTenantSuitable(processDefinition)) {    logger.error("there is not any valid tenant for the process definition: id:{},name:{}, ",            processDefinition.getId(), processDefinition.getName());    putMsg(result, Status.TENANT_NOT_SUITABLE);    return result;}
其二:TaskExecuteThread 实现类 run 方法中。
// task handlethis.task.handle();


12、本次测试任务为 SHELL 类型,则继续跟踪,SHELL 任务类中的 handle 方法


@Overridepublic void handle() throws Exception {    try {        // 创建任务脚本 即本例中的 **_**_node.sh        String command = buildCommand();        // 执行shell 任务,主要两个要点        // 1、创建执行脚本 即本例中的 **_**.command        // 2、使用 租户 执行        TaskResponse commandExecuteResult = shellCommandExecutor.run(command);        setExitStatusCode(commandExecuteResult.getExitStatusCode());        setAppIds(commandExecuteResult.getAppIds());        setProcessId(commandExecuteResult.getProcessId());        shellParameters.dealOutParam(shellCommandExecutor.getVarPool());    } catch (Exception e) {        logger.error("shell task error", e);        setExitStatusCode(EXIT_CODE_FAILURE);        throw e;    }}


13、ShellCommandExecutor 类 继承 AbstractCommandExecutor 类,其 run 方法大致如下


public TaskResponse run(String execCommand) throws IOException, InterruptedException {......String commandFilePath = buildCommandFilePath();// 创建任务脚本createCommandFileIfNotExists(execCommand, commandFilePath);//build process// 执行任务脚本buildProcess(commandFilePath);// 处理任务脚本输出内容parseProcessOutput(process);......}



14、buildProcess 方法 就是咱们最终需要关注的。

private void buildProcess(String commandFile) throws IOException {
// setting up user to run commands List<String> command = new LinkedList<>(); //init process builder ProcessBuilder processBuilder = new ProcessBuilder(); // setting up a working directory processBuilder.directory(new File(taskRequest.getExecutePath())); // merge error information to standard output stream processBuilder.redirectErrorStream(true); // setting up user to run commands command.add("sudo"); command.add("-u"); command.add(taskRequest.getTenantCode()); command.add(commandInterpreter()); command.addAll(Collections.emptyList()); command.add(commandFile); // setting commands processBuilder.command(command); process = processBuilder.start(); printCommand(command);}

15、该方法实现了使用 租户 执行该任务脚本,也就是咱们在前面看到的那句。


sudo -u root sh /tmp/dolphinscheduler/exec/process/3931179920032/3931236986272_1/11/21/11_21.command


到这儿,咱们这个追踪过程也该结束咯。





06


结尾



经过漫长煎熬,您终于看到了这儿……



本次主要和小伙伴们分享了在 DolphinScheduler 中理解租户的作用,希望该篇文章能够有幸帮助到您更好的理解在 DolphinScheduler 中租户的概念。好,话不多说,风紧,扯呼 ……




07


加入社区



随着国内开源的迅猛崛起,Apache DolphinScheduler 社区迎来蓬勃发展,为了做更好用、易用的调度,真诚欢迎热爱开源的伙伴加入到开源社区中来,为中国开源崛起献上一份自己的力量,让本土开源走向全球。


参与 DolphinScheduler 社区有非常多的参与贡献的方式,包括:


贡献第一个PR(文档、代码) 我们也希望是简单的,第一个PR用于熟悉提交的流程和社区协作以及感受社区的友好度。


社区汇总了以下适合新手的问题列表:https://github.com/apache/dolphinscheduler/issues/5689


非新手问题列表:https://github.com/apache/dolphinscheduler/issues?q=is%3Aopen+is%3Aissue+label%3A%22volunteer+wanted%22


如何参与贡献链接:https://dolphinscheduler.apache.org/zh-cn/community/development/contribute.html


来吧,DolphinScheduler开源社区需要您的参与,为中国开源崛起添砖加瓦吧,哪怕只是小小的一块瓦,汇聚起来的力量也是巨大的。


参与开源可以近距离与各路高手切磋,迅速提升自己的技能,如果您想参与贡献,我们有个贡献者种子孵化群,可以添加社区小助手微信(Leonard-ds) 手把手教会您( 贡献者不分水平高低,有问必答,关键是有一颗愿意贡献的心 )。添加小助手微信时请说明想参与贡献。







社区官网
https://dolphinscheduler.apache.org/

代码仓地址https://github.com/apache/dolphinscheduler

您的 Star,是 Apache DolphinScheduler 为爱发电的动力❤️ 

投稿请添加社区小助手微信

(Leonard-ds)




☞开源并不是大牛的专属,普通人也能有属于自己的一亩三分地

恭喜社区喜提三枚新 Committer!

在 Apache  DolphinScheduler 上调试 LDAP 登录,亲测有效!

☞4 亿用户,7W+ 作业调度难题,Bigo 基于 Apache DolphinScheduler 巧化解

☞荔枝机器学习平台与大数据调度系统“双剑合璧”,打造未来数据处理新模式!

看到社区代码不规范,强迫症的我顺手就给格式化了!

最佳实践|联通数科基于 DolphinScheduler 的二次开发

DolphinScheduler 荣获 2021 中国开源云联盟优秀开源项目奖!

☞巨变!a16z 关于新一代数据基础设施架构的深度洞察

手把手教你 Apache DolphinScheduler 本地开发环境搭建 | 中英文视频教程


点击阅读原文,加入开源!



点个在看你最好看

【声明】内容源于网络
0
0
海豚调度
Apache DolphinScheduler是一个分布式、去中心化、易扩展的可视化DAG工作流任务调度系统,其致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开箱即用。
内容 667
粉丝 0
海豚调度 Apache DolphinScheduler是一个分布式、去中心化、易扩展的可视化DAG工作流任务调度系统,其致力于解决数据处理流程中错综复杂的依赖关系,使调度系统在数据处理流程中开箱即用。
总阅读167
粉丝0
内容667