> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blockx.chaintable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 部署概览

> BlockX 在生产中的运行形态：有哪些进程、依赖什么、配置怎么发布、镜像怎么构建，以及停机与观测入口在哪。

BlockX 以 EC2 + systemd + ASG 的形态运行：Worker 由 systemd 托管一个前台 `nerdctl run` 容器，容器内的 broker 再通过宿主机 containerd 拉起 executor sandbox。生产上有两套互相隔离的集群（`block` 与 `bundle`，各自一对 coordinator + worker fleet），另有一支与之平行、不经过 coordinator 的 sync-invoker fleet。本页面向开发者，帮你建立"代码跑在哪、怎么被拉起来"的心智模型；完整的运维细节在 blockx 仓库 `docs/deploy.md` 与 `docs/deploy/`。

```mermaid theme={null}
flowchart LR
    client["调用方"]
    nlb["NLB"]

    subgraph blockCluster["block 集群"]
        blockCoord["blockx-coordinator<br/>cmd/coordinator"]
        blockWorkers["worker ASG<br/>cmd/worker (systemd + nerdctl)"]
    end

    subgraph bundleCluster["bundle 集群"]
        bundleCoord["blockx-bundle-coordinator<br/>cmd/bundle_coordinator"]
        bundleWorkers["bundle worker ASG<br/>cmd/bundle_worker"]
    end

    subgraph siFleet["sync-invoker fleet"]
        si["blockx-syncinvoker<br/>cmd/syncinvoker"]
    end

    etcd[("etcd<br/>/blockx/workers/<br/>/blockx/bundle-workers/")]
    deps["BlockDB / Meta / NodeRPC<br/>Glue + S3 (Iceberg)<br/>Usage Kafka"]
    redis[("Redis<br/>function code")]

    client -->|ReserveWorkerSlot| blockCoord
    client -. "路由待上游确认" .-> bundleCoord
    client -->|"SubmitTask / WatchTasks"| blockWorkers
    client --> nlb -->|Invoke| si

    blockCoord <-->|watch| etcd
    bundleCoord <-->|watch| etcd
    blockWorkers -->|"register / heartbeat"| etcd
    bundleWorkers -->|"register / heartbeat"| etcd
    blockCoord -->|RequestTaskSlot| blockWorkers
    bundleCoord -->|RequestTaskSlot| bundleWorkers

    blockWorkers --> deps
    bundleWorkers --> deps
    blockWorkers --> redis
    si --> deps
    si --> redis
```

两套集群的隔离只来自 etcd registry prefix：block coordinator 只 watch `/blockx/workers/`，bundle coordinator 只 watch bundle 前缀，请求字段与 gRPC 协议不带任何"集群"标识。bundle 集群上线不代表流量已经切过去，上游路由方案另行设计。sync-invoker 不注册到 etcd，也不经过 coordinator，一次 `Invoke` 就是一次同步函数调用（详见 [Sync Invoker](/components/sync-invoker)）。

## 进程与二进制

`Dockerfile` 会构建下表五个二进制，都放在同一个镜像里，由启动命令选择入口。四个 coordinator/worker 入口都是"薄 profile"：`cmd/*/main.go` 只声明 `app.Profile`，公共装配、配置加载和关闭顺序在 `internal/coordinator/app` 与 `internal/worker/app`。

| 二进制                         | 入口                       | 角色                                                                                                                                 | 主要依赖                                                                          | 默认端口（代码默认值）                                |
| --------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------ |
| `blockx-coordinator`        | `cmd/coordinator`        | block 集群调度入口；watch `/blockx/workers/`，为 Task 预留 Slot 并转发                                                                           | etcd；可选 chaintable-log Kafka、OTLP                                             | gRPC `:8080`；metrics 默认关闭                  |
| `blockx-bundle-coordinator` | `cmd/bundle_coordinator` | bundle 集群调度入口；同时 watch `/blockx/bundle-workers/` 与 `/blockx/prod/lanes/default/bundle-workers/`；对外暴露独立的 `BundleCoordinatorService` | 同上                                                                            | gRPC `:8080`；metrics 默认关闭                  |
| `blockx-worker`             | `cmd/worker`             | block worker，full / compatibility profile：注册全部 Builder 与 Writer Plugin（含 bundle 能力和 devstub）                                       | etcd、BlockDB、NodeRPC、Meta、Glue/S3、Usage Kafka、containerd（sandbox 模式）、可选 Redis | gRPC `:8081`；metrics 默认关闭                  |
| `blockx-bundle-worker`      | `cmd/bundle_worker`      | bundle worker profile：只注册 `BundleScan`、`CallList` Builder 与 `BundleWrite`、`TableUpserts` Plugin，`StreamBuild.Enabled` 默认为 `true`   | 同上                                                                            | 同上                                         |
| `blockx-syncinvoker`        | `cmd/syncinvoker`        | 同步调用服务：复用 worker 的 executor 池与 UDS adapter，不跑 task dispatcher                                                                      | BlockDB、NodeRPC、Redis（function code）；不依赖 etcd                                 | gRPC `localhost:18080`；metrics/health 默认关闭 |

<Note>
  表中端口是 `internal/coordinator/app/config.go`、`internal/worker/app/config.go`、`cmd/syncinvoker/config.go` 里的默认值。EC2 systemd 模板会覆盖它们：worker 与 sync-invoker 都用 gRPC `0.0.0.0:9900`、metrics `0.0.0.0:9901`（见 `deploy/env/*.env.example`）。metrics HTTP 端点只在设置了 `BLOCKX_PROMETHEUS_LISTEN_ADDR` 时启动，路径由 `BLOCKX_PROMETHEUS_PATH` 决定，默认 `/metrics`。
</Note>

worker 与 bundle worker 的区别只在 `app.Profile`：`Deployment`、`Service`、`WorkerRegistryPrefix`、`Builders`、`Plugins` 与 `TuneDefaults`。EC2 上通过 `worker.env` 里的 `WORKER_BINARY`（`blockx-worker` 或 `blockx-bundle-worker`）选入口，`deploy/scripts/blockx-worker-start` 只接受这两个值。

## 依赖服务

| 服务                          | 用途                                                                                                                                           | 使用方                  |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- |
| etcd                        | Worker 注册与心跳（带 lease TTL）；coordinator watch 前缀得到容量视图                                                                                         | coordinator、worker   |
| BlockDB                     | 链上数据读写；每类服务独立 gRPC 地址（`BLOCKDB_*_ADDR`），注册 BundleWrite / TableUpserts 的 worker 必须配 `BLOCKDB_BATCH_WRITE_ADDR`                                | worker、sync-invoker  |
| Meta Service                | logical-types 元数据（`META_ADDR`，不设则用本地 stub）                                                                                                   | worker               |
| Node RPC                    | 链节点 JSON-RPC 调用（`NODE_RPC_ENDPOINT`，不设则用 dev stub）                                                                                           | worker、sync-invoker  |
| AWS Glue + S3               | Iceberg 表 metadata / manifest 只读解析与 bundle parquet 读取；worker 走 EC2 instance role，只需 `glue:GetTable`、`s3:GetObject`（SSE-KMS 时加 `kms:Decrypt`） | worker、bundle worker |
| Usage Kafka                 | 计算用量账本，topic 默认 `chaintable-usage`；fail-closed，未配置 `BLOCKX_USAGE_BROKERS` 且未显式 `BLOCKX_USAGE_DISABLED=true` 时 worker 拒绝启动                    | worker、bundle worker |
| Redis                       | function code 元数据来源（`FUNCTION_CODE_REDIS_URL` / `FUNCTION_CODE_REDIS_KEY`），adapter 在 `internal/functioncode/adapters/redis`；不设时回退 devstub    | worker、sync-invoker  |
| containerd                  | `EXECUTOR_SPAWN_MODE=sandbox` 时 broker 通过宿主机 containerd 创建 `blockx-executor-*` sandbox                                                       | worker、sync-invoker  |
| chaintable-log Kafka / OTLP | 可选：`CHAINTABLE_LOG_BROKERS` 把 slog 记录转发到 Kafka；`OTEL_EXPORTER_OTLP_ENDPOINT` 打开 tracing                                                      | 全部                   |

BundleWrite 与 TableUpserts 的数据上传走 BlockDB 返回的 presigned URL 直接 `PUT`，不使用 worker 的 AWS credential chain，也不需要目标 bucket 写权限。IAM 与地址清单见 blockx 仓库 `docs/deploy.md` §2、§3.3。

## 配置发布方式

应用配置与云资源分属两个仓库：

* **blockx 仓**只维护运行时产物：systemd unit、host 脚本、env 示例、health gate 与 runbook，全部在 `deploy/`。
* **`Chaintable/blockx-ec2-manifest`** 维护 `worker.env`（以及后续的 `syncinvoker.env`）；合入 `main` 后按 Git SHA 上传为不可变 S3 对象，SSM 只保存 `s3-v1` 三行 pointer（`BLOCKX_WORKER_ENV_FORMAT` / `BLOCKX_WORKER_ENV_S3_URI` / `BLOCKX_WORKER_ENV_SHA256`），再触发 State Manager association。
* **`DeBankDeFi/SRE`（Terraform）** 维护 ASG、launch template、User Data、IAM、SG、NLB、Prometheus scrape 与 deploy bundle URI。blockx 侧不直接改 AWS 资源。

实例上的收敛路径：User Data 下载固定版本的 deploy bundle 到 `/opt/blockx-deploy`，调用 `blockx-worker-install`；之后 State Manager 周期性跑 `blockx-worker-reconcile`，env 无变化则 `skip`，有变化则委托 install 重装、重启并跑 health gate。校验失败 fail closed，不覆盖本机 `/etc/blockx/worker.env`。

`deploy/` 各目录：

| 路径                | 内容                                                                                                                                                                                                                                         |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `deploy/systemd/` | `blockx-worker.service`、`blockx-syncinvoker.service`：`ExecStartPre` → prestart，`ExecStart` → start（前台 `nerdctl run`），`ExecStop` → stop，`ExecStopPost` → cleanup；worker `TimeoutStopSec=140`，sync-invoker `30`                              |
| `deploy/scripts/` | `blockx-worker-{prestart,start,stop,cleanup,health,install,reconcile,fetch-env,drain,profile}` 与对应的 `blockx-syncinvoker-*`（无 drain/profile）；`build-blockx-ec2-deploy-bundle` 从固定 commit 的 `deploy/` 打出确定性 `blockx-ec2-deploy-<sha>.tar.gz` |
| `deploy/env/`     | `worker.env.example`、`syncinvoker.env.example`：既是 host 脚本读取的 `KEY=value` 文件，也以 `--env-file` 传给容器；只允许简单 `KEY=value`，不能有 `export`、引号或 shell 表达式                                                                                              |
| `deploy/tests/`   | 上述脚本的 bash 单测（fetch-env、install、reconcile、health、start、profile、deploy bundle），CI 的 `Deploy Script Tests` job 逐个执行                                                                                                                          |

<Warning>
  改了 `deploy/scripts/*` 或 `deploy/systemd/*` 之后要重新打 deploy bundle，并由 SRE 更新 bundle URI；`reconcile` 只收敛 env，不会把新脚本同步到已存在的实例。
</Warning>

## 镜像与 CI

`Dockerfile` 是两阶段构建：

1. `go-builder`（`golang:1.26-bookworm`）：编译 `blockx-coordinator`、`blockx-bundle-coordinator`、`blockx-syncinvoker`（`CGO_ENABLED=0`）与 `blockx-worker`、`blockx-bundle-worker`（`CGO_ENABLED=1`，DuckDB 需要 cgo），目标 `linux/amd64`。
2. `runtime`（`python:3.12-slim`）：`pip install /app/python` 装入 `blockx_executor` / `blockx_sdk` / `blockx_audit` 与私有依赖（`blockdb-py`、`blockx-py` 等），顺带装 `py-spy`；再把五个 Go 二进制复制到 `/usr/local/bin`。没有单独的 venv，Python 包直接装进镜像的系统 site-packages，`PYTHONPATH=/app/python`。

私有 Go 模块与 Python 包的 GitHub token 通过 BuildKit secret（`--secret id=github_token`）注入，不进镜像层。

`.github/workflows/`：

| workflow    | 名称            | 触发                                          | 做什么                                                                                                                                           |
| ----------- | ------------- | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `build.yml` | `Build image` | push 任意 tag、`workflow_dispatch`             | 在 self-hosted runner 上 `docker buildx build --push`，推 `294354037686.dkr.ecr.ap-northeast-1.amazonaws.com/chaintable/blockx:<tag>` 与 `:latest` |
| `test.yml`  | `CI`          | PR 到 `dev`、push 到 `dev`、`workflow_dispatch` | Deploy Script Tests、Go Tests、Python Tests、分片的 Worker Process E2E、Bundle Worker / Contract / System E2E、Perf E2E                               |

发一个镜像就是打 tag 并推送（`git tag v1.2.3 && git push origin v1.2.3`）。EC2 侧只引用固定 tag 或 digest，不拉 `latest`。

## 优雅停机

进程都处理 `SIGTERM` / `SIGINT`。coordinator 收到信号后 `cancel()` 根 context 并 `GracefulStop()` gRPC server。worker 的关闭在 `internal/worker/app/app.go` 里分两阶段：先 `SetDraining` 并从 etcd `Deregister`，等待在飞 task 完成（`DRAIN_TIMEOUT_MS`，代码默认 30s，EC2 模板 90s）；再 `cancel()`、停 executor 池、关 IO scope、关闭 WatchTasks 订阅，最后 `GracefulStop`（5s 内不结束则 `Stop`）。ASG scale-in 不接 lifecycle hook，靠 worker 内建 drain + systemd `ExecStop` + etcd lease TTL 三层兜底。细节见 [Worker](/components/worker)。

## 可观测性入口

| 入口         | 位置                                                                                                                         |
| ---------- | -------------------------------------------------------------------------------------------------------------------------- |
| 结构化日志      | JSON 到 stderr（`internal/obs/logger.go`）；systemd 场景用 `journalctl -u blockx-worker`；配置 `CHAINTABLE_LOG_BROKERS` 后同时转发到 Kafka |
| Prometheus | `BLOCKX_PROMETHEUS_LISTEN_ADDR` 打开，路径 `BLOCKX_PROMETHEUS_PATH`（默认 `/metrics`）；EC2 为 `:9901/metrics`                        |
| pprof      | worker 在同一 HTTP server 上挂 `/debug/pprof/`（`WORKER_DEBUG_PPROF=false` 关闭）；sync-invoker 在同一端口挂 `/readyz`、`/healthz`          |
| 文件 profile | `WORKER_CPU_PROFILE` / `WORKER_TRACE`、`COORDINATOR_CPU_PROFILE` / `COORDINATOR_TRACE` 写到指定路径                               |
| 宿主机采样      | `deploy/scripts/blockx-worker-profile` 用临时 profiler 容器采 Go / Python 火焰图，不要求宿主机装 `py-spy`                                   |
| Tracing    | `OTEL_EXPORTER_OTLP_ENDPOINT`，不设则 noop                                                                                     |

指标定义与看板解读见 [可观测性](/components/observability)。

## 相关文档

blockx 仓库：

* `docs/deploy.md` — SRE 视角的部署总览、环境变量完整表、镜像地址。
* `docs/deploy/ec2-automation-plan.md` — blockx / blockx-ec2-manifest / SRE 三方职责边界与发布路径。
* `docs/deploy/worker-systemd-nerdctl.md` — worker EC2 runbook（安装、env、验收、回滚）。
* `docs/deploy/syncinvoker-systemd-nerdctl.md` — sync-invoker EC2 runbook。
* `docs/deploy/sync-invoker-sandbox-host.md` — sync-invoker host containerd sandbox 形态的验证记录。
* `docs/deploy/sync-invoker-loadtest-2026-06-29.md` — sync-invoker 容量压测记录。
* `docs/sync-invoker-grafana.md` — sync-invoker Grafana 看板指标口径。
* `docs/specs/2026-07-21-block-bundle-clusters.md` — block / bundle 双集群拆分设计。
* `docs/specs/2026-07-28-registry-prefix-migration.md` — registry prefix v2 与 `COORDINATOR_REGISTRY_PREFIXES`。
* `docs/specs/2026-07-30-ec2-worker-profiling.md` — EC2 worker 无宿主机依赖的性能采样方案。

站内：

<Columns cols={2}>
  <Card title="本地开发环境" href="/development/getting-started">
    本地构建与跑起来的最小步骤。
  </Card>

  <Card title="可观测性" href="/components/observability">
    日志、指标、tracing 与 usage 的代码位置。
  </Card>

  <Card title="Worker" href="/components/worker">
    Worker 的装配、drain 与关闭顺序。
  </Card>

  <Card title="Bundle 集群" href="/components/bundle">
    bundle coordinator / worker 与 bundle 专属能力。
  </Card>
</Columns>
