Jindo CLI 集成最佳实践

阿里云Jindo CLI是管理OSS-HDFS服务的命令行工具。本文通过Java Spring Boot封装Jindo CLI命令为API接口,实现OSS-HDFS的程序化管理。支持集中部署和分离式部署两种架构,适用于运维管控系统、自动化脚本、CI/CD工具等场景。

方案选型

方式一:集中部署

业务应用与Jindo CLI工具部署在同一台服务器,通过本地调用方式实现集成。如图所示,业务应用在业务服务器上直接调用Jindo CLI,再访问OSS-HDFS服务。

image
  • 优点:实现简单,无网络开销。

  • 缺点:CLI执行会占用主业务资源。

  • 适用场景:快速验证、内部自动化脚本、单一管理平台等场景。

方式二:分离式部署

业务应用(服务器 A)与 Jindo CLI(服务器 B)分离,CLI 封装为独立的 HTTP Agent 服务,通过 Restful API 通信。如图所示,业务应用远程调用 HTTP Agent,由 Agent 调用 Jindo CLI,最终访问 OSS-HDFS 服务。

image
  • 优点:AccessKey隔离在工具服务器;业务与工具解耦,支持独立升级与跨语言调用;可同时为多个业务平台提供服务。

  • 缺点:需额外维护Agent服务,存在一定网络开销;RESTful API调用默认无鉴权,需要客户自行实现安全校验(如使用Auth Token等机制)。

  • 适用场景:生产环境,尤其是对扩展性要求高的系统,以及需要为多个业务平台提供服务的场景。

前置准备

在开始部署前,请确保已完成以下准备工作:

  1. OSS-HDFS服务:已开通OSS-HDFS服务的Bucket。参见开通OSS-HDFS服务

  2. 权限准备:RAM用户拥有访问OSS-HDFS权限。建议遵循最小权限原则,例如,若仅需读取,则不授予写或删除权限 。参见授权访问OSS-HDFS服务

  3. 服务器环境:至少一台服务器(ECS 或自建),并满足以下条件:与 Bucket 同地域、能通过内网访问 OSS、已安装 JDK。

  4. Jindo SDK:已安装并配置 Jindo SDK。

    • 集中部署:在业务服务器上安装。

    • 分离式部署:仅在 Agent 服务器(服务器 B)上安装。

    点击展开:Jindo SDK安装步骤(如已安装可跳过)

    1. 下载并解压SDK

      本文以6.10.0版本,Linux x86平台为例。其他版本请参见JindoSDK版本记录其他平台请参见多平台部署JindoSDK

      # 下载Jindo SDK包
      wget https://jindodata-binary.oss-cn-shanghai.aliyuncs.com/release/6.10.0/jindosdk-6.10.0-linux.tar.gz
      
      # 解压到当前目录
      tar zxvf jindosdk-6.10.0-linux.tar.gz
    2. 创建配置文件

      进入解压后SDK/conf目录。

      cd jindosdk-6.10.0-linux/conf/
    3. 创建jindosdk.cfg文件,内容如下(请替换为实际配置):

      [common]
      # 保持默认即可
      logger.dir = /tmp/jindo/
      logger.sync = false
      logger.consolelogger = false
      
      [jindosdk]
      # 替换为您的Bucket所在地域的OSS-HDFS Endpoint
      fs.oss.endpoint = cn-hangzhou.oss-dls.aliyuncs.com
      # 替换为您的AccessKey
      fs.oss.accessKeyId = yourAccessKeyId
      fs.oss.accessKeySecret = yourAccessKeySecret
    4. 配置Jindo CLI环境变量

      Jindo CLI安装在/usr/lib/jindosdk-6.10.0-linux目录为例:

      export JINDOSDK_HOME=/usr/lib/jindosdk-6.10.0-linux
      export JINDOSDK_CONF_DIR=${JINDOSDK_HOME}/conf
      export PATH=${PATH}:${JINDOSDK_HOME}/bin
      说明

      为使环境变量永久生效,建议将此export命令添加至~/.bashrc文件中。

方式一:集中部署

业务应用与Jindo CLI 在同一服务器,直接在业务应用内部调用 Jindo CLI 命令。适用于快速验证。

步骤一:开发后端API接口

创建 Spring Boot API 接口,封装 jindo fs -ls 命令。

说明

不同版本的 Jindo SDK 主命令可能存在差异。部分新版本使用 jindo,而一些早期版本使用 jindofs。

在复制代码前,建议您先在服务器的命令行中手动执行jindo -vjindofs -v,以确认您环境中正确的命令。本文所有示例均以 jindo 为例。

package com.example.jindotest;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

@RestController
public class JindoController {

    @PostMapping("/jindo-ls")
    public ResponseEntity<String> executeJindoLs(@RequestParam("path") String path) {
        // 校验路径格式
        if (path == null || !path.startsWith("oss://")) {
            return ResponseEntity.badRequest().body("Invalid OSS path");
        }

        try {
            // 构建 Jindo CLI 命令
            ProcessBuilder processBuilder = new ProcessBuilder("jindo", "fs", "-ls", path);
            processBuilder.redirectErrorStream(true);

            // 启动进程
            Process process = processBuilder.start();

            // 读取命令输出
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuilder result = new StringBuilder();  
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line).append("\n");
            }

            // 等待进程结束并检查退出码
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                // 返回命令执行结果
                return ResponseEntity.ok(result.toString());
            } else {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body("Command failed with exit code: " + exitCode);
            }
        } catch (IOException | InterruptedException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Error executing command: " + e.getMessage());
        }
    }
}

步骤二:结果验证

完成部署后,可通过以下方式验证集成效果。

  1. web界面验证:

    参考附录Web界面示例代码,将代码中的请求路径修改为/jindo-ls。访问http://<业务服务器公网IP>:8080(需要打开业务服务器入方向的8080端口)进行测试。输入文件路径:如:oss://my-bucket.cn-hangzhou.oss-dls.aliyuncs.com/,单击"List Files"按钮。如果界面成功展示了OSS的文件列表,则集成成功。

    image

  2. API接口验证(备选)

    如果暂不方便部署前端,可使用 curl 直接测试后端接口是否工作正常。

    curl -X POST "http://localhost:8080/jindo-ls?path=oss://bucket.endpoint/"

    返回文件列表即为集成成功。

验证成功后,可参考附录中的常用Jindo CLI命令,将其他命令集成到您的系统中。

方式二:分离式部署

生产环境推荐。此方案将Jindo CLI封装为独立的Agent服务,实现责任分离。适用于内网环境或安全要求相对较低的场景,如需高安全要求,请自行实现API访问校验机制。

步骤一:在服务器 B 启动 Agent 服务

  1. 创建Agent脚本

    创建JindoCliHttpAgent.py文件,脚本使用Python3实现。其他技术栈可参考相同思路进行实现。

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    
    import http.server
    import json
    import subprocess
    import socketserver
    import shlex  
    
    class CommandHandler(http.server.SimpleHTTPRequestHandler):
        def do_POST(self):
            if self.path == '/execute':
                # 读取请求体
                content_length = int(self.headers.get('Content-Length', 0))
                post_data = self.rfile.read(content_length)
                try:
                    # 解析 JSON 请求
                    data = json.loads(post_data)
                    command = data.get('command', '')
    
                    # 验证命令参数
                    if not command:
                        self.send_response(400)
                        self.send_header('Content-Type', 'application/json')
                        self.end_headers()
                        self.wfile.write(json.dumps({'error': 'Missing "command" parameter'}).encode('utf-8'))
                        return
    
                    # 安全执行命令
                    command_list = shlex.split(command)
                    output = subprocess.check_output(command_list, stderr=subprocess.STDOUT)
                    output_str = output.decode('utf-8').strip()
    
                    # 返回执行结果
                    self.send_response(200)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(json.dumps({'output': output_str}).encode('utf-8'))
                except Exception as e:
                    # 返回错误信息
                    self.send_response(500)
                    self.send_header('Content-Type', 'application/json')
                    self.end_headers()
                    self.wfile.write(json.dumps({'error': str(e)}).encode('utf-8'))
            else:
                self.send_error(404, 'Not Found')
    
    # 多线程 HTTP 服务器
    class ThreadedHTTPServer(socketserver.ThreadingMixIn, http.server.HTTPServer):
        pass
    
    if __name__ == '__main__':
        # 启动 HTTP Agent 服务
        server_address = ('0.0.0.0', 8000)
        httpd = ThreadedHTTPServer(server_address, CommandHandler)
        print('Starting secure HTTP Agent on port 8000...')
        httpd.serve_forever()
    
  2. 配置防火墙

    为服务器B开放入方向8000端口,仅允许业务服务器A的私网IP访问,禁止公网访问。

  3. 添加执行权限

    chmod +x JindoCliHttpAgent.py
  4. 启动HTTP Agent服务

    python3 JindoCliHttpAgent.py

    服务启动后会监听8000端口请求,启动效果如下图所示。

    image.png

  5. 验证Agent服务

    新开终端,测试Agent是否正常工作。替换<Bucketname>和<EndPoint>为实际值,例如:oss://my-bucket.cn-hangzhou.oss-dls.aliyuncs.com/

    curl -X POST http://localhost:8000/execute \
       -H "Content-Type: application/json" \
       -d '{"command": "jindo fs -ls oss://<Bucketname>.<EndPoint>/"}'

    如果返回 JSON 格式的文件列表,如下图示,则证明Agent已部署成功。

    image

步骤二:在服务器A上集成业务系统

说明

不同版本的 Jindo SDK 主命令可能存在差异。部分新版本使用 jindo,而一些早期版本使用 jindofs。

在复制代码前,建议您先在服务器的命令行中手动执行jindo -vjindofs -v,以确认您环境中正确的命令。本文所有示例均以 jindo 为例。

创建Java API接口调用Agent服务。请将代码中的<服务器B的私网IP>替换为实际的服务器B私网IP地址。

package com.example.jindotest;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;

@RestController
public class JindoAgentController {
    
    @PostMapping("/jindo-agent-ls")
    public ResponseEntity<String> executeJindoLs(@RequestParam String path) {
        // 校验路径格式
        if (path == null || !path.startsWith("oss://")) {
            return ResponseEntity.badRequest().body("Invalid OSS path");
        }

        try {
            // 构造 JSON 请求体
            String requestBody = String.format("{\"command\": \"jindo fs -ls %s\"}", path);
      
            // 通过 curl 调用 Agent 服务
            ProcessBuilder processBuilder = new ProcessBuilder(
                "curl", "-X", "POST", "http://<服务器B的私网IP>:8000/execute", 
                "-H", "Content-Type: application/json",
                "-d", requestBody
            );
          
            // 启动进程
            Process process = processBuilder.start();

            // 读取输出流
            BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
            StringBuilder result = new StringBuilder();  
            String line;
            while ((line = reader.readLine()) != null) {
                result.append(line).append("\n");
            }

            // 检查执行结果
            int exitCode = process.waitFor();
            if (exitCode == 0) {
                
                return ResponseEntity.ok(result.toString());
            } else {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Command failed with exit code: " + exitCode);
            }
        } catch (IOException | InterruptedException e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error executing command: " + e.getMessage());
        }
    }
}
说明

生产环境建议使用RestTemplateWebClientHTTP客户端库替代curl,获得更好的性能和错误处理能力。

步骤三:结果验证

  1. 网络连通性检查

    如果采用分离式部署,需要确保服务器间网络连通。在服务器A上执行ping <服务器B私网IP>。若稳定收到ping响应,无丢包。则证明两台服务器连通。image

  2. web界面验证

    参考附录Web界面示例代码,将代码中的请求路径修改为/jindo-agent-ls。访问http://<服务器A公网IP>:8080(需要打开服务器A入方向的8080端口)进行测试。输入文件路径:如:oss://my-bucket.cn-hangzhou.oss-dls.aliyuncs.com/,单击"List Files"按钮。如果界面成功展示了OSS的文件列表,则集成成功。

    image

  3. API接口验证(备选方式)

    如果暂不方便部署前端,可使用 curl 直接测试后端接口是否工作正常。

    curl -X POST "http://localhost:8080/jindo-agent-ls?path=oss://<bucket>.<Endpoint>/"

    返回文件列表即为集成成功。

验证成功后,可参考附录中的常用Jindo CLI命令,将其他命令集成到您的系统中。

安全建议

Jindo CLI 具有 OSS-HDFS 服务配置管理和数据删除能力,使用不当可能造成严重后果。在生产环境部署前,务必在测试环境充分验证。

核心原则

  • 最小权限:仅授予完成任务所需的最小权限,避免过度授权。

  • 访问控制:在调用端实施严格的身份验证和授权机制。

  • 并发控制:限制并发操作数量,防止配置冲突和数据管理混乱。

网络安全

  • 分离式部署:Agent 服务器仅允许业务服务器私网 IP 访问,禁止公网暴露。

  • 端口管理:通过安全组或防火墙精确控制访问端口。

相关文档

附录

常用Jindo CLI命令

命令

功能说明

命令示例

stat

显示文件状态。

jindo fs -stat oss://<bucket-name>.<oss-hdfs-endpoint>/<file>

ls

列出目录下文件。可选参数-R,表示递归显示。

jindo fs -ls [-R] oss://<bucket-name>.<oss-hdfs-endpoint>/<dir>

du

显示目录中所有文件的大小。可选参数如下:

  • -s:求目标文件夹的总和。

  • -h:标准单位显示。

jindo fs -du oss://<bucket-name>.<oss-hdfs-endpoint>/<dir>

count

显示文件大小以及文件数量。可选参数-h,显示文件大小单位。

jindo fs -count -h oss://<bucket-name>.<oss-hdfs-endpoint>/<dir>

listUserGroupsMappings

列出所有用户和组的关系。

jindo admin -listUserGroupsMappings -dlsUri oss://<bucket-name>.<oss-hdfs-endpoint> [-maxKeys <value>] [-marker <value>]

dumpInventory

导出文件元数据。

jindo admin -dumpInventory oss://<bucket-name>.<oss-hdfs-endpoint>/<dir>

putConfig

服务特性设置(如设置目录保护)

jindo admin putConfig -dlsUri oss://<bucket-name>.<oss-hdfs-endpoint> -conf <key1=value1> -conf <key2=value2> ...]

getConfig

获取配置信息(如目录保护信息)

jindo admin getConfig -dlsUri oss://<bucket-name>.<oss-hdfs-endpoint> -name <keys>

Web界面示例代码

将以下代码保存为index.html,放置在Spring Boot项目的src/main/resources/static/目录下:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Jindo CLI Management Interface</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 1000px;
            margin: 50px auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background-color: white;
            padding: 30px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            text-align: center;
            border-bottom: 2px solid #007bff;
            padding-bottom: 10px;
        }
        .form-group {
            margin-bottom: 20px;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #555;
        }
        input[type="text"] {
            width: 100%;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 16px;
            box-sizing: border-box;
        }
        button {
            background-color: #007bff;
            color: white;
            padding: 12px 24px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            margin-right: 10px;
        }
        button:hover {
            background-color: #0056b3;
        }
        button:disabled {
            background-color: #6c757d;
            cursor: not-allowed;
        }
        .result {
            margin-top: 20px;
            padding: 15px;
            border: 1px solid #ddd;
            border-radius: 4px;
            background-color: #f8f9fa;
            min-height: 100px;
            font-family: monospace;
            white-space: pre-wrap;
            font-size: 14px;
        }
        .loading {
            color: #007bff;
            font-style: italic;
        }
        .error {
            color: #dc3545;
            background-color: #f8d7da;
            border-color: #f5c6cb;
        }
        .success {
            color: #155724;
            background-color: #d4edda;
            border-color: #c3e6cb;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1> Jindo CLI Management Interface</h1>
        
        <div class="form-group">
            <label for="ossPath">OSS Path:</label>
            <input type="text" id="ossPath" 
                   value="oss://your-bucket-name.cn-hangzhou.oss-dls.aliyuncs.com/" 
                   placeholder="Enter OSS path (e.g., oss://bucket-name.endpoint/)">
        </div>
        
        <div class="form-group">
            <button onclick="listFiles()">List Files (ls)</button>
            <button onclick="clearResult()">Clear Result</button>
        </div>
        
        <div id="result" class="result">
            Ready to execute Jindo CLI commands...
        </div>
    </div>

    <script>
        function listFiles() {
            const path = document.getElementById('ossPath').value;
            const resultDiv = document.getElementById('result');
            
            if (!path || !path.startsWith('oss://')) {
                resultDiv.innerHTML = 'Error: Please enter a valid OSS path starting with "oss://"';
                resultDiv.className = 'result error';
                return;
            }
            
            // Show loading
            resultDiv.innerHTML = 'Loading... Please wait';
            resultDiv.className = 'result loading';
            
            // Call backend API
            fetch('/jindo-ls', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
                body: 'path=' + encodeURIComponent(path)
            })
            .then(response => {
                if (response.ok) {
                    return response.text();
                } else {
                    throw new Error('HTTP ' + response.status + ': ' + response.statusText);
                }
            })
            .then(data => {
                resultDiv.innerHTML = 'Success:\n\n' + data;
                resultDiv.className = 'result success';
            })
            .catch(error => {
                resultDiv.innerHTML = 'Error:\n\n' + error.message;
                resultDiv.className = 'result error';
            });
        }
        
        function clearResult() {
            document.getElementById('result').innerHTML = 'Ready to execute Jindo CLI commands...';
            document.getElementById('result').className = 'result';
        }
        
        // Allow Enter key to trigger listFiles
        document.getElementById('ossPath').addEventListener('keypress', function(e) {
            if (e.key === 'Enter') {
                listFiles();
            }
        });
    </script>
</body>
</html>