環境:接上你自己的 dev

你改了一兩個服務,想在箱子裡把它們跑起來,其餘的照舊用你現成的 dev:同一個資料庫、同一個 Redis、沒改的內部服務。環境就是做這件事的。

一個環境是你的一套 dev(或 staging),由三樣東西組成:

  • 連線:你在自己內網跑的連接器。它主動往外連 ParallelSandbox,你的防火牆不用開任何入口。箱子只連得到你在連線上列出的 host:port。一個環境可以有好幾條連線,一個網路一條(一個 VPC、一間辦公室);每條有自己的 token 與自己的位址清單。
  • 服務設定:每個服務在這個環境的整組環境變數,從 AWS ECS 匯入或上傳 .env。箱子裡是 /work/.sbx/env/<服務>.env 與 .sh。
  • externalBaseUrl:沒改的 HTTP 服務的網址(選填),與 sandbox_start 的同名參數一樣。

agent 開箱時帶 environment,箱子裡連 db.internal:5432 就會經你的連接器到你的資料庫,容器裡也一樣;改過的服務用它自己的設定檔啟動,設定跟它在 dev 上跑的時候一模一樣。

箱子怎麼連到一個位址

  1. 開箱時,箱子從環境的所有連線拿到位址清單。每個主機名在箱子裡有自己的位址(取自 198.18.0.0/15),箱子的 DNS 用它回答這個名字;往那個 host:port(IP 位址則是 IP:port)的連線,會轉到 boxd 替這個位址開的入口。boxd 不在位址本身的 port 上聽,所以 port 80 的位址(例如內部 load balancer)跟其他位址一樣能用,箱子裡的程式也可以聽同一個 port 號而不衝突:你在箱子裡跑自己的 Postgres 聽 5432 時,localhost:5432 是你的,db.internal:5432 照樣連到你的內網。
  2. 箱子裡的程式連過去時,ParallelSandbox 把這條連線交給「列了這個確切 host:port」的那條連線(那個網路),由那條連線的連接器在你的內網裡接通。兩條連線列了同一個位址時,用名稱排序在前的那條;每個位址請列在連得到它的那條連線上。
  3. 你的名字由連接器在你的內網裡解析,所以私有 DNS(Route 53 私有 hosted zone、Cloud Map、辦公室 DNS)跟你的服務用起來一樣。
  4. 預設 bridge、自訂的 Docker 網路與 docker compose 裡的容器,跟箱子自己的程式用同樣的方式連到這些位址:箱子的規則兩邊都套,箱子裡 Docker 的 DNS 也轉給箱子的解析器。
  5. 從頭到尾都是單純的 TCP:到你服務的 TLS 在中間不會被解開,憑證的行為跟在你的 dev 上一樣。
  6. 箱子經連線送出去的資料算它往外的流量(box_egress,見點數);你的網路回過來的不計量。
  7. 位址也可以是公開的主機,例如 api.partner.example:443。箱子就經連接器連過去,流量從你自己的網路、用它的公開 IP 出去。只接受已知 IP 的外部服務,就要這樣連:箱子自己連網際網路的流量,是從它所在主機的公開 IP 出去的,每台主機不一樣,主機來來去去也會跟著變(其他事實)。

你在箱子 services 宣告的名字若也是環境的位址,就改指到箱子(見在箱子裡用);link 到你另一個箱子、而且 host:port 相同的,也會蓋過那個位址(連到別的箱子)。你的網路上沒有任何東西能連進箱子:留在 dev 上的服務照樣呼叫 dev 上的那一份。

沒列出的東西一律連不到,各有各的樣子(在箱子上實測):

  • 列出的私有 IP 位址用沒列出的 port 連,跟沒列出的一樣沒有回應:連線會逾時,private-endpoints.log 也不會寫任何東西。
  • 列出的主機或箱子裡宣告的名字,用沒列、沒宣告的 port 連,會留在箱子裡:箱子裡在 0.0.0.0 聽那個 port 的程式會回應,沒有的話連線當場被拒絕。
  • 沒列出(也沒宣告)的主機名,由 ParallelSandbox 的解析器去查,它只回答公開的 DNS,不知道你的私有區域:
    • 只存在你私有 DNS 裡的名字(Cloud Map、Route 53 私有託管區域、辦公室的 DNS、私有 API Gateway 的 <api-id>.execute-api.<region>.amazonaws.com)解析不到:Could not resolve host、no such host、NXDOMAIN。把它連同撥的 port(API Gateway 是 443)列在連得到它的那條連線上,連接器就會在你的網路裡解析它;
    • 公開 DNS 會解析成私有 IP 的名字,例如 RDS endpoint 或 internal-….elb.amazonaws.com 的 load balancer,解析得到,接著連線就像沒列出的私有 IP 一樣逾時;要連到它就把它列上去;
    • 公開的名字照常解析,直接連上網際網路。
  • 列出的 IP 位址是在箱子裡被攔下來的:boxd 的規則把那個 IP 與 port 的連線,不管來自箱子的程式還是容器,都送到它為這條連線開的入口。
  • 沒列出的私有 IP 位址沒有回應:連線會逾時,private-endpoints.log 也不會寫任何東西。
  • port 80 不一樣:列在 port 80 的位址本身跟其他位址一樣能用,但列出的主機或宣告的名字在沒列、沒宣告 80 的情況下用 80 連,會連到 boxd 的場景入口,它回 502 nothing listens on <name>:80 in this box: that name is declared on another port. Dial the port it is declared on, or declare it on port 80 with targetPort, the port your process listens on。2026-09-24 20:30 UTC 之前開的箱子則是連到第一個宣告的服務。

設定

環境由 agent 用 REST(見下)設好,再用 sandbox_environments 讀結果;沒有畫面。只有兩件事要有人在你的網路裡做:跑連接器,以及用 AWS 的話建角色。順序:

  1. 建環境:POST /v1/environments,取個名字,例如 dev。agent 開箱時用這個名字。
  2. 新增連線,一個網路一條:POST /v1/environments/{env}/connections。回應裡有 runCommand,是一行 docker run,token 只顯示這一次(換發時再給一次)。在那個網路裡連得到那些資料庫與服務的機器上跑它(見下方「連接器」)。幾秒內 sandbox_environments 就會顯示這條連線 online。
  3. 設每條連線的位址:PUT /v1/environments/{env}/connections/{id}/endpoints,送整份清單,一個 host:port 一筆,照你服務設定裡的寫法,例如 mydb.xxxx.ap-northeast-1.rds.amazonaws.com:5432、cache.internal:6379、10.0.1.5:8080。一行一個 host:port,照你服務設定裡的寫法,例如 mydb.xxxx.ap-northeast-1.rds.amazonaws.com:5432、cache.internal:6379、10.0.1.5:8080。有了服務設定之後,GET /v1/environments/{env}/suggested-endpoints 會回從設定裡找到的位址:每個值裡的 host:port、每個網址(沒寫 port 的用 scheme 的預設:postgres 5432、mysql 3306、redis 6379、mongodb 27017、amqp 5672、http 80、https 443),以及配得上 *_PORT 的 *_HOST 變數;主機是私有 IPv4(10.0.0.0/8、172.16.0.0/12、192.168.0.0/16),或結尾是 .internal、.local、.lan、.corp、.svc、.cluster.local、.rds.amazonaws.com、.cache.amazonaws.com、.es.amazonaws.com、.mq.amazonaws.com、.docdb.amazonaws.com、.redshift.amazonaws.com 的才列。其他的,例如內部 load balancer 的 internal-*.elb.amazonaws.com 或用你們自己網域的私有 zone,要自己加。可能會改的服務,它們的內網名字也要列:沒改它的箱子連到你 dev 上的那一份,在 sandbox_start 宣告了那個名字的箱子則會接走它。
  4. 服務設定:從 AWS 匯入(POST .../services/import)或上傳 .env(PUT .../services/{name}),下面都有說。

用 REST 設定

上面的事都是 https://api.parallelsandbox.com 的 REST,帶 Authorization: Bearer <token>。token 是帳號的 psbx_ API key,或已接上的 client 的 OAuth access token(一小時過期;key 從哪來見REST)。MCP 工具 sandbox_environments 只讀:列出環境、連線與連得到的位址;建立與修改走 REST。{env} 是環境的名字,{id} 是 GET /v1/environments/{env} 裡連線的 id。設定的值只能寫、讀回來只有變數名字。

請求 內容 回傳
GET /v1/environments { "environments": [...] },每個跟下面一樣
POST /v1/environments { "name": "dev" },可加 externalBaseUrl;名字是 1 到 32 個小寫英文、數字與減號 環境:name、externalBaseUrl、awsRoleArn、awsRegion、connections[](id、name、online、sessions、lastSeenAt、connectorVersion、endpoints)、services[]
GET、PUT、DELETE /v1/environments/{env} PUT:externalBaseUrl、awsRoleArn、awsRegion 任選(有角色就要有 region;"" 清掉) 環境;DELETE 回 { "ok": true }
POST /v1/environments/{env}/connections { "name": "office" } connection、token 與 runCommand(連接器的 docker run 那一行);token 只在這裡與換發時出現
POST /v1/environments/{env}/connections/{id}/token 新的 token 與 runCommand;舊 token 立刻失效,用它的連接器會被斷線
DELETE /v1/environments/{env}/connections/{id} { "ok": true }
PUT /v1/environments/{env}/connections/{id}/endpoints { "endpoints": [{ "host": "mydb.internal", "port": 5432 }] },整份清單,最多 100 個 連線
GET /v1/environments/{env}/suggested-endpoints { "endpoints": [...] },從服務設定裡找到的位址
GET /v1/environments/{env}/services { "services": [...] },有 name、source(dotenv 或 aws-ecs)、keys、syncedAt
PUT /v1/environments/{env}/services/{name} { "dotenv": "KEY=value\n…" } 服務
POST /v1/environments/{env}/services/import { "region", "cluster", "service" },task 有好幾個容器時加 container,name 預設是 service 服務
POST /v1/environments/{env}/services/{name}/sync 服務,重新從 ECS 匯入
DELETE /v1/environments/{env}/services/{name} { "ok": true }
GET、PUT、DELETE /v1/aws PUT:{ "roleArn", "region" } 匯入用的唯讀角色,附它要信任的 externalId 與一鍵建立的 CloudFormation quickCreateUrl
GET /v1/aws/ecs/services?region=… 那個角色看得到的 ECS 服務

例如建一個環境、一條連線與一個服務的設定:

A=https://api.parallelsandbox.com/v1; H="Authorization: Bearer $PSBX_TOKEN"   # API key 或 OAuth access token
curl -s -H "$H" -X POST $A/environments -d '{"name":"dev"}'
curl -s -H "$H" -X POST $A/environments/dev/connections -d '{"name":"aws"}'    # token 與 runCommand,只出現這一次
curl -s -H "$H" -X PUT $A/environments/dev/connections/<id>/endpoints -d '{"endpoints":[{"host":"mydb.internal","port":5432}]}'
jq -Rs '{dotenv: .}' api.env | curl -s -H "$H" -X PUT $A/environments/dev/services/api --data-binary @-

連接器

docker run -d --name parallelsandbox-connector --restart=always \
  -e PSBX_CONNECTOR_TOKEN=psbx_conn_... \
  public.ecr.aws/b2n6a1j1/connector:latest
環境變數 說明
PSBX_CONNECTOR_TOKEN 必填,建立連線時拿到的 token
PSBX_URL ParallelSandbox 的 API,預設 https://api.parallelsandbox.com
PSBX_SESSIONS 同時開幾條到 ParallelSandbox 的連線,預設 2,範圍 1 到 8
PSBX_ALLOW 選填,這台連接器只准連的目標,逗號分隔:db.internal:5432,*.svc.local:*,10.0.0.0/8:*
  • 放在連得到目標的地方:跟你的服務同一個 VPC、同一個 Kubernetes cluster,或同一台機器。在 AWS 上可以跑成一個 ECS service(Fargate,1 個 task),子網與 security group 用你服務的那組(指令在團隊設定)。
  • 只往外連 api.parallelsandbox.com:443(WebSocket over TLS)。有 HTTP proxy 的話,照慣例設 HTTPS_PROXY。
  • 同一個 token 可以跑好幾份,任何一份在線上箱子就連得到。ParallelSandbox 換版時連接器會自己換到新的機器,新開的連線走新機器;已經開著的連線繼續用舊機器,舊機器最長一小時後下線時才斷,資料庫連線池會自己重連。
  • 能連到哪裡由連線上列的位址決定,ParallelSandbox 那邊先擋;PSBX_ALLOW 讓你在自己的機器上再限一次。
  • 連接器只有容器 image(public.ecr.aws/b2n6a1j1/connector,linux/amd64 與 linux/arm64),沒有另外的執行檔可下載。沒有 Docker 的主機要用別的 OCI runtime,例如 Podman(帶同樣的 -e 變數),或跑在 ECS、Kubernetes 上。
  • token 外洩或換人管理時,呼叫 POST /v1/environments/{env}/connections/{id}/token:舊 token 立刻失效,連著的連接器馬上斷線,用新 token 重新啟動。

規模與備援

  • 每個網路一個連接器,就能服務帳號裡的每個箱子,不管是誰開的。箱子往你的位址開的每條連線,是連接器某條 session(它連到 ParallelSandbox 的 WebSocket)裡的一條 stream,不是另外一條連線。
  • PSBX_SESSIONS(預設 2,1 到 8)是一份連接器同時開幾條 session。多於一條時,ParallelSandbox 換版期間另一條還在。
  • 要備援,就在另一台主機或另一個可用區域用同一個 token 再跑一份。ParallelSandbox 把每一份的 session 放在同一池:新的箱子連線先用收到它的那台 ParallelSandbox 機器上的 session,再來是別台機器上的,換版中正要移走的放最後。每一份都要連得到連線上列的每個位址:選中的那份連不到目標時,箱子當場拿到 502,不會改試另一份。
  • 連接器只是在 stream 與你的目標之間搬 bytes,最吃的是網路。在 AWS 上看 Fargate task 在 CloudWatch 的 CPU 與網路指標,偏高就加 CPU 或再跑一份。
  • 箱子凍結或停掉時,ParallelSandbox 會在大約一分鐘內關掉它的連線,不會一直佔著連接器上的 stream。
  • 環境與連接器在 ParallelSandbox 不收費;箱子經連線送出去的資料算它往外的流量(點數)。連接器本身的運算與網路(Fargate task、NAT gateway、辦公室的主機)由你的雲端或機房計費。

從 AWS 匯入服務設定

ParallelSandbox 用你授權的唯讀角色讀 ECS 服務目前的 task definition:environment 原樣帶入,secrets 裡引用的 SSM 參數與 Secrets Manager 值(含 :json-key)解開後帶入。environmentFiles(S3 上的檔案)讀不到,會列在「沒匯入」裡。設定預設用 ECS 服務的名字存,也可以另外取名;這個名字就是 /work/.sbx/env/<服務>.env 裡的 <服務>。

要帶上 S3 的 environmentFiles 檔案,就把它的內容當 .env 用另一個名字上傳(PUT .../services/{name}),例如 api-files,服務啟動時兩個檔案都給,這個放前面,讓 task definition 自己的值蓋過它,跟在 ECS 上一樣:docker run --env-file /work/.sbx/env/api-files.env --env-file /work/.sbx/env/api.env ...,或 . api-files.sh && . api.sh。用匯入的那個服務名稱上傳,會取代它匯入的設定。

連接 AWS 帳號(一個帳號做一次,走 REST):

  1. GET /v1/aws 回角色要信任的 externalId 與 quickCreateUrl:握有 AWS 帳號的人打開它,主控台會出現已經填好參數的 stack,建立即可。也可以自己建角色:信任 arn:aws:iam::580360261327:root,條件 sts:ExternalId 是那個 externalId;權限要 ecs:ListClusters、ecs:ListServices、ecs:DescribeServices、ecs:DescribeTaskDefinition、ssm:GetParameters、secretsmanager:GetSecretValue,SecureString 或自訂 KMS 金鑰加密的 secret 另外要 kms:Decrypt。範本:https://parallelsandbox-releases.s3.ap-northeast-1.amazonaws.com/releases/aws/readonly-role.yaml。
  2. PUT /v1/aws,內容 { "roleArn": "<stack Outputs 裡的 RoleArn>", "region": "<服務所在的 region>" }。ParallelSandbox 會實際登入一次,失敗時錯誤訊息會說原因。

之後 GET /v1/aws/ecs/services?region=… 列出角色看得到的服務,POST /v1/environments/{env}/services/import({ "region", "cluster", "service" })匯入一個(讀不到的會放在 sourceRef.skipped)。服務的 task definition 換版之後,POST /v1/environments/{env}/services/{name}/sync 再讀一次。

設定值在 ParallelSandbox 加密存放,只在箱子被認領的那一刻交給那個箱子;agent 只看得到變數名稱。

不在 ECS 上的服務的設定

服務跑在別的地方(Kubernetes、VM、伺服器上的 docker compose)時,把它的設定(一行一個 KEY=value)用 PUT /v1/environments/{環境}/services/{服務} 上傳,內容是 { "dotenv": "KEY=value\n…" };箱子拿到的 /work/.sbx/env/<服務>.env 與 .sh 跟匯入的一樣。從設定實際所在的地方整理出這個檔案,例如 Deployment 的 env 與它引用的 ConfigMap、Secret,systemd unit 的 EnvironmentFile,或 compose 的 env_file。上傳的設定不會同步:設定改了就再上傳一次。

箱子裡的 AWS 權限

服務在 AWS 上的權限通常不是寫在環境變數裡,而是 AWS 直接發給那台機器的身分(ECS 的 task role)。服務搬進箱子之後這個身分就沒了,所以要 S3、SQS 的功能會失敗。

環境可以指定一個角色給箱子用,走 OIDC,跟 GitHub Actions、Vercel 連 AWS 是同一套做法:

  1. GET /v1/environments/{env} 回這個環境的身分(awsSubject,即 tenant:<帳號>:env:<環境>)與 awsBoxRoleQuickCreateUrl,一鍵用 CloudFormation 建角色的連結。
  2. 在 AWS 建好角色、給它箱子需要的權限,再 PUT /v1/environments/{env},內容 { "awsRoleArn": "<角色 ARN>", "awsRegion": "<region>" }。
  3. 之後開的箱子裡,AWS_CONTAINER_CREDENTIALS_FULL_URI 已經設好(指到箱子裡跟 ECS 一樣的憑證位址),AWS_REGION 與 AWS_DEFAULT_REGION 也有;AWS 的 SDK 與 CLI 自己拿憑證、到期自己換,程式不用改。容器也一樣:用 docker run --env-file /work/.sbx/env/<服務>.env 起的容器就有。在箱子裡跑 aws sts get-caller-identity 會看到 assumed-role/<角色>/psbx-<箱子 id>。

平行沙盒替箱子簽一張十分鐘的身分證明,箱子自己拿它跟 AWS 換一小時到期的憑證。長期金鑰不存在任何地方,平行沙盒手上也沒有能進你帳號的權限;你在 AWS 刪掉角色或改信任政策就立刻失效。角色只信任你指定的那個環境。

要在箱子裡從你們自己的 ECR 拉 image(例如沒改的服務在 dev 上正在跑的那一版),角色要有 ecr:GetAuthorizationToken,以及對那些 repository 的 ecr:BatchCheckLayerAvailability、ecr:GetDownloadUrlForLayer、ecr:BatchGetImage(AWS 管理的 policy AmazonEC2ContainerRegistryReadOnly 四個都有)。然後在箱子裡:aws ecr get-login-password --region <region> | docker login --username AWS --password-stdin <account>.dkr.ecr.<region>.amazonaws.com,再 docker run --env-file /work/.sbx/env/<服務>.env … <image>。沒有這些權限的話,登入會失敗:AccessDeniedException … is not authorized to perform: ecr:GetAuthorizationToken。

在箱子裡用

sandbox_environments {}
sandbox_start { "name": "退款流程改版", "goal": "接 dev 的資料庫與服務改版 api 的退款流程;經 api 退的款在 dev 記下來就算完成", "environment": "dev", "services": [{ "name": "api", "port": 8080 }] }

sandbox_start 的回傳多一個 environment:reachable 是箱子裡連得到的內網位址,connectorOnline 是連接器在不在線上(有好幾條連線時,是其中有沒有任何一條在線上),connections[] 列出每條連線的 name、online、sessions、lastSeenAt、connectorVersion 與 reachable(走這條連線的位址,箱子自己跑的名字不算),services 是每個服務的設定檔路徑。有連線斷掉時,next 會點名它們與它們的位址;一條都不在線時,它說連接器不在線上。sandbox_status 回同一份 environment,每次呼叫都重新讀,所以箱子開起來之後才斷掉的連線也看得到。

環境有 externalBaseUrl 的話,箱子會沿用它,你宣告的每個服務一開始都是 external 模式:name:port 的 HTTP 轉到 externalBaseUrl。要在箱子裡跑的服務切成 box;boxd 不佔它們的 port,所以程式是不是已經在跑都沒關係:

sandbox_wire { "id": "<id>", "service": "api", "mode": "box" }

用各自的設定啟動改過的服務:

sandbox_exec { "id": "<id>", "cmd": "cd api && docker build -t api . && docker run -d --name api --env-file /work/.sbx/env/api.env -p 8080:8080 api", "note": "用 dev 的設定 build 並啟動 api" }
sandbox_exec { "id": "<id>", "cmd": "cd api && . /work/.sbx/env/api.sh && go run ./cmd/api", "background": true, "note": "用 dev 的設定跑 api" }
  • .env 是 docker run --env-file 的格式,值原樣;.sh 是 export KEY='VALUE',給直接跑的程式 source。多行的值只在 .sh 裡。只想在箱子裡改一個值,就在 --env-file 後面加 -e KEY=value,後面的會蓋過前面的。

  • 直接跑的程式,先 source .sh,再覆寫箱子裡要換的值,只給那一條命令,或用 export:

    . /work/.sbx/env/api.sh && DATABASE_URL="$(psbx-testdb url)" ./bin/backfill
    

    不要 source .env(set -a; . api.env):它的值沒加引號,含空白或引號的值會讓 shell 報錯停下,含 $ 的值則會不聲不響地變掉。

  • 用 version 宣告的已發布版本,會自動拿到它發布時登記的那個服務的設定檔 /work/.sbx/env/<服務>.env,不用你加任何參數(跑發布好的版本)。

  • docker compose 的 env_file 會展開值裡的 $;值裡有 $ 時改用 docker run --env-file,或先 source .sh 再用 environment: 帶進去。

  • 箱子裡的程式與容器照原本的主機名連就好,不用改設定:名字在箱子裡解析到各自的位址,boxd 把每條連線經 ParallelSandbox 與你的連接器接過去。

  • 別的服務用內網主機名呼叫你改的那個服務時(例如路由器轉給 api.svc.local:8080),在 sandbox_start 把它用同一個名字宣告在 services:[{ "name": "api.svc.local", "port": 8080 }]。port 是呼叫端連的 port;你的程式聽別的 port 時,把它加成 targetPort,呼叫端用 port 80 連的時候(例如內部 load balancer)一定要給:[{ "name": "internal-lb.svc.local", "port": 80, "targetPort": 8080 }]。這個名字在箱子裡就指到箱子自己,不經連接器,reachable 也不列它;其他沒改的照樣連你的 dev。跑在你 dev 上的呼叫端還是會連到 dev 上的那一份;測試需要的話,把呼叫端也放進箱子跑。之後才用 sandbox_wire 補宣告(port 是你自己的程式,version 是已發布的版本),在 sandbox_status → health.features 有 take-over 的箱子上(2026-09-25 12:40 UTC 起開的箱子都有)會就地接手這個名字:位址不變,經連接器開著的連線會被關掉,程式重連到箱子裡的那份。比較舊的箱子上,這種名字照樣經連接器連出去,所以在那種箱子上要在 sandbox_start 宣告(接手 link 或環境位址)。

  • 用自己的設定啟動的改過的服務,連的是你 dev 真正的資料庫:它的 migration 與寫入都會落在那裡。測試用的話,psbx-testdb up <名字> 會在箱子裡開一個乾淨的 Postgres 16,把連線資訊寫到 /work/.sbx/testdb/<名字>.env(DATABASE_URL、TEST_DATABASE_URL 等,都是 export 行;--as 變數名 再多寫一個);它聽 127.0.0.1,容器要加 --network host 才連得到。它的選項列在團隊設定。要讓服務用自己的資料庫,就在容器裡開一個,並在 --env-file 後面蓋掉設定(-e DATABASE_URL=…)。團隊設定有完整的命令。

  • 呼叫端不是用名字、而是從自己的註冊表拿到對方的 IP 直接連的時候(例如閘道從 Redis 名冊拿 worker 的 IP),在 services 用位址範圍宣告:[{ "name": "172.16.0.0/16", "port": 9090 }]。箱子裡(含容器)往這個範圍的那個 port 的連線,都會轉到箱子裡這個服務的 targetPort,往其他 port 的不動。只收私有位址:10.0.0.0/8、172.16.0.0/12、192.168.0.0/16、100.64.0.0/10,只能在 sandbox_start 宣告。同 port 又落在範圍內的內網位址不會再經連接器。

團隊設定有完整的例子:兩個網路、好幾個改過的服務、Sentry,以及從另一個箱子用改過的服務。

排錯

  • sandbox_environments:每個環境的 reachable、connectorOnline(任何一條連線在線上就是 true),以及 connections[]:每條連線的 online、sessions、lastSeenAt、connectorVersion 與它帶的位址。先看 connections[]:connectorOnline 還是 true 時,斷掉的 office 也會在這裡看得出來。箱子用的那個環境,sandbox_status → environment.connections[] 也看得到同樣的內容。要最後確認某個網路,仍然是在箱子裡用真正的用戶端(curl、pg_isready、psql 與 redis-cli 箱子裡都有;只做 TCP 連線不管怎樣都會成功,因為 boxd 會先接起來)連它的一個位址,再看 health.privateEndpoints 裡那個位址。
  • sandbox_status 的 health.privateEndpoints:每個位址在箱子裡由 boxd 開的入口(localIp、localPort)、目前幾條連線、開過幾條、失敗幾次、最近一次失敗的原因。
  • /work/.sbx/logs/private-endpoints.log:每次接不通一行。
  • 403:這個位址已經不在環境的任何連線上,用 PUT /v1/environments/{env}/connections/{id}/endpoints 加回去。箱子開起來之後才加的位址,那個箱子完全不知道(箱子裡沒有它的名字的位址),要開新的箱子。
  • 你的內部名稱(例如私有 API Gateway 的 <api-id>.execute-api.<region>.amazonaws.com)回 Could not resolve host(no such host、NXDOMAIN):這個名字沒有列在環境上,公網 DNS 也不認得它。把它連同 port 加到連得到它的那條連線上。PUT .../endpoints 會換掉整份清單,所以先從 GET /v1/environments/{env} 拿目前的清單,加上新位址再送回去;之後開新的箱子。
  • 503:列了這個位址的那條連線,它的連接器離線,訊息裡會寫出是哪一條連線(the connector "office" of this environment is offline; …);看連接器的 log(docker logs parallelsandbox-connector,或 ECS task 的 log)。箱子裡的用戶端看到的是連線先被接起、然後被重置。
  • 502:連接器連不上目標,檢查連接器所在的機器到那個位址之間的 DNS、路由與 security group。
  • 箱子剛解凍就出現錯誤:boxd 會在解凍後約 15 秒內,關掉凍結前就開著、連到你位址的連線,讓程式拿到錯誤,而不是握著一條永遠不會回應的連線。資料庫連線池通常會自己重連;長時間連著的用戶端要自己重試。

限制

  • 只轉 TCP。位址是確切的主機名或 IPv4 加上 port,不支援萬用字元或範圍。
  • 每條連線最多 100 個位址,每個箱子所有連線合計最多 200 個。
  • 箱子在被認領的那一刻拿到環境的位址、設定、externalBaseUrl 與 AWS 角色:之後加的位址或改的設定,要開新的箱子才會拿到。刪掉位址、連線或環境則立刻生效,已經在跑的箱子也連不到。