全部產品
Search
文件中心

Object Storage Service:Jindo CLI 整合最佳實務

更新時間:Sep 11, 2025

阿里雲Jindo CLI是管理OSS-HDFS服務的命令列工具。本文通過Java Spring Boot封裝Jindo CLI命令為API介面,實現OSS-HDFS的程式化管理。支援集中式部署和分離式部署兩種架構,適用於營運管控系統、自動化指令碼、CI/CD工具等情境。

方案選型

方式一:集中式部署

業務應用與Jindo CLI工具部署在同一台伺服器,通過本地調用方式實現整合。如圖所示,業務應用在商務服務器上直接調用Jindo CLI,再訪問OSS-HDFS服務。

  • 優點:實現簡單,無網路開銷。

  • 缺點:CLI執行會佔用主業務資源。

  • 適用情境:快速驗證、內部自動化指令碼、單一管理平台等情境。

方式二:分離式部署

業務應用(伺服器 A)與 Jindo CLI(伺服器 B)分離,CLI 封裝為獨立的 HTTP Agent 服務,通過 Restful API 通訊。如圖所示,業務應用遠程調用 HTTP Agent,由 Agent 調用 Jindo CLI,最終訪問 OSS-HDFS 服務。

  • 優點: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平台為例。

      # 下載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());
        }
    }
}
說明

生產環境建議使用RestTemplate或WebClient等HTTP用戶端庫替代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>