在 Cloud Run 中執行程式碼

Cloud Run 沙箱可讓您直接在容器工作負載中,於隔離環境執行不受信任的程式碼。這項功能可協助 AI 代理安全執行子代理、執行運算工作,或啟動自動化瀏覽器,而不會對主機系統造成風險。

主要優點

Cloud Run 沙箱提供下列主要優勢:

  • 快速建立:沙箱可互動,幾乎能立即執行指令。在代理執行的現有 Cloud Run 資源中建立沙箱,可縮短建立時間,不必為每項工作建立新的 Cloud Run 資源。這項效率有助於確保代理能及時回應。

  • 安全性:沙箱會隔離程序執行作業。根據預設,沙箱無法存取父項工作負載、環境變數、密鑰或 Google Cloud 中繼資料伺服器。所有沙箱都完全獨立。

  • 存取控管和環境:程序會以非超級使用者的身分,使用 sudo 權限執行,讓您在執行期間使用 apt、pip 或 npm 等套件管理工具安裝工具。雖然沙箱環境是暫時性的,會在完成後刪除,但您可以使用永久目錄或快照,將特定工作區或對應資料儲存至 Cloud Storage bucket。

事前準備

  1. 登入 Google Cloud 帳戶。如果您未曾使用過 Google Cloud,歡迎 建立帳戶,親自體驗實際使用我們產品的成效。新客戶還能獲得價值 $300 美元的免費抵免額,用於執行、測試及部署工作負載。
  2. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  3. Verify that billing is enabled for your Google Cloud project.

  4. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  5. Verify that billing is enabled for your Google Cloud project.

  6. 安裝並初始化 gcloud CLI。
  7. 部署 Cloud Run 資源,例如第二代 服務、工作、worker 集區或執行個體。

啟用沙箱

如要使用沙箱,您必須先使用 --sandbox-launcher 旗標或 sandboxLauncher YAML 屬性,在 Cloud Run 資源上啟用沙箱。如要查看詳細設定步驟,請選擇下列資源類型:

啟動沙箱

啟用沙箱後,您可以在容器執行環境中啟動沙箱。沙箱二進位檔位於 /usr/local/gcp/bin/sandbox。

本指南中的範例使用 sandbox 指令,而非絕對路徑 /usr/local/gcp/bin/sandbox。

您可以在原始碼中參照二進位檔的絕對路徑,藉此執行二進位檔。舉例來說,如要在獨立沙箱中輸出 Hello,請選擇下列其中一個選項:

Node.js

如要從 Node.js 應用程式執行沙箱指令,請加入下列程式碼:

exec(`sandbox do -- /bin/echo "Hello"`, (e, stdout, stderr) => {
    res.send({ stdout, stderr });
});

Python

如要從 Python 應用程式執行沙箱指令,請加入下列程式碼:

import subprocess
result = subprocess.run(
    ["sandbox", "do", "--", "/bin/echo", "Hello"],
    capture_output=True,
    text=True,
)
return {"stdout": result.stdout, "stderr": result.stderr}

Go

如要從 Go 應用程式執行沙箱指令,請加入下列程式碼:

cmd := exec.Command("sandbox", "do", "--", "/bin/echo", "Hello")
out, err := cmd.CombinedOutput()

Sandbox CLI

如要直接從指令列執行沙箱指令,請執行下列指令:

sandbox do -- /bin/echo "Hello"

如要查看可用指令的完整清單,請執行 sandbox -h 指令。

使用沙箱指令列功能

sandbox 指令列工具包含執行、設定及管理沙箱的指令。

在沙箱中執行指令

您可以使用 sandbox do 指令,在新的臨時沙箱中執行指令。sandbox do 指令會執行下列工作:

  1. 啟動沙箱環境 (sandbox run)。
  2. 執行您指定的指令 (sandbox exec)。
  3. 成功執行後刪除沙箱 (sandbox delete)。

舉例來說,如要在沙箱中執行數學運算,請根據您偏好的語言執行下列程式碼片段。請確認您執行的任何指令或工具 (例如 python3) 都已安裝在容器映像檔中:

Node.js

如要從 Node.js 應用程式執行沙箱指令,請按照下列步驟操作:

exec(`sandbox do -- /usr/bin/python3 -c "print(1+2)"`, (e, stdout, stderr) => {
    res.send({ stdout, stderr });
});

Python

如要透過 Python 應用程式執行沙箱指令,請按照下列步驟操作:

import subprocess
result = subprocess.run(
    ["sandbox", "do", "--", "/usr/bin/python3", "-c", "print(1+2)"],
    capture_output=True,
    text=True,
)
return {"stdout": result.stdout, "stderr": result.stderr}

Go

如要從 Go 應用程式執行沙箱指令,請按照下列步驟操作:

cmd := exec.Command("sandbox", "do", "--", "/usr/bin/python3", "-c", "print(1+2)")
out, err := cmd.CombinedOutput()

Sandbox CLI

如要直接從指令列執行沙箱指令,請按照下列步驟操作:

sandbox do -- /usr/bin/python3 -c "print(1+2)"

如果您執行指令時只使用名稱,而未使用絕對路徑 (例如使用 python3 而非 /usr/bin/python3),請使用 --env 旗標,在沙箱中明確設定 PATH 環境變數。

在不同執行作業中保留資料

沙箱預設為暫時性。如要在同一個 Cloud Run 執行個體中,跨不同沙箱執行作業保留資料,可以使用標準 tar 封存檔案匯入及匯出工作區檔案系統狀態。或者,您也可以設定繫結掛接,直接在主機容器和沙箱環境之間共用目錄。

執行 sandbox do 指令時,請使用下列旗標:

  • --export-tar:完成後,將修改過的疊加檔案擷取到 tar 封存檔案中。
  • --import-tar:在執行前,將 tar 封存檔案中的檔案擷取到沙箱。
  • --sync-tar:執行前先匯入,完成後再匯出,執行雙向同步處理。

沙箱內的 /tmp 目錄不屬於根檔案系統疊加層,因此封存檔案不包含寫入 /tmp 的檔案。如要保留資料,請將資料寫入其他目錄,例如 /workspace。

舉例來說,如要使用封存檔案在兩個沙箱呼叫之間傳遞資料,請執行下列指令:

  1. 在沙箱中寫入資料,並將狀態匯出至封存檔案:

    sandbox do --write --export-tar=/tmp/work.tar \
      -- /usr/bin/bash -c "mkdir -p /workspace && echo 'task-complete' > /workspace/status.txt"
    
  2. 在後續呼叫中匯入封存檔案,以擷取資料:

    sandbox do --write --import-tar=/tmp/work.tar \
      -- /usr/bin/bash -c "cat /workspace/status.txt"
    

或者,如要使用單一指令自動匯入現有封存狀態並匯出新變更,請使用 --sync-tar=/tmp/work.tar。沙箱程序終止時,Cloud Run 會永久刪除未匯出至封存檔案的臨時疊加檔案。

在背景執行指令

如要執行長時間執行的程序、無頭瀏覽器或背景伺服器 (例如持續監聽傳入要求的背景代理迴圈),請使用 --detach 旗標。

舉例來說,執行下列指令,即可啟動含有閒置或背景程式的分離沙箱:

sandbox run my-web-server --detach -- /usr/bin/long_running_or_idle_program

您可以使用 detach 旗標,在多項測試中重複使用同一個沙箱。如要在執行中的分離沙箱內互動或執行其他指令,請使用 sandbox exec 指令,並依名稱指定沙箱。

舉例來說,如要在現有的 my-web-server 背景沙箱中執行測試指令,請執行下列指令:

sandbox exec my-web-server -- /usr/bin/python3 -c "print('test-complete')"

設定環境變數

在沙箱上設定環境變數,就像設定其他容器一樣。沙箱不會沿用主機容器的環境變數。執行 sandbox 指令時,您必須使用 --env 旗標明確提供這些引數。

舉例來說,如要將設定變數傳遞至沙箱,請執行下列指令:

sandbox do --env AGENT_MODE="test" -- /usr/bin/bash -c "echo \$AGENT_MODE"

請避免使用 env 旗標傳遞 Secret,因為沙箱程序可能會看到這些 Secret。

建立檔案系統快照

在背景部署具名沙箱,處理網頁伺服器或長時間執行的 Agentic Workflows 等持續性工作、動態執行沙箱指令,並將修改後的檔案系統狀態擷取至 tar 封存檔。

舉例來說,如要部署背景沙箱、將檔案寫入其疊加層,並建立其狀態的快照,以驗證資料是否擷取,請執行下列指令:

  1. 在背景部署已命名的沙箱,並啟用寫入權限,在工作區中建立檔案。將檔案寫入 /tmp 以外的路徑,因為寫入沙箱內 /tmp 的檔案不會擷取到快照中:

    sandbox run --write my-sandbox --detach -- /usr/bin/bash -c "echo 'hi' > /workspace/hello.txt && sleep 1h"
    
  2. 使用 sandbox tar 指令,為執行中沙箱的修改檔案系統建立快照:

    sandbox tar my-sandbox --file=/tmp/foo.tar
    
  3. 擷取並確認快照封存檔案包含沙箱中寫入的資料:

    tar -xvf /tmp/foo.tar
    

    您應該會看到下列結果:

    ./
    ./workspace/
    ./workspace/hello.txt
    

設定網路

根據預設,系統會封鎖沙箱的所有傳出流量。如要允許傳出網路存取,請使用 --allow-egress 旗標:

舉例來說,如要從外部端點擷取資料,請執行下列指令:

sandbox do --allow-egress -- /usr/bin/python3 -c 'import urllib.request; print(urllib.request.urlopen("https://google.com").getcode())'

這個指令會傳回標準 HTTP 狀態碼 200,表示連線成功。

存取檔案系統

根據預設,在沙箱中執行的程序對主機容器根檔案系統具有唯讀存取權。您可以使用 --write 旗標,啟用寫入暫存檔案系統 (tmpfs) 疊加層的功能。不過,刪除沙箱後,寫入的資料就會遺失。如要啟用對主機容器的永久寫入功能,可以設定繫結掛接。

即使沒有 --write 旗標,沙箱中的 /tmp 目錄一律可寫入。刪除沙箱時,系統會刪除 /tmp 目錄,且該目錄不會包含在快照或封存檔案中。

預設唯讀存取權

在沙箱中,程序可以讀取主機容器中的檔案,但無法寫入 /tmp 以外的根檔案系統。

以下範例假設您是從主機容器的根目錄 (/) 執行指令。

執行下列指令,驗證預設的唯讀存取權:

  1. 在主機容器上建立 Python 指令碼:

    mkdir -p /tmp/my-scripts
    echo "print('hi')" > /tmp/my-scripts/task.py
    
  2. 確認檔案存在於本機:

    cat /tmp/my-scripts/task.py
    
  3. 在沙箱中執行檔案:

    sandbox do -- /usr/bin/python3 /tmp/my-scripts/task.py
    

    這個指令會傳回 hi,確認沙箱具有讀取權限。

    如果嘗試直接將資料寫入沙箱根檔案系統,但未進行額外設定,執行作業就會失敗。舉例來說,如果嘗試寫入預設沙箱中的根目錄 (/),系統會傳回唯讀檔案系統錯誤:

    執行下列指令,寫入根檔案系統:

    sandbox do -- /usr/bin/bash -c "echo 'hi' > /testfile.txt"
    

    指令失敗,並顯示下列錯誤:

    /usr/bin/bash: line 1: /testfile.txt: Read-only file system
    Error: failed to exec in container: cmd.Wait(exec) failed: exit status 1
    

使用繫結掛接共用資料

如要允許沙箱內的程序寫入可持續資料,請使用 --mount 旗標附加共用磁碟區:

  1. 在主機容器上建立共用磁碟區目錄,並在其中植入初始檔案:

    mkdir -p /tmp/my-volume
    echo 'read' > /tmp/my-volume/readwrite.txt
    
  2. 執行沙箱,從繫結掛接路徑讀取檔案:

    sandbox do --mount type=bind,source=/tmp/my-volume,destination=/mnt/my-mount -- /usr/bin/bash -c "cat /mnt/my-mount/readwrite.txt"
    

    這個指令會傳回 read。

  3. 執行沙箱,將新資料從掛接點寫回主機:

    sandbox do --mount type=bind,source=/tmp/my-volume,destination=/mnt/my-mount -- /usr/bin/bash -c "echo 'write' > /mnt/my-mount/readwrite.txt"
    
  4. 在主機容器上,確認沙箱已成功修改檔案:

    cat /tmp/my-volume/readwrite.txt
    

    這個指令會傳回 write。

設定唯讀掛接

如要授予沙箱主機目錄存取權,同時明確禁止沙箱修改檔案,請將 readonly 屬性附加至掛接規格。

舉例來說,執行下列指令,測試唯讀繫結掛接的寫入限制:

sandbox do --mount type=bind,source=/tmp/my-volume,destination=/mnt/my-mount,readonly -- /usr/bin/bash -c "echo 'fails' > /mnt/my-mount/hello.txt"

寫入嘗試失敗,並顯示下列錯誤:

/usr/bin/bash: line 1: /mnt/my-mount/hello.txt: Read-only file system
Error: failed to exec in container: cmd.Wait(exec) failed: exit status 1

查看記錄

Cloud Run 會自動在 Cloud Logging 中擷取沙箱生命週期事件,例如執行開始和結束。

sandbox CLI 會將沙箱指令的標準輸出內容 (stdout) 和標準錯誤 (stderr),直接寫入叫用程序的標準串流。如要在 Cloud Logging 查看這些記錄,請將串流轉送至容器的標準輸出內容和標準錯誤:

Node.js

const { exec } = require('child_process');
const child = exec('sandbox do -- /usr/bin/python3 -c "print(1+2)"');
child.stdout.pipe(process.stdout);
child.stderr.pipe(process.stderr);

Python

subprocess.run(
    ["sandbox", "do", "--", "/usr/bin/python3", "-c", "print(1+2)"],
    stdout=sys.stdout,
    stderr=sys.stderr,
)

Go

cmd := exec.Command("sandbox", "do", "--", "/usr/bin/python3", "-c", "print(1+2)")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Run()

後續步驟