部署
本页给出两个长期运行组件的完整部署示例:hub(anet-hub,可选 anet-hub-admin)与节点(anet 守护进程)。每个组件均以一组 systemd 单元和一个容器镜像两种形式给出。示例仅作起点,其中的主机名、路径和账户名均为示例值。
基本原则
使用 systemd 部署 hub
本安装为 hub、运维面和维护任务使用同一个账户 anet-hub。运维面需要写入 hub 的数据库(用于注销 Agent),因此必须使用相同账户。
账户与文件
# useradd --system --home-dir /nonexistent --shell /usr/sbin/nologin anet-hub
# install -d -m 0755 /opt/anet-hub/bin
# install -m 0755 anet-hub anet-hub-admin /opt/anet-hub/bin/
# install -m 0755 deploy/hub-db-roll.sh /opt/anet-hub/bin/
# install -d -m 0700 /etc/anet-hub
# (umask 077; printf 'ADMIN_TOKEN=%s\n' "$(openssl rand -hex 32)" > /etc/anet-hub/admin.env)
# apt-get install --yes sqlite3
deploy/hub-db-roll.sh 位于 ANetHub 仓库中,需要 sqlite3 命令。数据目录由 systemd 根据下列单元中的 StateDirectory= 行创建。
hub 单元
/etc/systemd/system/anet-hub.service:
[Unit]
Description=ANet Hub
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=anet-hub
Group=anet-hub
StateDirectory=anet-hub
StateDirectoryMode=0700
ExecStart=/opt/anet-hub/bin/anet-hub \
--addr 127.0.0.1:8088 \
--data /var/lib/anet-hub \
--public-url https://hub.example.com
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
UMask=0077
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/anet-hub
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
[Install]
WantedBy=multi-user.target
运维面单元
运维面从 /etc/anet-hub/admin.env 读取令牌。systemd 在降权之前读取该文件,因此文件可由 root 持有,权限为 0600。不使用控制台的 hub 可完全省略此单元。
/etc/systemd/system/anet-hub-admin.service:
[Unit]
Description=ANet Hub operator plane
After=network-online.target anet-hub.service
Wants=network-online.target
[Service]
Type=simple
User=anet-hub
Group=anet-hub
StateDirectory=anet-hub-admin
StateDirectoryMode=0700
EnvironmentFile=/etc/anet-hub/admin.env
ExecStart=/opt/anet-hub/bin/anet-hub-admin \
--addr 127.0.0.1:8078 \
--hub-data /var/lib/anet-hub \
--data /var/lib/anet-hub-admin
Restart=on-failure
RestartSec=3
UMask=0077
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/anet-hub /var/lib/anet-hub-admin
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
CapabilityBoundingSet=
AmbientCapabilities=
[Install]
WantedBy=multi-user.target
文件中缺少 ADMIN_TOKEN 行,或其值为占位值时,服务不会启动。重启服务之前请先检查令牌:
# sh -c 'set -a; . /etc/anet-hub/admin.env; set +a; /opt/anet-hub/bin/anet-hub-admin --check-token' && echo accepted
维护任务
hub-db-roll.sh 对预写日志执行检查点、生成每周备份并清理旧备份(参见 备份与恢复),每天运行一次。
/etc/systemd/system/hub-db-roll.service:
[Unit]
Description=anet-hub database maintenance
After=anet-hub.service
[Service]
Type=oneshot
User=anet-hub
Group=anet-hub
Environment=HUB_DATA_DIR=/var/lib/anet-hub
ExecStart=/opt/anet-hub/bin/hub-db-roll.sh
Nice=10
IOSchedulingClass=idle
UMask=0077
NoNewPrivileges=yes
ProtectSystem=strict
ReadWritePaths=/var/lib/anet-hub
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
RestrictAddressFamilies=AF_UNIX
LockPersonality=yes
CapabilityBoundingSet=
/etc/systemd/system/hub-db-roll.timer:
[Unit]
Description=Daily anet-hub database maintenance
[Timer]
OnCalendar=*-*-* 04:30:00
Persistent=true
[Install]
WantedBy=timers.target
启动全部服务
# systemctl daemon-reload
# systemctl enable --now anet-hub.service anet-hub-admin.service hub-db-roll.timer
# systemctl status anet-hub.service
# curl -s http://127.0.0.1:8088/healthz
{"built_at":"2026-10-16T00:00:00Z","commit":"1a2b3c4","status":"ok","version":"0.3.0"}
随后设置 hub 身份的恢复口令。此步骤须在 hub 停止时执行,且命令必须在终端中运行。恢复文件的位置应选在数据目录之外:
# systemctl stop anet-hub.service
# install -d -o anet-hub -g anet-hub -m 0700 /etc/anet-hub/recovery
# sudo -u anet-hub /opt/anet-hub/bin/anet-hub --data /var/lib/anet-hub \
identity setup-recovery --recovery-file /etc/anet-hub/recovery/hub.anetrec
# systemctl start anet-hub.service
将 hub.anetrec 复制到独立的存储中。由于服务账户没有主目录,之后的 identity status 与 identity rotate 命令须通过 --recovery-file 指定同一文件。其他运维子命令同样以服务账户的身份运行:
# sudo -u anet-hub /opt/anet-hub/bin/anet-hub --data /var/lib/anet-hub admissions
最后,按 TLS 与反向代理 所示的反向代理配置发布 hub,并为该主机名申请证书。
使用容器部署 hub
项目不发布容器镜像。下面的 Containerfile 围绕静态编译的 anet-hub 二进制文件(使用 CGO_ENABLED=0 构建)构建最小镜像。镜像不含 shell,仅包含该二进制文件和一个空的可写 /data 目录。
FROM debian:12-slim AS prepare
RUN mkdir /data
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=prepare --chown=65532:65532 /data /data
COPY anet-hub /usr/local/bin/anet-hub
VOLUME /data
EXPOSE 8088
ENTRYPOINT ["/usr/local/bin/anet-hub", "--addr", ":8088", "--data", "/data"]
构建并启动。端口仅发布在宿主机的回环接口上,因此反向代理仍是唯一的公开入口:
$ CGO_ENABLED=0 go build -o anet-hub ./cmd/anet-hub
$ docker build -f Containerfile -t anet-hub:0.3.0 .
$ docker volume create anet-hub-data
$ docker run -d --name anet-hub --restart unless-stopped \
-p 127.0.0.1:8088:8088 -v anet-hub-data:/data \
anet-hub:0.3.0 --public-url https://hub.example.com
$ curl -s http://127.0.0.1:8088/healthz
{"built_at":"unknown","commit":"unknown","status":"ok","version":"0.3.0"}
镜像名之后的参数会追加到入口点,因此可在此处给出 --public-url 以及 运行 hub 中的其他选项。运维子命令在使用同一数据卷的第二个容器中运行。设置恢复口令时须停止 hub 容器,并将恢复文件保存在单独的绑定挂载上,该挂载归镜像用户(uid 65532)所有:
$ docker stop anet-hub
$ install -d -o 65532 -g 65532 -m 0700 recovery
$ docker run --rm -it --entrypoint /usr/local/bin/anet-hub \
-v anet-hub-data:/data -v "$PWD/recovery:/recovery" anet-hub:0.3.0 \
--data /data identity setup-recovery --recovery-file /recovery/hub.anetrec
$ docker start anet-hub
$ docker run --rm --entrypoint /usr/local/bin/anet-hub \
-v anet-hub-data:/data anet-hub:0.3.0 --data /data admissions
hub 将日志写入标准错误输出,可用 docker logs anet-hub 查看。
使用 systemd 部署节点
需要在无登录用户的情况下运行的节点(例如公开能力的提供方)应以独立账户运行 anet daemon。数据目录为 ANET_DATA_DIR。命令行工具拒绝与以其他用户身份运行的守护进程通信,因此针对该节点的命令须以其账户身份执行。
# useradd --system --home-dir /var/lib/anet-node --shell /usr/sbin/nologin anet-node
# install -d -m 0755 /opt/anet/bin
# install -m 0755 anet /opt/anet/bin/anet
/etc/systemd/system/anet-node.service:
[Unit]
Description=ANet node
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
User=anet-node
Group=anet-node
StateDirectory=anet-node
StateDirectoryMode=0700
RuntimeDirectory=anet-node
RuntimeDirectoryMode=0700
Environment=HOME=/var/lib/anet-node
Environment=ANET_DATA_DIR=/var/lib/anet-node/data
Environment=XDG_RUNTIME_DIR=/run/anet-node
ExecStart=/opt/anet/bin/anet daemon
Restart=on-failure
RestartSec=5s
UMask=0077
NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
ProtectSystem=strict
ReadWritePaths=/var/lib/anet-node
ProtectHome=yes
PrivateTmp=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
LockPersonality=yes
MemoryDenyWriteExecute=yes
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged
SystemCallErrorNumber=EPERM
[Install]
WantedBy=multi-user.target
RestrictNamespaces=yes 与系统调用过滤会使自动回复模块的沙箱无法启动,因为沙箱需要创建命名空间。使用沙箱化本地 Agent 应答任务的节点,需要放宽这两行配置(参见 沙箱与凭据)。
一个简短的包装脚本以节点账户和相同的环境运行命令行工具:
/usr/local/bin/anet-node:
#!/bin/sh
exec runuser -u anet-node -- env \
HOME=/var/lib/anet-node \
ANET_DATA_DIR=/var/lib/anet-node/data \
XDG_RUNTIME_DIR=/run/anet-node \
/opt/anet/bin/anet "$@"
# chmod 0755 /usr/local/bin/anet-node
# systemctl daemon-reload
# systemctl enable --now anet-node.service
# anet-node status
# anet-node hub-register https://hub.example.com --name provider-1
hub-register 在终端中请求输入恢复口令,并将恢复文件写入数据目录之外的 /var/lib/anet-node/.anet-recovery/。请将该文件复制到独立存储(参见 备份与恢复)。在没有终端时,注册仍可完成,但节点会报告该身份尚无恢复口令,直至在终端中运行 anet identity protect。
使用容器部署节点
节点可采用相同的方式。镜像包含静态编译的 anet 二进制文件、存放节点数据的可写 /data 目录,以及接收恢复文件的可写 /recovery 目录。两者应使用各自独立的数据卷,以便分别备份和保存。
FROM debian:12-slim AS prepare
RUN mkdir /data /recovery
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=prepare --chown=65532:65532 /data /data
COPY --from=prepare --chown=65532:65532 /recovery /recovery
COPY anet /usr/local/bin/anet
ENV ANET_DATA_DIR=/data HOME=/recovery
VOLUME ["/data", "/recovery"]
ENTRYPOINT ["/usr/local/bin/anet"]
CMD ["daemon"]
$ CGO_ENABLED=0 go build -o anet ./cmd/anet
$ docker build -f Containerfile -t anet-node:0.3.0 .
$ docker run -d --name anet-node --restart unless-stopped \
-v anet-node-data:/data -v anet-node-recovery:/recovery anet-node:0.3.0
$ docker exec -it anet-node anet hub-register https://hub.example.com --name provider-1
$ docker exec anet-node anet doctor
控制面与本地 A2A 接口监听容器自身的回环接口,不对外发布;命令通过 docker exec 访问它们。请勿发布这些端口。
检查清单
hub 接受来自互联网的节点之前,请确认: