43 Commits

Author SHA1 Message Date
xiuting.xu
a4fd0f3128 调整version 2026-07-28 14:21:08 +08:00
xiuting.xu
859a56c434 增加cache restart和cache shutdown 2026-07-24 11:10:32 +08:00
xiuting.xu
5fb79a7c8d modify scripts 2026-07-17 09:51:53 +08:00
xiuting.xu
490b79f22c add scripts 2026-07-14 14:44:40 +08:00
xiuting.xu
e2bdcd3820 Server和client部署整理 2026-07-09 17:14:21 +08:00
xiuting.xu
b5cbc7fbee arm构建 2026-07-06 11:01:30 +08:00
xiuting.xu
cc3518897f 删除无用字段 2026-06-25 14:37:19 +08:00
xiuting.xu
b105b3b7d0 增加rtr一些必要的api接口 2026-06-23 17:04:00 +08:00
xiuting.xu
303bdffd97 remove unused data file 2026-06-18 09:53:03 +08:00
xiuting.xu
3c61fbf152 modify rtr server metrics data 2026-06-18 09:44:55 +08:00
xiuting.xu
b70bad4721 增加必要的数据记录 2026-06-16 14:05:26 +08:00
xiuting.xu
fa19fc8722 add Cargo.lock 2026-06-15 15:14:18 +08:00
xiuting.xu
13f29843db 删除无用代码 2026-05-27 13:44:31 +08:00
xiuting.xu
cdf9372929 增加环境变量 2026-05-18 11:42:33 +08:00
xiuting.xu
a11f2bc864 增加观测日志 2026-05-13 16:49:10 +08:00
xiuting.xu
ad76745619 内存优化 2026-05-13 14:57:43 +08:00
xiuting.xu
9dc69e5f31 内存优化,优化随着更新ccr文件增多,内存增长的情况 2026-05-13 14:08:07 +08:00
xiuting.xu
ff711a2fe1 内存优化 2026-05-12 16:45:56 +08:00
xiuting.xu
4c6b441753 优化部署 2026-05-12 15:23:09 +08:00
xiuting.xu
a4d674190a ccr的读取换成读取文件目录 2026-05-11 14:06:38 +08:00
xiuting.xu
b79e25ffea 修改serial query的没有更新情况下的pdu 2026-05-09 14:49:21 +08:00
xiuting.xu
84866c1d86 修改部署文件 2026-05-09 10:12:51 +08:00
xiuting.xu
ddeff71618 修改bird的docker-compose 2026-05-08 10:17:59 +08:00
xiuting.xu
ca0cf4800a 修改bird配置 2026-05-08 09:54:00 +08:00
xiuting.xu
897d168ceb 修改部署文件; 2026-05-07 16:47:08 +08:00
xiuting.xu
19225edaa7 内存优化 2026-05-06 15:27:16 +08:00
xiuting.xu
6e4b59a208 增加bird部署;
优化内存占用
2026-05-06 09:49:41 +08:00
xiuting.xu
88c11e7a97 修改deploy 2026-04-24 11:33:54 +08:00
xiuting.xu
8a65f639cb 修改dockerfile及docker-compose,失败后不要重新启动,方便测试 2026-04-24 10:50:11 +08:00
xiuting.xu
cef9495534 add bird deploy
add data
add certs
2026-04-23 15:13:50 +08:00
xiuting.xu
b60d579a38 增加ssh
增加deploy下细分的tcp、tls、ssh
2026-04-22 16:02:42 +08:00
xiuting.xu
99250f8aa9 增加timing环境变量 2026-04-15 16:26:09 +08:00
xiuting.xu
17c1a02c90 rtr client 开发 2026-04-15 15:43:59 +08:00
xiuting.xu
c1d3112a45 add dockerfile 2026-04-10 14:29:40 +08:00
xiuting.xu
23cdad095d 优化rtr 2026-04-08 14:03:23 +08:00
xiuting.xu
b483c2af8b remove unused code 2026-04-01 16:31:52 +08:00
xiuting.xu
cd0330e8ae 新增slurm功能 2026-04-01 16:24:01 +08:00
xiuting.xu
8d6173f605 rtr的输入换成ccr文件 2026-03-30 10:03:43 +08:00
xiuting.xu
7432fbcac4 完善routerkey和aspa功能 2026-03-26 10:04:09 +08:00
xiuting.xu
03c0ab0ec7 补充rtr-client,完善tls连接等 2026-03-25 10:08:40 +08:00
xiuting.xu
9cbea4e2d0 RTR server以及client的开发、test的补充 2026-03-17 16:36:42 +08:00
xiuting.xu
251dea8e5e RTR server 2026-03-09 11:25:42 +08:00
xuxt
f5b7da921a RC证书解析部分代码 (#2)
Co-authored-by: xiuting.xu <xiutingxt.xu@gmail.com>
Reviewed-on: #2
Reviewed-by: yuyr <yuyr@zgclab.edu.cn>
Co-authored-by: xuxt <xuxt@zgclab.edu.cn>
Co-committed-by: xuxt <xuxt@zgclab.edu.cn>
2026-02-03 10:01:32 +08:00
691 changed files with 33689 additions and 188219 deletions

View File

@@ -1,17 +1,9 @@
target/
.git/
.git
.gitignore
perf.*
**/* copy.excalidraw
ui/rpki-explorer/node_modules/
ui/rpki-explorer/dist/
ui/rpki-explorer/playwright-report/
ui/rpki-explorer/test-results/
ui/rpki-explorer/.vite/
deploy/arm64-compose/.env
target/
*.profraw
*.profdata
*.tar
*.tar.gz
*.zip
.idea
target
rtr-db
tests
specs
scripts
README.md

12
.gitignore vendored
View File

@@ -1,9 +1,5 @@
target/
Cargo.lock
perf.*
specs/* copy.excalidraw
ui/rpki-explorer/node_modules/
ui/rpki-explorer/dist/
ui/rpki-explorer/playwright-report/
ui/rpki-explorer/test-results/
ui/rpki-explorer/.vite/
rtr-db/
.idea/
logs/
report/

3528
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,37 +1,40 @@
[package]
name = "rpki"
version = "0.1.0"
version = "1.3.0"
edition = "2024"
[features]
default = ["full"]
# Full build used by the main RP implementation (includes RocksDB-backed storage).
full = ["dep:rocksdb"]
profile = ["dep:pprof", "dep:flate2"]
[[bin]]
name = "rpki_rtr"
path = "src/main_rtr.rs"
[dependencies]
asn1-rs = "0.7.1"
der-parser = { version = "10.0.0", features = ["serialize"] }
der-parser = "10.0.0"
hex = "0.4.3"
base64 = "0.22.1"
sha2 = "0.10.8"
thiserror = "2.0.18"
time = "0.3.45"
ring = "0.17.14"
x509-parser = { version = "0.18.0", features = ["verify"] }
url = "2.5.8"
serde = { version = "1.0.218", features = ["derive"] }
serde_json = { version = "1.0.140", features = ["raw_value"] }
toml = "0.8.20"
rocksdb = { version = "0.22.0", optional = true, default-features = false, features = ["lz4"] }
serde_cbor = "0.11.2"
memmap2 = "0.9.10"
roxmltree = "0.20.0"
quick-xml = "0.37.2"
uuid = { version = "1.7.0", features = ["v4"] }
reqwest = { version = "0.12.12", default-features = false, features = ["blocking", "rustls-tls", "gzip", "brotli", "deflate"] }
pprof = { version = "0.14.1", optional = true, features = ["flamegraph", "prost-codec"] }
flate2 = { version = "1.0.35", optional = true }
tempfile = "3.16.0"
[dev-dependencies]
asn1-rs = "0.7.1"
asn1-rs-derive = "0.6.0"
asn1 = "0.23.0"
arc-swap = "1.7.0"
chrono = { version = "0.4.44", features = ["serde"] }
chrono-tz = "0.10"
bytes = "1.11.1"
tokio = { version = "1.49.0", features = ["full"] }
rand = "0.10.0"
rocksdb = { version = "0.21.0", default-features = false }
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
base64 = "0.22"
anyhow = "1"
tracing = "0.1.44"
sha2 = "0.10"
tempfile = "3"
tokio-rustls = "0.26"
rustls = "0.23"
rustls-pemfile = "2"
rustls-pki-types = "1.14.0"
socket2 = "0.5"
russh = { version = "0.60.0", default-features = false, features = ["ring", "rsa"] }
tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"] }

287
README.client.md Normal file
View File

@@ -0,0 +1,287 @@
# RTR Client 使用说明
本文说明仓库内三类 RTR Client 的用途、启动方式和验证命令:
- `deploy/client`: 本仓库自带的 `rtr_debug_client`,适合做 RTR 协议调试、连通性检查和单次查询。
- `deploy/frr`: FRR 作为黑盒 RTR Client,适合验证真实路由软件是否能消费 server 下发的 VRP。
- `deploy/bird`: BIRD 作为黑盒 RTR Client,适合验证 TCP 或 SSH transport 下的 RPKI/ROA/ASPA 数据导入。
以下命令默认从仓库根目录执行。
## 目录
- [前置条件](#前置条件)
- [rtr_debug_client](#rtr_debug_client)
- [Docker attach 交互命令](#docker-attach-交互命令)
- [TCP](#tcp)
- [TLS/mTLS](#tlsmtls)
- [SSH](#ssh)
- [多客户端压测](#多客户端压测)
- [FRR Client](#frr-client)
- [BIRD Client](#bird-client)
- [TCP](#tcp-1)
- [SSH](#ssh-1)
- [如何判断 Client 正常](#如何判断-client-正常)
## 前置条件
先启动 RTR Server。TCP 模式是最常用的本地验证方式:
```bash
./deploy/server/build-run.sh up --mode tcp
```
如果要测试 TLS 或 SSH,需要先启动对应 server 模式,并准备匹配的证书或 SSH key:
```bash
./deploy/server/build-run.sh up --mode tls
./deploy/server/build-run.sh up --mode ssh
```
TLS/SSH 所需的本地开发证书和密钥可以通过脚本生成:
```bash
bash scripts/generate-certs.sh
```
已有 `certs/` 时使用 `--force` 覆盖。脚本生成的 `certs/tls` 和 `certs/ssh` 文件名与本仓库 compose 默认配置一致。
Client 连接地址不要求固定在某个 Docker 网络里。`deploy/client` 的 TCP/TLS/SSH 模式分别通过 `RPKI_RTR_TCP_SERVER_ADDR`、`RPKI_RTR_TLS_SERVER_ADDR`、`RPKI_RTR_SSH_SERVER_ADDR` 指定 server 地址。`deploy/bird` 通过 `RPKI_BIRD_RPKI_HOST`/`RPKI_BIRD_RPKI_PORT` 指定 TCP server 地址,通过 `RPKI_BIRD_SSH_RPKI_HOST`/`RPKI_RTR_SSH_PORT` 指定 SSH server 地址。示例 compose 里的默认值只是为了和本仓库 server compose 直接联调。
`deploy/frr` 示例默认连接 `127.0.0.1:323`,可以通过 `RPKI_FRR_RPKI_HOST`/`RPKI_FRR_RPKI_PORT` 覆盖。
## rtr_debug_client
`rtr_debug_client` 是本仓库提供的调试客户端。它会发起 RTR Reset Query 或 Serial Query,并打印 server 返回的 PDU 摘要。Docker 配置默认执行:
```text
<server_addr> <protocol_version> reset --summary-only
```
Docker 镜像的 `entrypoint.sh` 会把 stdout/stderr 重定向到 `logs/client/*.stdout.log` 和 `logs/client/*.stderr.log`。因此 `docker attach` 后可以输入交互命令,但命令响应和收到的 PDU 仍然主要从日志文件查看:
```bash
tail -f logs/client/*.stdout.log
tail -f logs/client/*.stderr.log
```
### Docker attach 交互命令
如果 debug client 已经通过 compose 启动,可以 attach 到容器后输入命令:
```bash
docker attach --sig-proxy=false <rtr-debug-client-container>
```
脱离 attach 会话时使用 `Ctrl-P` 后接 `Ctrl-Q`,避免 `Ctrl-C` 直接停止客户端进程。
可用交互命令如下:
| 命令 | 作用 |
| --- | --- |
| `help` | 显示可用命令列表。 |
| `state` | 打印当前客户端状态,包括 `session_id`、`serial`、`refresh/retry/expire`、输出模式、自动轮询状态等。 |
| `version` | 查看当前 RTR 协议版本。 |
| `version <n>` | 修改后续查询使用的 RTR 协议版本,例如 `version 2`。 |
| `reset` | 发送 Reset Query,通常用于让 server 重新下发完整数据集。 |
| `serial` | 使用当前已记录的 `session_id` 和 `serial` 发送 Serial Query。需要客户端已经从 EndOfData 中拿到状态。 |
| `serial <session_id> <serial>` | 显式指定参数发送 Serial Query,例如 `serial 31320 156`。 |
| `timeout` | 查看当前读取 PDU 的超时时间。 |
| `timeout <secs>` | 修改读取 PDU 的超时时间。超时只打印提示,连接保持打开。 |
| `poll` | 查看当前自动轮询间隔、来源、refresh hint、错误状态和暂停状态。 |
| `poll <secs>` | 修改自动轮询间隔。 |
| `poll pause` | 暂停自动轮询。 |
| `poll resume` | 恢复自动轮询。 |
| `output` | 查看当前输出模式,以及 summary 模式下跳过的 payload PDU 数量。 |
| `output verbose` | 打印所有收到的 PDU,包括 IPv4 Prefix、IPv6 Prefix、Router Key、ASPA 等 payload PDU。 |
| `output summary` | 只打印摘要,跳过 payload PDU 详情。compose 默认使用该模式。 |
| `quit` / `exit` | 退出客户端进程。 |
常用调试流程:
```text
output verbose
reset
state
serial
```
### TCP
```bash
./deploy/client/build-run.sh up --mode tcp
./deploy/client/build-run.sh logs --mode tcp
./deploy/client/build-run.sh down --mode tcp
```
默认连接 `rpki-rtr:323`,协议版本默认是 `2`。可以通过 `deploy/client/.env` 或环境变量覆盖:
```bash
RPKI_RTR_TCP_SERVER_ADDR=10.0.0.12:323 \
RPKI_RTR_PROTOCOL_VERSION=2 \
./deploy/client/build-run.sh up --mode tcp
```
### TLS/mTLS
```bash
./deploy/client/build-run.sh up --mode tls
./deploy/client/build-run.sh logs --mode tls
./deploy/client/build-run.sh down --mode tls
```
TLS 模式默认读取 `certs/tls` 下的示例证书,并使用 `localhost` 作为 server name。常用变量:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `RPKI_RTR_TLS_SERVER_ADDR` | `rpki-rtr:324` | TLS RTR server 地址 |
| `RPKI_RTR_TLS_SERVER_NAME` | `localhost` | 必须匹配 server 证书 SAN dNSName |
| `RPKI_RTR_TLS_CA_CERT_PATH` | `/app/certs/client-ca.crt` | 容器内 CA 证书路径 |
| `RPKI_RTR_TLS_CLIENT_CERT_PATH` | `/app/certs/client-good.crt` | 容器内 client 证书路径 |
| `RPKI_RTR_TLS_CLIENT_KEY_PATH` | `/app/certs/client-good.key` | 容器内 client 私钥路径 |
| `RPKI_RTR_TLS_CERTS_HOST_DIR` | `../../certs/tls` | 宿主机 TLS 证书目录 |
### SSH
```bash
./deploy/client/build-run.sh up --mode ssh
./deploy/client/build-run.sh logs --mode ssh
./deploy/client/build-run.sh down --mode ssh
```
SSH 模式默认连接 `rpki-rtr-ssh:22`,使用 key 认证,并校验 server public key。常用变量:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `RPKI_RTR_SSH_SERVER_ADDR` | `rpki-rtr:22` | SSH RTR server 地址 |
| `RPKI_RTR_SSH_USERNAME` | `rpki-rtr` | SSH 用户名 |
| `RPKI_RTR_CLIENT_KEYS_VOLUME` | `../../certs/ssh:/app/certs:ro` | 宿主机 SSH key 目录挂载 |
| `RPKI_RTR_CLIENT_KEY_PATH` | `/app/certs/rtr-client.key` | 容器内 client 私钥 |
| `RPKI_RTR_SSH_SERVER_PUBKEY_PATH` | `/app/certs/ssh_host_rsa_key.pub` | 容器内 server public key |
### 多客户端压测
`--clients multi` 会启动 5 个 TCP debug client,用于观察并发连接、client report 和 server 连接统计。
```bash
./deploy/client/build-run.sh up --mode tcp --clients multi
./deploy/client/build-run.sh logs --mode tcp --clients multi
./deploy/client/build-run.sh down --mode tcp --clients multi
```
## FRR Client
FRR 示例用于验证标准路由软件能否通过 RTR over TCP 从 server 获取前缀验证数据。
启动:
```bash
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
```
默认连接 `127.0.0.1:323`。可以通过环境变量覆盖:
```bash
RPKI_FRR_RPKI_HOST=10.0.0.12 \
RPKI_FRR_RPKI_PORT=323 \
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
```
验证连接和数据:
```bash
docker exec -it frr-rpki-client vtysh -c "show rpki configuration"
docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection"
docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table"
```
停止:
```bash
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml down
```
FRR 容器启动时会读取 `deploy/frr/frr.conf.template`,并生成 `/etc/frr/frr.conf`。模板中的 RTR cache 配置为:
```text
rpki cache tcp ${RPKI_FRR_RPKI_HOST} ${RPKI_FRR_RPKI_PORT} preference ${RPKI_FRR_RPKI_PREFERENCE}
```
如果需要调整 polling、timeout、BGP router-id 等其他 FRR 配置,可以修改 `deploy/frr/frr.conf.template`,或在 compose 中挂载自己的模板和 entrypoint。
## BIRD Client
BIRD 示例用于验证 BIRD 3.x 对 RTR v2、ROA 和 ASPA 的导入情况。容器启动后会周期性输出 RPKI 协议状态和表项摘要。
### TCP
启动:
```bash
docker compose --env-file deploy/bird/.env -f deploy/bird/docker-compose.yml up -d --build
```
查看日志:
```bash
docker logs -f bird-rpki-client
```
停止:
```bash
docker compose --env-file deploy/bird/.env -f deploy/bird/docker-compose.yml down
```
默认连接 `rpki-rtr:323`。可以通过环境变量覆盖:
```bash
RPKI_BIRD_RPKI_HOST=10.0.0.12 \
RPKI_BIRD_RPKI_PORT=323 \
docker compose --env-file deploy/bird/.env -f deploy/bird/docker-compose.yml up -d --build
```
### SSH
先启动 server SSH 模式:
```bash
./deploy/server/build-run.sh up --mode ssh
```
再启动 BIRD SSH client:
```bash
docker compose --env-file deploy/bird/.env \
-f deploy/bird/docker-compose.yml \
-f deploy/bird/docker-compose.ssh.yml \
up -d --build
```
查看日志:
```bash
docker logs -f bird-rpki-client
```
SSH 模式默认读取 `certs/ssh` 目录下的 key,并使用 `bird.conf.ssh.template` 生成运行时配置。关键默认值:
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `RPKI_BIRD_SSH_RPKI_HOST` | `rpki-rtr` | SSH RTR server 主机名 |
| `RPKI_RTR_SSH_PORT` | `22` | SSH RTR server 端口 |
| `RPKI_BIRD_SSH_CERTS_HOST_DIR` | `../../certs/ssh` | 宿主机 SSH 证书和 key 目录,复用 `rtr-client.key` |
## 如何判断 Client 正常
- `rtr_debug_client`: 日志中能看到 Reset Query 成功、EndOfData 和 payload 统计。
- FRR: `show rpki cache-connection` 显示已连接,`show rpki prefix-table` 有 VRP 条目。
- BIRD: 日志中 RPKI protocol 状态为 `up`,ROA/ASPA 表项摘要不为空。
如果 client 无法连接,优先检查:
- server 是否按对应 transport 启动。
- client 配置的 server 地址是否从当前容器或主机可达。
- TLS 的 server name 是否匹配证书 SAN。
- SSH 的用户名、client key 和 server public key 是否匹配。
- `RPKI_RTR_PROTOCOL_VERSION` 或 BIRD 配置中的 `min version`/`max version` 是否和 server 能力一致。

388
README.md
View File

@@ -1,63 +1,373 @@
# RPKI RTR Server
这是一个用 Rust 实现的 RPKI-to-Router (RTR) cache server。服务端从 CCR 文件中读取 RPKI 数据,生成 RTR cache snapshot 和 delta,并通过 RTR 协议提供给路由器或调试客户端。
# 单元测试
当前代码重点覆盖:
```
cargo test
- RTR server:TCP 默认启用,可选启用 TLS/mTLS 和进程内 SSH transport。
- 数据输入:从 CCR 目录选择最新 `.ccr` 文件,解析 VRP 和 VAP/ASPA。
- 本地策略:可选加载 SLURM 文件,对 CCR 解析出的 payload 做过滤和本地断言。
- 持久化:使用 RocksDB 保存 RTR cache 状态。
- 运行报告:输出 source、client、runtime 三类 JSON report。
- 运行期管理:可选启用 HTTP Admin API,修改部分 runtime config、触发 source reload、管理 SLURM 文件。
- 调试工具:`rtr_debug_client` 支持 TCP、TLS 和 SSH 连接 RTR server。
# 查看输出
cargo test -- --nocapture
## 目录
- [协议参考](#协议参考)
- [项目结构](#项目结构)
- [构建与测试](#构建与测试)
- [数据输入](#数据输入)
- [快速启动](#快速启动)
- [前置数据生成](#前置数据生成)
- [Docker 运行](#docker-运行)
- [跨架构构建](#跨架构构建)
- [传输模式](#传输模式)
- [主要环境变量](#主要环境变量)
- [运行报告](#运行报告)
- [Admin API](#admin-api)
- [调试客户端](#调试客户端)
- [部署入口](#部署入口)
- [开发说明](#开发说明)
## 协议参考
- RTR: [RFC 6810](https://www.rfc-editor.org/rfc/rfc6810.html), [RFC 8210](https://www.rfc-editor.org/rfc/rfc8210.html), [draft-ietf-sidrops-8210bis-26](https://www.ietf.org/archive/id/draft-ietf-sidrops-8210bis-26.html)
- SLURM: [RFC 8416](https://www.rfc-editor.org/rfc/rfc8416.html), [draft-ietf-sidrops-aspa-slurm-04](https://www.ietf.org/archive/id/draft-ietf-sidrops-aspa-slurm-04.html)(已过期,Expired 2026-05-20)
- CCR: [draft-ietf-sidrops-rpki-ccr](https://www.ietf.org/archive/id/draft-ietf-sidrops-rpki-ccr-02.html)
## 项目结构
| 路径 | 说明 |
| --- | --- |
| `src/main_rtr.rs` | RTR server 入口 |
| `src/rtr/` | RTR 协议、cache、store、server transport、admin、report |
| `src/source/` | CCR/SLURM source 加载流程 |
| `src/slurm/` | SLURM 文件解析、合并和策略应用 |
| `src/data_model/` | RPKI 相关数据结构 |
| `src/bin/rtr_debug_client/` | RTR 调试客户端 |
| `deploy/server/` | server Docker 镜像和 compose |
| `deploy/client/` | debug client Docker 镜像和 compose |
| `deploy/frr/`, `deploy/bird/` | FRR/BIRD 作为 RTR client 的测试部署 |
| `tests/` | 单元和集成测试 |
## 构建与测试
```bash
cargo build --bin rpki_rtr
cargo build --bin rtr_debug_client
cargo test
```
# 覆盖率(cargo-llvm-cov)
服务端 binary 名称是 `rpki_rtr`:
安装工具:
```
rustup component add llvm-tools-preview
cargo install cargo-llvm-cov --locked
```bash
cargo run --bin rpki_rtr
```
统计行覆盖率并要求 >=90%:
默认配置会监听 `0.0.0.0:323`,从 `./data` 查找 CCR 文件,并使用 `./rtr-db` 保存 RocksDB 数据。启动前需要确保 CCR 目录里存在可解析的 `.ccr` 文件。
```
./scripts/coverage.sh
# 或
cargo llvm-cov --fail-under-lines 90
## 数据输入
服务端启动和刷新时会读取 `RPKI_RTR_CCR_DIR` 指向的目录:
1. 如果该目录下存在包含 `.ccr` 文件的子目录,先按子目录名排序选择最新子目录。
2. 在选中的目录中按文件名排序选择最新 `.ccr` 文件。
3. 从 CCR 中解析 VRP 和 VAP/ASPA。
4. 如果配置了 `RPKI_RTR_SLURM_DIR`,加载其中启用的 `*.slurm` 文件并应用本地策略。
## 快速启动
### 前置数据生成
TLS/mTLS 或 SSH transport 需要本地证书和密钥。开发和 Docker 联调可用脚本生成 compose 默认引用的最小文件集:
```bash
bash scripts/10_generate-certs.sh
```
默认会复用现有插桩产物,不会先 clean。需要强制全量重编译时:
如需覆盖已有 `certs/` 内容:
```
COVERAGE_FORCE_CLEAN=1 ./scripts/coverage.sh
```bash
bash scripts/10_generate-certs.sh --force
```
说明:
- 默认行为适合本地重复确认覆盖率,避免每次都重编译整套插桩目标;
- 默认还会设置 `RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=1`,跳过会拉起 shell replay pipeline 的重型集成测试,避免 coverage 期间额外触发 `target/release` 构建;
- 默认还会设置 `RPKI_SKIP_HEAVY_BLACKBOX_TESTS=1`,跳过更慢的 blackbox CLI / CIR record 脚本测试,进一步降低日常 coverage 成本;
- 默认还会设置 `RPKI_SKIP_HEAVY_CRYPTO_TESTS=1`,跳过需要大量 OpenSSL 生成证书/CRL 的重型密码学测试,进一步压缩日常 coverage 时长;
- 如需把这批脚本回放测试也纳入 coverage,可显式关闭该开关:
`10_generate-certs.sh` 常用参数:
```
RPKI_SKIP_HEAVY_SCRIPT_REPLAY_TESTS=0 ./scripts/coverage.sh
| 参数 | 说明 |
| --- | --- |
| `--out-dir DIR` | 指定输出目录,默认生成到 `./certs`。 |
| `--server-dns NAME` | 指定 TLS server 证书的 dNSName SAN,默认 `localhost`。 |
| `--tls-only` | 只生成 `certs/tls` 下的 TLS 证书和私钥。 |
| `--ssh-only` | 只生成 `certs/ssh` 下的 SSH host key、client key 和 `authorized_keys`。 |
| `--force` | 覆盖已有文件。未指定时,如果目标文件已存在会直接报错退出。 |
| `-h`, `--help` | 查看脚本帮助。 |
TLS server name 默认是 `localhost`。如果需要其它 DNS 名称,需要同时设置 `scripts/10_generate-certs.sh --server-dns <name>` 和 client 的 `RPKI_RTR_TLS_SERVER_NAME=<name>`。
### Docker 运行
推荐使用 `deploy/server/build-run.sh` 和 `deploy/client/build-run.sh` 分别管理 server 与 debug client:
以下命令默认从仓库根目录执行。
```bash
chmod +x deploy/server/build-run.sh deploy/client/build-run.sh
./deploy/server/build-run.sh up --mode tcp
./deploy/client/build-run.sh up --mode tcp
./deploy/client/build-run.sh down
./deploy/server/build-run.sh down
```
如需连同第二批 blackbox 测试一起跑:
脚本常用参数:
```
RPKI_SKIP_HEAVY_BLACKBOX_TESTS=0 ./scripts/coverage.sh
| 参数/变量 | server | client | 说明 |
| --- | --- | --- | --- |
| `--mode base|tcp|tls|ssh`, `MODE` | yes | yes | 选择 compose 模式,默认 `tcp`。 |
| `--clients single|multi`, `CLIENTS` | no | yes | 单 client 或 5 client,默认 `single`。 |
| `--no-build`, `BUILD=0` | yes | yes | 只运行已有镜像,不执行 compose build。 |
| `--server-image TAG`, `SERVER_IMAGE` | yes | no | server 镜像名;会传给 compose 的 `RPKI_RTR_SERVER_IMAGE`。 |
| `--client-image TAG`, `CLIENT_IMAGE` | no | yes | client 镜像名;会传给 compose 的 `RPKI_RTR_CLIENT_IMAGE`。 |
| `--platform VALUE`, `TARGET_PLATFORM` | yes | yes | `buildx` 目标平台,默认 `linux/arm64`。 |
| `--push`, `BUILDX_PUSH=1` | yes | yes | `buildx` 构建后推送到 registry;不设置时加载到本地 Docker。 |
| `--skip-base-pull`, `SKIP_BASE_IMAGE_PULL=1` | yes | yes | 跳过 buildx 前的基础镜像预拉取。 |
支持的 action 包括 `up`、`build`、`buildx`、`rebuild`、`down`/`stop`、`restart`、`logs`、`ps`。完整帮助见:
```bash
./deploy/server/build-run.sh --help
./deploy/client/build-run.sh --help
```
如需连同重型 OpenSSL 证书路径测试一起跑:
### 跨架构构建
示例:x86 开发机本地构建 ARM 镜像,打包后传到 ARM 部署机。
```
RPKI_SKIP_HEAVY_CRYPTO_TESTS=0 ./scripts/coverage.sh
```bash
./deploy/server/build-run.sh buildx \
--platform linux/arm64 \
--server-image rpki-rtr:arm64
./deploy/client/build-run.sh buildx \
--platform linux/arm64 \
--client-image rpki-rtr-debug-client:arm64
mkdir -p dist/images
docker save rpki-rtr:arm64 -o dist/images/rpki-rtr-arm64.tar
docker save rpki-rtr-debug-client:arm64 -o dist/images/rpki-rtr-debug-client-arm64.tar
scp dist/images/*.tar user@arm-host:/tmp/
```
- replay 脚本现在也支持通过环境变量注入现成二进制,避免找不到二进制时自动 `cargo build --release`:
- `RPKI_BIN`
- `CIR_MATERIALIZE_BIN`
- `CIR_EXTRACT_INPUTS_BIN`
- `CCR_TO_COMPARE_VIEWS_BIN`
- `COVERAGE_FORCE_CLEAN=1` 适合需要完全从零重建插桩目标时使用。
ARM 部署机加载镜像并运行:
```bash
docker load -i /tmp/rpki-rtr-arm64.tar
docker load -i /tmp/rpki-rtr-debug-client-arm64.tar
SERVER_IMAGE=rpki-rtr:arm64 \
BUILD=0 ./deploy/server/build-run.sh up --mode tcp
CLIENT_IMAGE=rpki-rtr-debug-client:arm64 \
BUILD=0 ./deploy/client/build-run.sh up --mode tcp
```
```bash
docker compose -f deploy/server/docker-compose.yml up -d --build
docker compose -f deploy/server/docker-compose.yml down
```
TLS/mTLS 示例:
```bash
docker compose -f deploy/server/docker-compose.tls.yml up -d --build
docker compose -f deploy/server/docker-compose.tls.yml down
```
## 传输模式
TCP 默认启用。TLS 和 SSH 是附加监听,不会替代 TCP。
| 模式 | 默认地址 | 启用方式 |
| --- | --- | --- |
| TCP | `0.0.0.0:323` | 默认启用 |
| TLS/mTLS | `0.0.0.0:324` | `RPKI_RTR_ENABLE_TLS=true` |
| SSH | `0.0.0.0:22` | `RPKI_RTR_ENABLE_SSH=true` |
TLS server 会加载服务端证书、私钥和 client CA。SSH server 使用 OpenSSH host key 和 authorized_keys 文件;`RPKI_RTR_SSH_AUTH_MODE` 支持 `key`、`password`、`both`。
## 主要环境变量
布尔值支持 `true/false`、`1/0`、`yes/no`、`on/off`。
| 变量 | 默认值 | 说明 |
| --- | --- | --- |
| `RPKI_RTR_ENABLE_TLS` | `false` | 是否启用 TLS/mTLS 监听 |
| `RPKI_RTR_ENABLE_SSH` | `false` | 是否启用 SSH 监听 |
| `RPKI_RTR_TCP_ADDR` | `0.0.0.0:323` | TCP 监听地址 |
| `RPKI_RTR_TLS_ADDR` | `0.0.0.0:324` | TLS 监听地址 |
| `RPKI_RTR_SSH_ADDR` | `0.0.0.0:22` | SSH 监听地址 |
| `RPKI_RTR_SSH_PORT` | 未设置 | 仅覆盖 `RPKI_RTR_SSH_ADDR` 中的端口 |
| `RPKI_RTR_DB_PATH` | `./rtr-db` | RocksDB 路径 |
| `RPKI_RTR_CCR_DIR` | `./data` | CCR 输入目录 |
| `RPKI_RTR_SLURM_DIR` | 未设置 | SLURM 目录;空值表示禁用 |
| `RPKI_RTR_REPORT_DIR` | `./report` | JSON report 输出目录 |
| `RPKI_RTR_MAX_DELTA` | `100` | 每个 RTR 版本保留的最大 delta 数,必须大于 0 |
| `RPKI_RTR_PRUNE_DELTA_BY_SNAPSHOT_SIZE` | `false` | 是否按 snapshot wire size 继续裁剪 delta window |
| `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` | `300` | source 刷新间隔,单位秒 |
| `RPKI_RTR_REFRESH_INTERVAL_SECS` | 未设置 | 旧变量名,仍兼容;建议使用 `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS` |
| `RPKI_RTR_TIMING_REFRESH_SECS` | `3600` | RTR EndOfData `refresh` 字段 |
| `RPKI_RTR_TIMING_RETRY_SECS` | `600` | RTR EndOfData `retry` 字段 |
| `RPKI_RTR_TIMING_EXPIRE_SECS` | `7200` | RTR EndOfData `expire` 字段,必须大于 refresh 和 retry |
| `RPKI_RTR_MAX_CONNECTIONS` | `512` | 最大并发 RTR 连接数 |
| `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES` | `128` | 最大并发握手数,不能大于最大连接数 |
| `RPKI_RTR_NOTIFY_QUEUE_SIZE` | `1024` | Serial Notify 广播队列大小 |
| `RPKI_RTR_TCP_KEEPALIVE_SECS` | `60` | TCP keepalive;设为 `0` 表示禁用 |
| `RPKI_RTR_WARN_INSECURE_TCP` | `true` | TCP 模式是否输出安全提示 |
| `RPKI_RTR_REQUIRE_TLS_SERVER_DNS_NAME_SAN` | `false` | 是否要求 TLS 服务端证书包含 dNSName SAN |
| `RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH` | `true` | 是否校验 TLS client 证书 SAN IP 与 peer IP 匹配 |
| `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS` | `300` | runtime report 周期 |
| `RPKI_RTR_REPORT_HISTORY_LIMIT` | `10` | 每类 report 滚动保留数量 |
| `RPKI_RTR_TIMEZONE` | `Asia/Shanghai` | 日志和 report 使用的时区 |
| `RPKI_RTR_ADMIN_ADDR` | 未设置 | Admin API 监听地址;未设置时关闭 |
| `RPKI_RTR_ADMIN_TOKEN` | 未设置 | Admin API Bearer token;非 loopback 监听时必须设置 |
TLS 相关路径:
| 变量 | 默认值 |
| --- | --- |
| `RPKI_RTR_TLS_CERT_PATH` | `./certs/tls/server-dns.crt` |
| `RPKI_RTR_TLS_KEY_PATH` | `./certs/tls/server-dns.key` |
| `RPKI_RTR_TLS_CLIENT_CA_PATH` | `./certs/tls/client-ca.crt` |
SSH 相关配置:
| 变量 | 默认值 |
| --- | --- |
| `RPKI_RTR_SSH_HOST_KEY_PATH` | `./certs/ssh/ssh_host_rsa_key` |
| `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH` | `./certs/ssh/rtr-authorized_keys` |
| `RPKI_RTR_SSH_USERNAME` | `rpki-rtr` |
| `RPKI_RTR_SSH_SUBSYSTEM_NAME` | `rpki-rtr` |
| `RPKI_RTR_SSH_AUTH_MODE` | `key` |
| `RPKI_RTR_SSH_PASSWORD` | 未设置 |
## 运行报告
服务会在 `RPKI_RTR_REPORT_DIR` 下写入 JSON report,并按 `RPKI_RTR_REPORT_HISTORY_LIMIT` 滚动保留:
- `rtr-source-*.json`:CCR/SLURM source、fingerprint、刷新状态、数据质量、cache 统计。
- `rtr-clients-*.json`:client 连接数和连接方式统计。
- `rtr-runtime-*.json`:进程状态、服务状态和当前生效的 runtime config。
## Admin API
Admin API 默认关闭。设置 `RPKI_RTR_ADMIN_ADDR` 后启用:
```bash
export RPKI_RTR_ADMIN_ADDR=127.0.0.1:8323
export RPKI_RTR_ADMIN_TOKEN=change-me
```
如果监听非 loopback 地址,例如 `0.0.0.0:8323`,必须设置 `RPKI_RTR_ADMIN_TOKEN`。设置 token 后,请求需要携带:
```http
Authorization: Bearer change-me
```
当前实现的主要接口:
| 方法 | 路径 | 说明 |
| --- | --- | --- |
| `GET` | `/admin/rtr/health` | 健康检查和能力开关 |
| `GET` | `/admin/rtr/config` | 查看 runtime config |
| `POST` | `/admin/rtr/config` | 修改部分 runtime config |
| `GET` | `/admin/rtr/logs/tail` | 读取 stdout/stderr 日志 tail |
| `POST` | `/admin/rtr/slurm/reload` | 触发 source reload |
| `GET` | `/admin/rtr/slurm/files` | 列出 SLURM 文件 |
| `POST` | `/admin/rtr/slurm/files` | 新增或覆盖 SLURM 文件 |
| `GET` | `/admin/rtr/slurm/files/{name}` | 读取 SLURM 文件 |
| `PUT` | `/admin/rtr/slurm/files/{name}` | 新增或覆盖指定 SLURM 文件 |
| `DELETE` | `/admin/rtr/slurm/files/{name}` | 删除 SLURM 文件 |
| `POST` | `/admin/rtr/slurm/files/{name}/enable` | 启用 `.slurm.disabled` 文件 |
| `POST` | `/admin/rtr/slurm/files/{name}/disable` | 禁用 `.slurm` 文件 |
示例:
```bash
curl http://127.0.0.1:8323/admin/rtr/config \
-H "Authorization: Bearer change-me"
curl -X POST http://127.0.0.1:8323/admin/rtr/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer change-me" \
-d '{"max_delta": 6, "source_refresh_interval_seconds": 60}'
```
## 调试客户端
`rtr_debug_client` 用于手动发起 Reset Query 或 Serial Query,并观察服务端返回的 PDU。
RTR Client 的 Docker 调试、FRR/BIRD 互通验证和常用排障步骤见 `README.client.md`。
TCP:
```bash
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 reset
cargo run --bin rtr_debug_client -- 127.0.0.1:323 1 serial 42 100
```
TLS:
```bash
cargo run --bin rtr_debug_client -- \
127.0.0.1:324 1 reset \
--tls \
--ca-cert certs/tls/client-ca.crt \
--server-name localhost \
--client-cert certs/tls/client-good.crt \
--client-key certs/tls/client-good.key
```
SSH:
```bash
cargo run --bin rtr_debug_client -- \
127.0.0.1:22 1 reset \
--ssh \
--ssh-user rpki-rtr \
--ssh-key certs/ssh/rtr-client.key \
--ssh-server-key certs/ssh/ssh_host_rsa_key.pub
```
SSH password auth 也已实现;SSH 模式下 `--ssh-key` 和 `--ssh-password` 二选一,同时必须通过 `--ssh-known-hosts` 或 `--ssh-server-key` 做 host key 校验。
更多参数见 `src/bin/rtr_debug_client/README.md`。
## 部署入口
| 用途 | Compose 文件 |
| --- | --- |
| Server | `deploy/server/docker-compose.yml` |
| Server TCP 示例 | `deploy/server/docker-compose.tcp.yml` |
| Server TLS 示例 | `deploy/server/docker-compose.tls.yml` |
| Server SSH 示例 | `deploy/server/docker-compose.ssh.yml` |
| Debug client | `deploy/client/docker-compose.yml` |
| Debug client 多实例 | `deploy/client/docker-compose.clients.yml` |
| FRR client | `deploy/frr/docker-compose.yml` |
| BIRD client | `deploy/bird/docker-compose.yml` |
RTR Client 统一使用说明见 `README.client.md`。
通用操作:
```bash
COMPOSE_FILE=deploy/server/docker-compose.yml
docker compose -f "${COMPOSE_FILE}" up -d --build
docker compose -f "${COMPOSE_FILE}" ps
docker compose -f "${COMPOSE_FILE}" down
```
## 开发说明
- 代码中的 RTR cache 按协议版本维护状态;v0 只包含 Route Origin,v1 包含 Route Origin 和 Router Key,v2 包含 Route Origin、Router Key 和 ASPA。
- 当前 source pipeline 从 CCR 生成 Route Origin 和 ASPA,再可选应用 SLURM。
- `RtrStore::save_cache_state_versioned(...)` 是 cache 状态写入 RocksDB 的核心入口,相关边界测试在 `tests/test_store_boundary.rs`。
- Admin API 的日志 tail 依赖部署入口写出的 stdout/stderr 日志文件;容器入口脚本会把日志写到 `/app/logs`。

View File

@@ -1,8 +0,0 @@
[package]
name = "ours-manifest-bench"
version = "0.1.0"
edition = "2024"
[dependencies]
rpki = { path = "../..", default-features = false }

View File

@@ -1,145 +0,0 @@
use rpki::data_model::manifest::ManifestObject;
use std::hint::black_box;
use std::path::PathBuf;
use std::time::Instant;
#[derive(Debug, Clone)]
struct Config {
sample: Option<String>,
manifest_path: Option<PathBuf>,
iterations: u64,
warmup_iterations: u64,
repeats: u32,
}
fn usage_and_exit() -> ! {
eprintln!(
"Usage:\n ours-manifest-bench (--sample <name> | --manifest <path>) [--iterations N] [--warmup-iterations N] [--repeats N]\n\nExamples:\n cargo run --release -- --sample small-01 --iterations 20000 --warmup-iterations 2000 --repeats 3\n cargo run --release -- --manifest ../../tests/benchmark/selected_der/small-01.mft"
);
std::process::exit(2);
}
fn parse_args() -> Config {
let mut sample: Option<String> = None;
let mut manifest_path: Option<PathBuf> = None;
let mut iterations: u64 = 20_000;
let mut warmup_iterations: u64 = 2_000;
let mut repeats: u32 = 3;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--sample" => sample = Some(args.next().unwrap_or_else(|| usage_and_exit())),
"--manifest" => {
manifest_path = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit())))
}
"--iterations" => {
iterations = args
.next()
.unwrap_or_else(|| usage_and_exit())
.parse()
.unwrap_or_else(|_| usage_and_exit())
}
"--warmup-iterations" => {
warmup_iterations = args
.next()
.unwrap_or_else(|| usage_and_exit())
.parse()
.unwrap_or_else(|_| usage_and_exit())
}
"--repeats" => {
repeats = args
.next()
.unwrap_or_else(|| usage_and_exit())
.parse()
.unwrap_or_else(|_| usage_and_exit())
}
"-h" | "--help" => usage_and_exit(),
_ => usage_and_exit(),
}
}
if sample.is_none() && manifest_path.is_none() {
usage_and_exit();
}
if sample.is_some() && manifest_path.is_some() {
usage_and_exit();
}
Config {
sample,
manifest_path,
iterations,
warmup_iterations,
repeats,
}
}
fn derive_manifest_path(sample: &str) -> PathBuf {
// Assumes current working directory is `rpki/benchmark/ours_manifest_bench`.
PathBuf::from(format!("../../tests/benchmark/selected_der/{sample}.mft"))
}
fn main() {
let cfg = parse_args();
let manifest_path = cfg
.manifest_path
.clone()
.unwrap_or_else(|| derive_manifest_path(cfg.sample.as_deref().unwrap()));
let bytes = std::fs::read(&manifest_path).unwrap_or_else(|e| {
eprintln!("read manifest fixture failed: {e}; path={}", manifest_path.display());
std::process::exit(1);
});
let decoded_once = ManifestObject::decode_der(&bytes).unwrap_or_else(|e| {
eprintln!("decode failed: {e}; path={}", manifest_path.display());
std::process::exit(1);
});
let file_count = decoded_once.manifest.file_count();
let mut round_ns_per_op: Vec<f64> = Vec::with_capacity(cfg.repeats as usize);
let mut round_ops_per_s: Vec<f64> = Vec::with_capacity(cfg.repeats as usize);
for _round in 0..cfg.repeats {
for _ in 0..cfg.warmup_iterations {
let obj = ManifestObject::decode_der(black_box(&bytes)).expect("warmup decode");
black_box(obj);
}
let start = Instant::now();
for _ in 0..cfg.iterations {
let obj = ManifestObject::decode_der(black_box(&bytes)).expect("timed decode");
black_box(obj);
}
let elapsed = start.elapsed();
let ns_per_op = (elapsed.as_secs_f64() * 1e9) / (cfg.iterations as f64);
let ops_per_s = (cfg.iterations as f64) / elapsed.as_secs_f64();
round_ns_per_op.push(ns_per_op);
round_ops_per_s.push(ops_per_s);
}
let avg_ns_per_op = round_ns_per_op.iter().sum::<f64>() / (round_ns_per_op.len() as f64);
let avg_ops_per_s = round_ops_per_s.iter().sum::<f64>() / (round_ops_per_s.len() as f64);
let sample_name = cfg.sample.clone().unwrap_or_else(|| {
manifest_path
.file_name()
.map(|s| s.to_string_lossy().to_string())
.unwrap_or_else(|| manifest_path.display().to_string())
});
let sample_name = sample_name
.strip_suffix(".mft")
.unwrap_or(&sample_name)
.to_string();
println!("fixture: {}", manifest_path.display());
println!();
println!("| sample | avg ns/op | ops/s | file count |");
println!("|---|---:|---:|---:|");
println!(
"| {} | {:.2} | {:.2} | {} |",
sample_name, avg_ns_per_op, avg_ops_per_s, file_count
);
}

View File

@@ -1,8 +0,0 @@
[package]
name = "routinator-object-bench"
version = "0.1.0"
edition = "2024"
publish = false
[dependencies]
rpki = { version = "=0.19.1", features = ["repository"] }

View File

@@ -1,552 +0,0 @@
use rpki::repository::cert::Cert;
use rpki::repository::crl::Crl;
use rpki::repository::manifest::Manifest;
use rpki::repository::roa::Roa;
use rpki::repository::aspa::Aspa;
use rpki::repository::resources::{AsResources, IpResources};
use std::hint::black_box;
use std::path::{Path, PathBuf};
use std::time::Instant;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum ObjType {
Cer,
Crl,
Manifest,
Roa,
Aspa,
}
impl ObjType {
fn parse(s: &str) -> Result<Self, String> {
match s {
"cer" => Ok(Self::Cer),
"crl" => Ok(Self::Crl),
"manifest" => Ok(Self::Manifest),
"roa" => Ok(Self::Roa),
"aspa" => Ok(Self::Aspa),
_ => Err("type must be one of: cer, crl, manifest, roa, aspa".into()),
}
}
fn as_str(self) -> &'static str {
match self {
ObjType::Cer => "cer",
ObjType::Crl => "crl",
ObjType::Manifest => "manifest",
ObjType::Roa => "roa",
ObjType::Aspa => "aspa",
}
}
fn ext(self) -> &'static str {
match self {
ObjType::Cer => "cer",
ObjType::Crl => "crl",
ObjType::Manifest => "mft",
ObjType::Roa => "roa",
ObjType::Aspa => "asa",
}
}
}
#[derive(Clone, Debug)]
struct Sample {
obj_type: ObjType,
name: String,
path: PathBuf,
}
#[derive(Clone, Debug)]
struct Config {
dir: PathBuf,
type_filter: Option<ObjType>,
sample_filter: Option<String>,
fixed_iters: Option<u64>,
warmup_iters: u64,
rounds: u64,
min_round_ms: u64,
max_adaptive_iters: u64,
strict: bool,
cert_inspect: bool,
out_csv: Option<PathBuf>,
out_md: Option<PathBuf>,
}
fn usage_and_exit(err: Option<&str>) -> ! {
if let Some(err) = err {
eprintln!("error: {err}");
eprintln!();
}
eprintln!(
"Usage:\n\
cargo run --release --manifest-path rpki/benchmark/routinator_object_bench/Cargo.toml -- [OPTIONS]\n\
\n\
Options:\n\
--dir <PATH> Fixtures root dir (default: ../../tests/benchmark/selected_der_v2)\n\
--type <cer|crl|manifest|roa|aspa> Filter by type\n\
--sample <NAME> Filter by sample name (e.g. p50)\n\
--iters <N> Fixed iterations per round (optional; otherwise adaptive)\n\
--warmup-iters <N> Warmup iterations (default: 50)\n\
--rounds <N> Rounds (default: 5)\n\
--min-round-ms <MS> Adaptive: minimum round time (default: 200)\n\
--max-iters <N> Adaptive: maximum iters (default: 1_000_000)\n\
--strict <true|false> Strict DER where applicable (default: true)\n\
--cert-inspect Also run Cert::inspect_ca/inspect_ee where applicable (default: false)\n\
--out-csv <PATH> Write CSV output\n\
--out-md <PATH> Write Markdown output\n\
"
);
std::process::exit(2);
}
fn parse_bool(s: &str, name: &str) -> bool {
match s {
"1" | "true" | "TRUE" | "yes" | "YES" => true,
"0" | "false" | "FALSE" | "no" | "NO" => false,
_ => usage_and_exit(Some(&format!("{name} must be true/false"))),
}
}
fn parse_u64(s: &str, name: &str) -> u64 {
s.parse::<u64>()
.unwrap_or_else(|_| usage_and_exit(Some(&format!("{name} must be an integer"))))
}
fn default_samples_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../tests/benchmark/selected_der_v2")
}
fn parse_args() -> Config {
let mut dir: PathBuf = default_samples_dir();
let mut type_filter: Option<ObjType> = None;
let mut sample_filter: Option<String> = None;
let mut fixed_iters: Option<u64> = None;
let mut warmup_iters: u64 = 50;
let mut rounds: u64 = 5;
let mut min_round_ms: u64 = 200;
let mut max_adaptive_iters: u64 = 1_000_000;
let mut strict: bool = true;
let mut cert_inspect: bool = false;
let mut out_csv: Option<PathBuf> = None;
let mut out_md: Option<PathBuf> = None;
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--dir" => dir = PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None))),
"--type" => {
type_filter = Some(ObjType::parse(
&args.next().unwrap_or_else(|| usage_and_exit(None)),
)
.unwrap_or_else(|e| usage_and_exit(Some(&e))))
}
"--sample" => {
sample_filter = Some(args.next().unwrap_or_else(|| usage_and_exit(None)))
}
"--iters" => {
fixed_iters = Some(parse_u64(
&args.next().unwrap_or_else(|| usage_and_exit(None)),
"--iters",
))
}
"--warmup-iters" => {
warmup_iters = parse_u64(
&args.next().unwrap_or_else(|| usage_and_exit(None)),
"--warmup-iters",
)
}
"--rounds" => {
rounds = parse_u64(&args.next().unwrap_or_else(|| usage_and_exit(None)), "--rounds")
}
"--min-round-ms" => {
min_round_ms = parse_u64(
&args.next().unwrap_or_else(|| usage_and_exit(None)),
"--min-round-ms",
)
}
"--max-iters" => {
max_adaptive_iters = parse_u64(
&args.next().unwrap_or_else(|| usage_and_exit(None)),
"--max-iters",
)
}
"--strict" => {
strict = parse_bool(
&args.next().unwrap_or_else(|| usage_and_exit(None)),
"--strict",
)
}
"--cert-inspect" => cert_inspect = true,
"--out-csv" => out_csv = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
"--out-md" => out_md = Some(PathBuf::from(args.next().unwrap_or_else(|| usage_and_exit(None)))),
"-h" | "--help" => usage_and_exit(None),
_ => usage_and_exit(Some(&format!("unknown argument: {arg}"))),
}
}
if warmup_iters == 0 {
usage_and_exit(Some("--warmup-iters must be > 0"));
}
if rounds == 0 {
usage_and_exit(Some("--rounds must be > 0"));
}
if min_round_ms == 0 {
usage_and_exit(Some("--min-round-ms must be > 0"));
}
if max_adaptive_iters == 0 {
usage_and_exit(Some("--max-iters must be > 0"));
}
if let Some(n) = fixed_iters {
if n == 0 {
usage_and_exit(Some("--iters must be > 0"));
}
}
Config {
dir,
type_filter,
sample_filter,
fixed_iters,
warmup_iters,
rounds,
min_round_ms,
max_adaptive_iters,
strict,
cert_inspect,
out_csv,
out_md,
}
}
fn read_samples(root: &Path) -> Vec<Sample> {
let mut out = Vec::new();
for obj_type in [
ObjType::Cer,
ObjType::Crl,
ObjType::Manifest,
ObjType::Roa,
ObjType::Aspa,
] {
let dir = root.join(obj_type.as_str());
let rd = match std::fs::read_dir(&dir) {
Ok(rd) => rd,
Err(_) => continue,
};
for ent in rd.flatten() {
let path = ent.path();
if path.extension().and_then(|s| s.to_str()) != Some(obj_type.ext()) {
continue;
}
let name = path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
out.push(Sample { obj_type, name, path });
}
}
out.sort_by(|a, b| a.obj_type.cmp(&b.obj_type).then_with(|| a.name.cmp(&b.name)));
out
}
fn choose_iters_adaptive<F: FnMut()>(mut op: F, min_round_ms: u64, max_iters: u64) -> u64 {
let min_secs = (min_round_ms as f64) / 1e3;
let mut iters: u64 = 1;
loop {
let start = Instant::now();
for _ in 0..iters {
op();
}
let elapsed = start.elapsed().as_secs_f64();
if elapsed >= min_secs {
return iters;
}
if iters >= max_iters {
return iters;
}
iters = (iters.saturating_mul(2)).min(max_iters);
}
}
fn count_ip(res: &IpResources) -> u64 {
if res.is_inherited() {
return 1;
}
let Ok(blocks) = res.to_blocks() else {
return 0;
};
blocks.iter().count() as u64
}
fn count_as(res: &AsResources) -> u64 {
if res.is_inherited() {
return 1;
}
let Ok(blocks) = res.to_blocks() else {
return 0;
};
blocks.iter().count() as u64
}
fn complexity(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) -> u64 {
match obj_type {
ObjType::Cer => {
let cert = Cert::decode(bytes).expect("decode cert");
if cert_inspect {
if cert.is_ca() {
cert.inspect_ca(strict).expect("inspect ca");
} else {
cert.inspect_ee(strict).expect("inspect ee");
}
}
count_ip(cert.v4_resources())
.saturating_add(count_ip(cert.v6_resources()))
.saturating_add(count_as(cert.as_resources()))
}
ObjType::Crl => {
let crl = Crl::decode(bytes).expect("decode crl");
crl.revoked_certs().iter().count() as u64
}
ObjType::Manifest => {
let mft = Manifest::decode(bytes, strict).expect("decode manifest");
if cert_inspect {
mft.cert().inspect_ee(strict).expect("inspect ee");
}
mft.content().len() as u64
}
ObjType::Roa => {
let roa = Roa::decode(bytes, strict).expect("decode roa");
if cert_inspect {
roa.cert().inspect_ee(strict).expect("inspect ee");
}
roa.content().iter().count() as u64
}
ObjType::Aspa => {
let asa = Aspa::decode(bytes, strict).expect("decode aspa");
if cert_inspect {
asa.cert().inspect_ee(strict).expect("inspect ee");
}
asa.content().provider_as_set().len() as u64
}
}
}
fn decode_profile(obj_type: ObjType, bytes: &[u8], strict: bool, cert_inspect: bool) {
match obj_type {
ObjType::Cer => {
let cert = Cert::decode(black_box(bytes)).expect("decode cert");
if cert_inspect {
if cert.is_ca() {
cert.inspect_ca(strict).expect("inspect ca");
} else {
cert.inspect_ee(strict).expect("inspect ee");
}
}
black_box(cert);
}
ObjType::Crl => {
let crl = Crl::decode(black_box(bytes)).expect("decode crl");
black_box(crl);
}
ObjType::Manifest => {
let mft = Manifest::decode(black_box(bytes), strict).expect("decode manifest");
if cert_inspect {
mft.cert().inspect_ee(strict).expect("inspect ee");
}
black_box(mft);
}
ObjType::Roa => {
let roa = Roa::decode(black_box(bytes), strict).expect("decode roa");
if cert_inspect {
roa.cert().inspect_ee(strict).expect("inspect ee");
}
black_box(roa);
}
ObjType::Aspa => {
let asa = Aspa::decode(black_box(bytes), strict).expect("decode aspa");
if cert_inspect {
asa.cert().inspect_ee(strict).expect("inspect ee");
}
black_box(asa);
}
}
}
#[derive(Clone, Debug)]
struct ResultRow {
obj_type: String,
sample: String,
size_bytes: usize,
complexity: u64,
avg_ns_per_op: f64,
ops_per_sec: f64,
}
fn render_markdown(title: &str, rows: &[ResultRow]) -> String {
let mut out = String::new();
out.push_str(&format!("# {title}\n\n"));
out.push_str("| type | sample | size_bytes | complexity | avg ns/op | ops/s |\n");
out.push_str("|---|---|---:|---:|---:|---:|\n");
for r in rows {
out.push_str(&format!(
"| {} | {} | {} | {} | {:.2} | {:.2} |\n",
r.obj_type, r.sample, r.size_bytes, r.complexity, r.avg_ns_per_op, r.ops_per_sec
));
}
out
}
fn render_csv(rows: &[ResultRow]) -> String {
let mut out = String::new();
out.push_str("type,sample,size_bytes,complexity,avg_ns_per_op,ops_per_sec\n");
for r in rows {
let sample = r.sample.replace('"', "\"\"");
out.push_str(&format!(
"{},{},{},{},{:.6},{:.6}\n",
r.obj_type,
format!("\"{}\"", sample),
r.size_bytes,
r.complexity,
r.avg_ns_per_op,
r.ops_per_sec
));
}
out
}
fn create_parent_dirs(path: &Path) {
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).unwrap_or_else(|e| {
panic!("create_dir_all {}: {e}", parent.display());
});
}
}
fn write_text_file(path: &Path, content: &str) {
create_parent_dirs(path);
std::fs::write(path, content).unwrap_or_else(|e| panic!("write {}: {e}", path.display()));
}
fn main() {
let cfg = parse_args();
let mut samples = read_samples(&cfg.dir);
if samples.is_empty() {
usage_and_exit(Some(&format!(
"no samples found under: {}",
cfg.dir.display()
)));
}
if let Some(t) = cfg.type_filter {
samples.retain(|s| s.obj_type == t);
if samples.is_empty() {
usage_and_exit(Some(&format!("no sample matched --type {}", t.as_str())));
}
}
if let Some(filter) = cfg.sample_filter.as_deref() {
samples.retain(|s| s.name == filter);
if samples.is_empty() {
usage_and_exit(Some(&format!("no sample matched --sample {filter}")));
}
}
println!("# Routinator baseline (rpki crate) decode benchmark (selected_der_v2)");
println!();
println!("- dir: {}", cfg.dir.display());
println!("- strict: {}", cfg.strict);
println!("- cert_inspect: {}", cfg.cert_inspect);
if let Some(t) = cfg.type_filter {
println!("- type: {}", t.as_str());
}
if let Some(s) = cfg.sample_filter.as_deref() {
println!("- sample: {}", s);
}
if let Some(n) = cfg.fixed_iters {
println!("- iters: {} (fixed)", n);
} else {
println!(
"- warmup: {} iters, rounds: {}, min_round: {}ms (adaptive iters, max {})",
cfg.warmup_iters, cfg.rounds, cfg.min_round_ms, cfg.max_adaptive_iters
);
}
if let Some(p) = cfg.out_csv.as_ref() {
println!("- out_csv: {}", p.display());
}
if let Some(p) = cfg.out_md.as_ref() {
println!("- out_md: {}", p.display());
}
println!();
println!("| type | sample | size_bytes | complexity | avg ns/op | ops/s |");
println!("|---|---|---:|---:|---:|---:|");
let mut rows: Vec<ResultRow> = Vec::with_capacity(samples.len());
for sample in &samples {
let bytes = std::fs::read(&sample.path)
.unwrap_or_else(|e| panic!("read {}: {e}", sample.path.display()));
let size_bytes = bytes.len();
let complexity = complexity(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
for _ in 0..cfg.warmup_iters {
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
}
let mut per_round_ns_per_op = Vec::with_capacity(cfg.rounds as usize);
for _round in 0..cfg.rounds {
let iters = if let Some(n) = cfg.fixed_iters {
n
} else {
choose_iters_adaptive(
|| decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect),
cfg.min_round_ms,
cfg.max_adaptive_iters,
)
};
let start = Instant::now();
for _ in 0..iters {
decode_profile(sample.obj_type, bytes.as_slice(), cfg.strict, cfg.cert_inspect);
}
let elapsed = start.elapsed();
let total_ns = elapsed.as_secs_f64() * 1e9;
per_round_ns_per_op.push(total_ns / (iters as f64));
}
let avg_ns = per_round_ns_per_op.iter().sum::<f64>() / (per_round_ns_per_op.len() as f64);
let ops_per_sec = 1e9_f64 / avg_ns;
println!(
"| {} | {} | {} | {} | {:.2} | {:.2} |",
sample.obj_type.as_str(),
sample.name,
size_bytes,
complexity,
avg_ns,
ops_per_sec
);
rows.push(ResultRow {
obj_type: sample.obj_type.as_str().to_string(),
sample: sample.name.clone(),
size_bytes,
complexity,
avg_ns_per_op: avg_ns,
ops_per_sec,
});
}
if let Some(path) = cfg.out_md.as_ref() {
let md = render_markdown(
"Routinator baseline (rpki crate) decode+inspect (selected_der_v2)",
&rows,
);
write_text_file(path, &md);
eprintln!("Wrote {}", path.display());
}
if let Some(path) = cfg.out_csv.as_ref() {
let csv = render_csv(&rows);
write_text_file(path, &csv);
eprintln!("Wrote {}", path.display());
}
}

23
data/example.slurm Normal file
View File

@@ -0,0 +1,23 @@
{
"slurmVersion": 2,
"validationOutputFilters": {
"prefixFilters": [
{
"prefix": "24.0.0.0/8",
"comment": "Filter many VRPs in current CCR sample"
}
],
"bgpsecFilters": [],
"aspaFilters": [
{
"customerAsn": 80,
"comment": "Filter one ASPA known to exist in current CCR sample"
}
]
},
"locallyAddedAssertions": {
"prefixAssertions": [],
"bgpsecAssertions": [],
"aspaAssertions": []
}
}

View File

@@ -1,87 +0,0 @@
# ours RP ARM64 Docker Compose 示例配置
# 复制为 .env 后再启动:
# cp .env.example .env
# 业务镜像名称;需要先通过 docker load 导入远端 Docker。
RPKI_IMAGE=ours-rp-runtime-arm64:dev
# 核心 RP 镜像强制以 ARM64 运行;远端233为 x86_64,需要 binfmt/qemu 支持。
RPKI_PLATFORM=linux/arm64
# 验收默认只跑 APNIC;all5 可改成 afrinic,apnic,arin,lacnic,ripe。
RIRS=apnic
# 固定跑两轮:首轮 snapshot,第二轮 delta。
# 负数表示持续运行;ARM64 QEMU 首版验收不建议默认持续运行。
MAX_RUNS=2
# 两轮之间间隔秒数。
INTERVAL_SECS=0
# 保留最近多少个 run 目录。
RETAIN_RUNS=5
# TA/TAL 输入模式:
# - file-with-ta:完全使用镜像内 fixture;
# - file-live-ta:snapshot 轮先完成 live TA 获取,delta 轮后台刷新 TA;
# - url:从 TAL URL 拉取。
TAL_INPUT_MODE=file-live-ta
# rsync 默认按 module-root 批量拉取,和当前 ours RP soak 默认优化配置一致。
RSYNC_SCOPE=module-root
# 容器里不需要杀宿主机上的其他 RP 进程;远端宿主机如有竞争进程,请在宿主机侧处理。
DISABLE_COMPETING_RPS=0
# 运行态数据目录。容器内固定路径,外部由 compose volume 保存。
RUN_ROOT=/var/lib/ours-rp
DB_DIR=/var/lib/ours-rp/state/db
RSYNC_MIRROR_ROOT=/var/lib/ours-rp/state/rsync-mirror
# 每轮结束后清理 daemon 临时目录。
# Docker Compose 中 tmp 是独立 volume 挂载点,不能删除挂载点本身;默认关闭。
CLEAN_TMP_AFTER_RUN=0
# 报告使用 compact JSON,降低写盘体积。
OUTPUT_COMPACT_REPORT=1
# 复用 rsync mirror,避免 delta 每轮从零拉取。
ALLOW_RSYNC_MIRROR_REUSE=1
# 前一轮失败时,新一轮从 snapshot 恢复。
FAILURE_SNAPSHOT_RESET=1
# QEMU 验收优先降低额外统计开销;需要精确 DB 统计时改为 3 或其他正整数。
DB_STATS_EXACT_EVERY=0
# 开启当前主线使用的验证缓存与请求预取能力。
ENABLE_CHILD_CERTIFICATE_VALIDATION_CACHE=1
RPKI_ANALYZE=1
RPKI_EXTRA_ARGS="--enable-transport-request-prefetch --enable-publication-point-validation-cache --enable-roa-validation-cache --parallel-max-repo-sync-workers-global 4 --parallel-phase2-object-workers 4 --memory-trim-after-validation"
# 进度日志阈值。QEMU 下执行较慢,阈值不宜过低。
RPKI_PROGRESS_LOG=1
RPKI_PROGRESS_SLOW_SECS=20
RPKI_PROGRESS_STAGE_FRESH_SLOW_MS=2000
RPKI_PROGRESS_PP_CONTROL_SLOW_MS=200
RPKI_PROGRESS_PP_CACHE_SLOW_MS=100
RPKI_PROGRESS_CONTROL_LOOP_SLOW_MS=2000
# live TA 刷新超时。
LIVE_TA_REFRESH_CONNECT_TIMEOUT_SECS=15
LIVE_TA_REFRESH_MAX_TIME_SECS=120
# file-live-ta 在 snapshot 轮先完成 live TA 获取再启动子进程,避免首轮使用旧 fixture TA。
LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1
# metrics sidecar 配置。
METRICS_INSTANCE=remote233-arm64-qemu
METRICS_PORT=9556
METRICS_POLL_SECS=10
# Prometheus / Grafana 配置。
PROMETHEUS_PORT=9090
PROMETHEUS_RETENTION=7d
GRAFANA_PORT=3000
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin

View File

@@ -1,94 +0,0 @@
services:
ours-rp-soak:
image: ${RPKI_IMAGE:-ours-rp-runtime-arm64:dev}
platform: ${RPKI_PLATFORM:-linux/arm64}
container_name: ours-rp-arm64-soak
env_file:
- ./.env
environment:
PACKAGE_ROOT: /opt/ours-rp
ENV_FILE: /opt/ours-rp/.env
RUN_ROOT: /var/lib/ours-rp
BIN_DIR: /opt/ours-rp/bin
FIXTURE_DIR: /opt/ours-rp/fixtures
volumes:
- ./.env:/opt/ours-rp/.env:ro
- rpki-state:/var/lib/ours-rp/state
- rpki-runs:/var/lib/ours-rp/runs
- rpki-logs:/var/lib/ours-rp/logs
- rpki-tmp:/var/lib/ours-rp/tmp
restart: "no"
profiles:
- core
artifact-metrics:
image: ${RPKI_IMAGE:-ours-rp-runtime-arm64:dev}
platform: ${RPKI_PLATFORM:-linux/arm64}
container_name: ours-rp-arm64-artifact-metrics
env_file:
- ./.env
command:
- /opt/ours-rp/bin/rpki_artifact_metrics
- --run-root
- /var/lib/ours-rp
- --listen
- 0.0.0.0:9556
- --poll-secs
- ${METRICS_POLL_SECS:-10}
- --instance
- ${METRICS_INSTANCE:-remote233-arm64-qemu}
ports:
- "${METRICS_PORT:-9556}:9556"
volumes:
- rpki-state:/var/lib/ours-rp/state:ro
- rpki-runs:/var/lib/ours-rp/runs:ro
- rpki-logs:/var/lib/ours-rp/logs:ro
restart: unless-stopped
profiles:
- sidecar
prometheus:
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}
container_name: ours-rp-arm64-prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-7d}
- --web.enable-lifecycle
depends_on:
- artifact-metrics
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
restart: unless-stopped
profiles:
- monitor
grafana:
image: ${GRAFANA_IMAGE:-grafana/grafana:11.3.1}
container_name: ours-rp-arm64-grafana
depends_on:
- prometheus
ports:
- "${GRAFANA_PORT:-3000}:3000"
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
restart: unless-stopped
profiles:
- monitor
volumes:
rpki-state:
rpki-runs:
rpki-logs:
rpki-tmp:
prometheus-data:
grafana-data:

View File

@@ -1,761 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_publication_points",
"legendFormat": "publication points",
"refId": "A"
}
],
"title": "Publication Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_ok\"}",
"legendFormat": "rrdp ok",
"refId": "A"
}
],
"title": "RRDP OK Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 0
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "sum(ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}) or vector(0)",
"legendFormat": "fallback",
"refId": "A"
}
],
"title": "Rsync Fallback Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 0
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
"legendFormat": "failed no cache",
"refId": "A"
}
],
"title": "Failed No Cache Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 4
},
"id": 5,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"repo_sync_total\"}",
"legendFormat": "repo sync total",
"refId": "A"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rrdp_download_total\"}",
"legendFormat": "rrdp download",
"refId": "B"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rsync_download_total\"}",
"legendFormat": "rsync download",
"refId": "C"
}
],
"title": "Repo Sync Download Durations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 12,
"w": 12,
"h": 8
},
"id": 6,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_repo_sync_phase_count",
"legendFormat": "{{phase}}",
"refId": "A"
}
],
"title": "Repo Sync Phase Counts",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 12,
"w": 12,
"h": 8
},
"id": 7,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}",
"legendFormat": "rrdp failed, rsync ok",
"refId": "A"
},
{
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_failed\"}",
"legendFormat": "rrdp failed, rsync failed",
"refId": "B"
},
{
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
"legendFormat": "failed no cache",
"refId": "C"
},
{
"expr": "ours_rp_tree_instances{state=\"failed\"}",
"legendFormat": "tree failed",
"refId": "D"
}
],
"title": "Repo Failure / Fallback Counts",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 20,
"w": 12,
"h": 8
},
"id": 8,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_ok\"}",
"legendFormat": "rsync fallback duration",
"refId": "A"
},
{
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_failed\"}",
"legendFormat": "failed duration",
"refId": "B"
},
{
"expr": "ours_rp_repo_terminal_state_duration_seconds_total{terminal_state=\"failed_no_cache\"}",
"legendFormat": "failed no cache duration",
"refId": "C"
}
],
"title": "Repo Failure / Fallback Durations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 29,
"w": 12,
"h": 9
},
"id": 9,
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
}
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_rrdp_rsync_failed_repository_duration_seconds",
"format": "table",
"instant": true,
"legendFormat": "",
"refId": "A"
}
],
"title": "RRDP + Rsync Failed Repositories",
"type": "table",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"job": true,
"terminal_state": true,
"rank": true,
"transport": true,
"__name__": true,
"publication_points": true,
"instance": true,
"repo_id": true,
"pp_id": true,
"exported_instance": true,
"rp": true,
"source": true
},
"indexByName": {
"Time": 0,
"host": 1,
"phase": 2,
"uri": 3,
"Value": 4
},
"renameByName": {
"Value": "duration"
}
}
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 29,
"w": 12,
"h": 9
},
"id": 11,
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
}
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "topk(20, ours_rp_top_repository_sync_duration_seconds)",
"format": "table",
"instant": true,
"legendFormat": "",
"refId": "A"
}
],
"title": "Top 20 Repositories by Sync Duration",
"type": "table",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"job": true,
"terminal_state": true,
"__name__": true,
"publication_points": true,
"instance": true,
"repo_id": true,
"phase": true,
"pp_id": true,
"exported_instance": true,
"rp": true,
"source": true
},
"indexByName": {
"Time": 0,
"host": 1,
"rank": 2,
"transport": 3,
"uri": 4,
"Value": 5
},
"renameByName": {
"Value": "value"
}
}
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 38,
"w": 24,
"h": 9
},
"id": 10,
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
}
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "topk(20, ours_rp_top_publication_point_object_count)",
"format": "table",
"instant": true,
"legendFormat": "",
"refId": "A"
}
],
"title": "Top Publication Points by Objects",
"type": "table",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"job": true,
"__name__": true,
"publication_points": true,
"instance": true,
"repo_id": true,
"phase": true,
"pp_id": true,
"exported_instance": true,
"rp": true,
"source": true
},
"indexByName": {
"Time": 0,
"host": 1,
"rank": 2,
"terminal_state": 3,
"transport": 4,
"uri": 5,
"Value": 6
},
"renameByName": {}
}
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"description": "Per-repository sync success in the latest successful run; 1 means successful, 0 means failed or failed_no_cache.",
"fieldConfig": {
"defaults": {
"unit": "bool"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 47
},
"id": 12,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"expr": "ours_rp_repository_sync_success",
"legendFormat": "{{host}} {{repo_id}}",
"refId": "A"
}
],
"title": "Repository Sync Success by Repo",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"description": "Per-repository total sync duration aggregated from publication point repo_sync_duration_ms.",
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 55
},
"id": 13,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"expr": "ours_rp_repository_sync_duration_seconds{stat=\"sum\"}",
"legendFormat": "{{host}} {{repo_id}}",
"refId": "A"
}
],
"title": "Repository Sync Duration by Repo",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"description": "Per-repository downloaded bytes attributed from report.json downloads events.",
"fieldConfig": {
"defaults": {
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 63
},
"id": 14,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"expr": "ours_rp_repository_download_bytes",
"legendFormat": "{{host}} {{repo_id}}",
"refId": "A"
}
],
"title": "Repository Download Bytes by Repo",
"type": "timeseries"
}
],
"refresh": "5s",
"schemaVersion": 40,
"tags": [
"ours-rp",
"rpki",
"soak",
"repo-sync"
],
"templating": {
"list": []
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Ours RP Repo Sync",
"uid": "ours-rp-repo-sync",
"version": 3,
"weekStart": ""
}

View File

@@ -1,875 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 0,
"w": 6,
"h": 4
},
"id": 1,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_cir_trust_anchors",
"legendFormat": "RIRs",
"refId": "A"
}
],
"title": "Current Run RIRs",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 6,
"y": 0,
"w": 6,
"h": 4
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_run_duration_seconds",
"legendFormat": "wall",
"refId": "A"
}
],
"title": "Latest Wall Time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 0,
"w": 6,
"h": 4
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_run_max_rss_bytes",
"legendFormat": "rss",
"refId": "A"
}
],
"title": "Latest Max RSS",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 18,
"y": 0,
"w": 6,
"h": 4
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_publication_points",
"legendFormat": "publication points",
"refId": "A"
}
],
"title": "Publication Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 4,
"w": 6,
"h": 4
},
"id": 9,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_run_sequence",
"legendFormat": "seq",
"refId": "A"
}
],
"title": "Latest Run Sequence",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 2,
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 90
},
{
"color": "green",
"value": 98
}
]
}
},
"overrides": []
},
"gridPos": {
"x": 6,
"y": 4,
"w": 6,
"h": 4
},
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
"legendFormat": "PP cache hit ratio",
"refId": "A"
}
],
"title": "Latest PP Cache Hit Ratio",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 4,
"w": 6,
"h": 4
},
"id": 11,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_vrps{kind=\"total\"}",
"legendFormat": "VRPs raw",
"refId": "A"
}
],
"title": "VRPs",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"gridPos": {
"x": 18,
"y": 4,
"w": 6,
"h": 4
},
"id": 12,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_vaps",
"legendFormat": "VAPs",
"refId": "A"
}
],
"title": "VAPs",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 8,
"w": 12,
"h": 8
},
"id": 5,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_duration_seconds",
"legendFormat": "wall",
"refId": "A"
},
{
"expr": "ours_rp_stage_duration_seconds{stage=\"validation\"}",
"legendFormat": "validation",
"refId": "B"
}
],
"title": "Run / Validation Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 8,
"w": 12,
"h": 8
},
"id": 6,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_vrps{kind=\"total\"}",
"legendFormat": "VRPs raw",
"refId": "A"
},
{
"expr": "ours_rp_vrps{kind=\"unique\"}",
"legendFormat": "VRPs unique",
"refId": "D"
},
{
"expr": "ours_rp_vaps",
"legendFormat": "VAPs",
"refId": "B"
},
{
"expr": "ours_rp_cir_objects",
"legendFormat": "CIR objects",
"refId": "C"
}
],
"title": "Output and Input Sizes",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 16,
"w": 12,
"h": 8
},
"id": 8,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_large_publication_points",
"legendFormat": "> {{object_count_gt}} objects",
"refId": "A"
}
],
"title": "Large Publication Points by Object Count",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 16,
"w": 12,
"h": 8
},
"id": 13,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"validation\"}",
"legendFormat": "validation",
"refId": "A"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"report_write\"}",
"legendFormat": "report write",
"refId": "E"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"ccr_write\"}",
"legendFormat": "ccr write",
"refId": "F"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"cir_write\"}",
"legendFormat": "cir write",
"refId": "G"
}
],
"title": "Output Stage Durations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"decimals": 2
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 24,
"w": 12,
"h": 8
},
"id": 14,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_max_rss_bytes",
"legendFormat": "Max RSS",
"refId": "A"
}
],
"title": "Max RSS Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 2,
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 90
},
{
"color": "green",
"value": 98
}
]
}
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 24,
"w": 12,
"h": 8
},
"id": 17,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
"legendFormat": "PP cache hit ratio",
"refId": "A"
}
],
"title": "PP Cache Hit Ratio",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"decimals": 2
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 32,
"w": 24,
"h": 8
},
"id": 15,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_state_db_size_bytes",
"legendFormat": "{{db}}",
"refId": "A"
}
],
"title": "State DB Size Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 40,
"w": 24,
"h": 8
},
"id": 16,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_state_db_files",
"legendFormat": "{{db}}",
"refId": "A"
}
],
"title": "State DB File Count Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0,
"min": 0
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 48,
"w": 24,
"h": 8
},
"id": 18,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"fresh\"})",
"legendFormat": "fresh pp",
"refId": "A"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"roa\"})",
"legendFormat": "fresh roa",
"refId": "B"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"manifest\"})",
"legendFormat": "fresh mft",
"refId": "C"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"certificate\"})",
"legendFormat": "fresh crt",
"refId": "D"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"crl\"})",
"legendFormat": "fresh crl",
"refId": "E"
}
],
"title": "Fresh PP / Object Counts by Run",
"type": "timeseries"
}
],
"refresh": "5s",
"schemaVersion": 40,
"tags": [
"ours-rp",
"rpki",
"soak"
],
"templating": {
"list": []
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Ours RP Soak Overview",
"uid": "ours-rp-soak-overview",
"version": 4,
"weekStart": ""
}

View File

@@ -1,12 +0,0 @@
apiVersion: 1
providers:
- name: ours-rp-arm64
orgId: 1
folder: Ours RP ARM64
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards

View File

@@ -1,10 +0,0 @@
apiVersion: 1
datasources:
- name: Prometheus
uid: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true

View File

@@ -1,13 +0,0 @@
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_configs:
- job_name: ours-rp-artifact-metrics
metrics_path: /metrics
static_configs:
- targets:
- artifact-metrics:9556
labels:
rp: ours-rp
source: arm64-compose-artifact-sidecar

View File

@@ -1,77 +0,0 @@
# ours RP ARM64 installer configuration
# 中文说明见 docs/README.zh-CN.md。English guide: docs/README.en.md
# Compose project name.
COMPOSE_PROJECT_NAME=ours-rp-arm64
# Runtime image loaded from images/*.tar.gz by install.sh.
RPKI_IMAGE=ours-rp-runtime-arm64:dev
RPKI_PLATFORM=linux/arm64
# Restart policy for the soak container. Production default keeps the daemon alive.
# For finite acceptance tests such as MAX_RUNS=3, set SOAK_RESTART_POLICY=no to avoid an extra restarted run.
SOAK_RESTART_POLICY=unless-stopped
# Host-side persistent data directory. All state/runs/logs/monitoring data are bind-mounted here.
HOST_DATA_DIR=/var/lib/ours-rp-arm64
# RIR list. Options: afrinic,apnic,arin,lacnic,ripe
RIRS=afrinic,apnic,arin,lacnic,ripe
# Negative MAX_RUNS means keep running forever. Default production interval is 10 minutes.
MAX_RUNS=-1
INTERVAL_SECS=600
RETAIN_RUNS=100
# TAL/TA input mode:
# file-with-ta: use packaged fixture TAL + TA only.
# file-live-ta: use packaged fixture TAL; snapshot waits for live TA refresh, delta refreshes TA in background.
# url: pass TAL URL to child process.
TAL_INPUT_MODE=file-live-ta
LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1
LIVE_TA_REFRESH_CONNECT_TIMEOUT_SECS=15
LIVE_TA_REFRESH_MAX_TIME_SECS=120
# Sync and runtime behavior.
RSYNC_SCOPE=module-root
DISABLE_COMPETING_RPS=0
RUN_ROOT=/var/lib/ours-rp
DB_DIR=/var/lib/ours-rp/state/db
RSYNC_MIRROR_ROOT=/var/lib/ours-rp/state/rsync-mirror
CLEAN_TMP_AFTER_RUN=0
OUTPUT_COMPACT_REPORT=1
ALLOW_RSYNC_MIRROR_REUSE=1
FAILURE_SNAPSHOT_RESET=1
DB_STATS_EXACT_EVERY=0
# Validation and performance options aligned with current optimized soak defaults.
ENABLE_CHILD_CERTIFICATE_VALIDATION_CACHE=1
RPKI_ANALYZE=1
RPKI_EXTRA_ARGS="--enable-transport-request-prefetch --enable-publication-point-validation-cache --enable-roa-validation-cache --parallel-max-repo-sync-workers-global 4 --parallel-phase2-object-workers 4 --memory-trim-after-validation"
# Progress logs.
RPKI_PROGRESS_LOG=1
RPKI_PROGRESS_SLOW_SECS=20
RPKI_PROGRESS_STAGE_FRESH_SLOW_MS=2000
RPKI_PROGRESS_PP_CONTROL_SLOW_MS=200
RPKI_PROGRESS_PP_CACHE_SLOW_MS=100
RPKI_PROGRESS_CONTROL_LOOP_SLOW_MS=2000
# Metrics sidecar.
METRICS_INSTANCE=arm64-installer
METRICS_PORT=9556
METRICS_POLL_SECS=10
# Prometheus / Grafana.
# Monitor images are packaged as ARM64 docker-save archives and loaded by install.sh.
MONITOR_PLATFORM=linux/arm64
PROMETHEUS_IMAGE=prom/prometheus:v2.55.1
GRAFANA_IMAGE=grafana/grafana:11.3.1
PROMETHEUS_PORT=9090
PROMETHEUS_RETENTION=7d
GRAFANA_PORT=3000
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin
# First snapshot waiting timeout used by start.sh.
FIRST_RUN_WAIT_TIMEOUT_SECS=7200

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
DRY_RUN=1
KEEP_RUNS=""
usage() {
cat <<'USAGE'
Usage: ./cleanup.sh [--execute] [--keep-runs N]
By default this is a dry-run. It removes old run_* directories beyond KEEP_RUNS
and clears tmp contents.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--execute)
DRY_RUN=0
shift
;;
--keep-runs)
KEEP_RUNS="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
load_env
keep="${KEEP_RUNS:-${RETAIN_RUNS:-100}}"
mapfile -t runs < <(find "$HOST_DATA_DIR/runs" -maxdepth 1 -type d -name 'run_*' 2>/dev/null | sort)
delete_count=$(( ${#runs[@]} - keep ))
if (( delete_count > 0 )); then
for ((i=0; i<delete_count; i++)); do
if [[ "$DRY_RUN" == "1" ]]; then
echo "DRY-RUN rm -rf ${runs[$i]}"
else
rm -rf "${runs[$i]}"
fi
done
fi
if [[ "$DRY_RUN" == "1" ]]; then
echo "DRY-RUN rm -rf $HOST_DATA_DIR/tmp/*"
else
find "$HOST_DATA_DIR/tmp" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
fi
df -h "$HOST_DATA_DIR" 2>/dev/null || true

View File

@@ -1,90 +0,0 @@
services:
ours-rp-soak:
image: ${RPKI_IMAGE:-ours-rp-runtime-arm64:dev}
platform: ${RPKI_PLATFORM:-linux/arm64}
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-arm64}-soak
env_file:
- ../.env
environment:
PACKAGE_ROOT: /opt/ours-rp
ENV_FILE: /opt/ours-rp/.env
RUN_ROOT: /var/lib/ours-rp
BIN_DIR: /opt/ours-rp/bin
FIXTURE_DIR: /opt/ours-rp/fixtures
volumes:
- ../.env:/opt/ours-rp/.env:ro
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/state:/var/lib/ours-rp/state
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/runs:/var/lib/ours-rp/runs
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/logs:/var/lib/ours-rp/logs
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/tmp:/var/lib/ours-rp/tmp
restart: ${SOAK_RESTART_POLICY:-unless-stopped}
profiles:
- core
artifact-metrics:
image: ${RPKI_IMAGE:-ours-rp-runtime-arm64:dev}
platform: ${RPKI_PLATFORM:-linux/arm64}
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-arm64}-artifact-metrics
env_file:
- ../.env
command:
- /opt/ours-rp/bin/rpki_artifact_metrics
- --run-root
- /var/lib/ours-rp
- --listen
- 0.0.0.0:9556
- --poll-secs
- ${METRICS_POLL_SECS:-10}
- --instance
- ${METRICS_INSTANCE:-arm64-installer}
ports:
- "${METRICS_PORT:-9556}:9556"
volumes:
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/state:/var/lib/ours-rp/state:ro
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/runs:/var/lib/ours-rp/runs:ro
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/logs:/var/lib/ours-rp/logs:ro
restart: unless-stopped
profiles:
- sidecar
prometheus:
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}
platform: ${MONITOR_PLATFORM:-linux/arm64}
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-arm64}-prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-7d}
- --web.enable-lifecycle
depends_on:
- artifact-metrics
user: "0:0"
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/prometheus:/prometheus
restart: unless-stopped
profiles:
- monitor
grafana:
image: ${GRAFANA_IMAGE:-grafana/grafana:11.3.1}
platform: ${MONITOR_PLATFORM:-linux/arm64}
container_name: ${COMPOSE_PROJECT_NAME:-ours-rp-arm64}-grafana
depends_on:
- prometheus
user: "0:0"
ports:
- "${GRAFANA_PORT:-3000}:3000"
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- ${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}/grafana:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
restart: unless-stopped
profiles:
- monitor

View File

@@ -1,761 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 0
},
"id": 1,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_publication_points",
"legendFormat": "publication points",
"refId": "A"
}
],
"title": "Publication Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_ok\"}",
"legendFormat": "rrdp ok",
"refId": "A"
}
],
"title": "RRDP OK Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 0
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "sum(ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}) or vector(0)",
"legendFormat": "fallback",
"refId": "A"
}
],
"title": "Rsync Fallback Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 0
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
"legendFormat": "failed no cache",
"refId": "A"
}
],
"title": "Failed No Cache Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 4
},
"id": 5,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"repo_sync_total\"}",
"legendFormat": "repo sync total",
"refId": "A"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rrdp_download_total\"}",
"legendFormat": "rrdp download",
"refId": "B"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"rsync_download_total\"}",
"legendFormat": "rsync download",
"refId": "C"
}
],
"title": "Repo Sync Download Durations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 12,
"w": 12,
"h": 8
},
"id": 6,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_repo_sync_phase_count",
"legendFormat": "{{phase}}",
"refId": "A"
}
],
"title": "Repo Sync Phase Counts",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "short"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 12,
"w": 12,
"h": 8
},
"id": 7,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_ok\"}",
"legendFormat": "rrdp failed, rsync ok",
"refId": "A"
},
{
"expr": "ours_rp_repo_sync_phase_count{phase=\"rrdp_failed_rsync_failed\"}",
"legendFormat": "rrdp failed, rsync failed",
"refId": "B"
},
{
"expr": "ours_rp_repo_terminal_state_count{terminal_state=\"failed_no_cache\"}",
"legendFormat": "failed no cache",
"refId": "C"
},
{
"expr": "ours_rp_tree_instances{state=\"failed\"}",
"legendFormat": "tree failed",
"refId": "D"
}
],
"title": "Repo Failure / Fallback Counts",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 20,
"w": 12,
"h": 8
},
"id": 8,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_ok\"}",
"legendFormat": "rsync fallback duration",
"refId": "A"
},
{
"expr": "ours_rp_repo_sync_phase_duration_seconds_total{phase=\"rrdp_failed_rsync_failed\"}",
"legendFormat": "failed duration",
"refId": "B"
},
{
"expr": "ours_rp_repo_terminal_state_duration_seconds_total{terminal_state=\"failed_no_cache\"}",
"legendFormat": "failed no cache duration",
"refId": "C"
}
],
"title": "Repo Failure / Fallback Durations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 29,
"w": 12,
"h": 9
},
"id": 9,
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
}
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_rrdp_rsync_failed_repository_duration_seconds",
"format": "table",
"instant": true,
"legendFormat": "",
"refId": "A"
}
],
"title": "RRDP + Rsync Failed Repositories",
"type": "table",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"job": true,
"terminal_state": true,
"rank": true,
"transport": true,
"__name__": true,
"publication_points": true,
"instance": true,
"repo_id": true,
"pp_id": true,
"exported_instance": true,
"rp": true,
"source": true
},
"indexByName": {
"Time": 0,
"host": 1,
"phase": 2,
"uri": 3,
"Value": 4
},
"renameByName": {
"Value": "duration"
}
}
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 29,
"w": 12,
"h": 9
},
"id": 11,
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
}
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "topk(20, ours_rp_top_repository_sync_duration_seconds)",
"format": "table",
"instant": true,
"legendFormat": "",
"refId": "A"
}
],
"title": "Top 20 Repositories by Sync Duration",
"type": "table",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"job": true,
"terminal_state": true,
"__name__": true,
"publication_points": true,
"instance": true,
"repo_id": true,
"phase": true,
"pp_id": true,
"exported_instance": true,
"rp": true,
"source": true
},
"indexByName": {
"Time": 0,
"host": 1,
"rank": 2,
"transport": 3,
"uri": 4,
"Value": 5
},
"renameByName": {
"Value": "value"
}
}
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 38,
"w": 24,
"h": 9
},
"id": 10,
"options": {
"showHeader": true,
"cellHeight": "sm",
"footer": {
"show": false,
"reducer": [
"sum"
],
"countRows": false,
"fields": ""
}
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "topk(20, ours_rp_top_publication_point_object_count)",
"format": "table",
"instant": true,
"legendFormat": "",
"refId": "A"
}
],
"title": "Top Publication Points by Objects",
"type": "table",
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"job": true,
"__name__": true,
"publication_points": true,
"instance": true,
"repo_id": true,
"phase": true,
"pp_id": true,
"exported_instance": true,
"rp": true,
"source": true
},
"indexByName": {
"Time": 0,
"host": 1,
"rank": 2,
"terminal_state": 3,
"transport": 4,
"uri": 5,
"Value": 6
},
"renameByName": {}
}
}
]
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"description": "Per-repository sync success in the latest successful run; 1 means successful, 0 means failed or failed_no_cache.",
"fieldConfig": {
"defaults": {
"unit": "bool"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 47
},
"id": 12,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"expr": "ours_rp_repository_sync_success",
"legendFormat": "{{host}} {{repo_id}}",
"refId": "A"
}
],
"title": "Repository Sync Success by Repo",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"description": "Per-repository total sync duration aggregated from publication point repo_sync_duration_ms.",
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 55
},
"id": 13,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"expr": "ours_rp_repository_sync_duration_seconds{stat=\"sum\"}",
"legendFormat": "{{host}} {{repo_id}}",
"refId": "A"
}
],
"title": "Repository Sync Duration by Repo",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"description": "Per-repository downloaded bytes attributed from report.json downloads events.",
"fieldConfig": {
"defaults": {
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 63
},
"id": 14,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "desc"
}
},
"targets": [
{
"expr": "ours_rp_repository_download_bytes",
"legendFormat": "{{host}} {{repo_id}}",
"refId": "A"
}
],
"title": "Repository Download Bytes by Repo",
"type": "timeseries"
}
],
"refresh": "5s",
"schemaVersion": 40,
"tags": [
"ours-rp",
"rpki",
"soak",
"repo-sync"
],
"templating": {
"list": []
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Ours RP Repo Sync",
"uid": "ours-rp-repo-sync",
"version": 3,
"weekStart": ""
}

View File

@@ -1,875 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"panels": [
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 0,
"w": 6,
"h": 4
},
"id": 1,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_cir_trust_anchors",
"legendFormat": "RIRs",
"refId": "A"
}
],
"title": "Current Run RIRs",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 6,
"y": 0,
"w": 6,
"h": 4
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_run_duration_seconds",
"legendFormat": "wall",
"refId": "A"
}
],
"title": "Latest Wall Time",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "bytes"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 0,
"w": 6,
"h": 4
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_run_max_rss_bytes",
"legendFormat": "rss",
"refId": "A"
}
],
"title": "Latest Max RSS",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 18,
"y": 0,
"w": 6,
"h": 4
},
"id": 4,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_publication_points",
"legendFormat": "publication points",
"refId": "A"
}
],
"title": "Publication Points",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 4,
"w": 6,
"h": 4
},
"id": 9,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_run_sequence",
"legendFormat": "seq",
"refId": "A"
}
],
"title": "Latest Run Sequence",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 2,
"unit": "percent",
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 90
},
{
"color": "green",
"value": 98
}
]
}
},
"overrides": []
},
"gridPos": {
"x": 6,
"y": 4,
"w": 6,
"h": 4
},
"id": 10,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
"legendFormat": "PP cache hit ratio",
"refId": "A"
}
],
"title": "Latest PP Cache Hit Ratio",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 4,
"w": 6,
"h": 4
},
"id": 11,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_vrps{kind=\"total\"}",
"legendFormat": "VRPs raw",
"refId": "A"
}
],
"title": "VRPs",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"gridPos": {
"x": 18,
"y": 4,
"w": 6,
"h": 4
},
"id": 12,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"pluginVersion": "11.3.1",
"targets": [
{
"expr": "ours_rp_vaps",
"legendFormat": "VAPs",
"refId": "A"
}
],
"title": "VAPs",
"type": "stat"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 8,
"w": 12,
"h": 8
},
"id": 5,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_duration_seconds",
"legendFormat": "wall",
"refId": "A"
},
{
"expr": "ours_rp_stage_duration_seconds{stage=\"validation\"}",
"legendFormat": "validation",
"refId": "B"
}
],
"title": "Run / Validation Duration",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 8,
"w": 12,
"h": 8
},
"id": 6,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_vrps{kind=\"total\"}",
"legendFormat": "VRPs raw",
"refId": "A"
},
{
"expr": "ours_rp_vrps{kind=\"unique\"}",
"legendFormat": "VRPs unique",
"refId": "D"
},
{
"expr": "ours_rp_vaps",
"legendFormat": "VAPs",
"refId": "B"
},
{
"expr": "ours_rp_cir_objects",
"legendFormat": "CIR objects",
"refId": "C"
}
],
"title": "Output and Input Sizes",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"decimals": 0,
"unit": "none"
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 16,
"w": 12,
"h": 8
},
"id": 8,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_large_publication_points",
"legendFormat": "> {{object_count_gt}} objects",
"refId": "A"
}
],
"title": "Large Publication Points by Object Count",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "s"
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 16,
"w": 12,
"h": 8
},
"id": 13,
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"validation\"}",
"legendFormat": "validation",
"refId": "A"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"report_write\"}",
"legendFormat": "report write",
"refId": "E"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"ccr_write\"}",
"legendFormat": "ccr write",
"refId": "F"
},
{
"expr": "ours_rp_run_stage_duration_seconds{stage=\"cir_write\"}",
"legendFormat": "cir write",
"refId": "G"
}
],
"title": "Output Stage Durations",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"decimals": 2
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 24,
"w": 12,
"h": 8
},
"id": 14,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_run_max_rss_bytes",
"legendFormat": "Max RSS",
"refId": "A"
}
],
"title": "Max RSS Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "percent",
"decimals": 2,
"min": 0,
"max": 100,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "orange",
"value": 90
},
{
"color": "green",
"value": 98
}
]
}
},
"overrides": []
},
"gridPos": {
"x": 12,
"y": 24,
"w": 12,
"h": 8
},
"id": 17,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"min",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "100 * sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"publication_point_cache\"}) / sum by (job, instance, exported_instance) (ours_rp_publication_points)",
"legendFormat": "PP cache hit ratio",
"refId": "A"
}
],
"title": "PP Cache Hit Ratio",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"decimals": 2
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 32,
"w": 24,
"h": 8
},
"id": 15,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_state_db_size_bytes",
"legendFormat": "{{db}}",
"refId": "A"
}
],
"title": "State DB Size Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 40,
"w": 24,
"h": 8
},
"id": 16,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "ours_rp_state_db_files",
"legendFormat": "{{db}}",
"refId": "A"
}
],
"title": "State DB File Count Over Time",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0,
"min": 0
},
"overrides": []
},
"gridPos": {
"x": 0,
"y": 48,
"w": 24,
"h": 8
},
"id": 18,
"options": {
"legend": {
"calcs": [
"lastNotNull",
"max"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_repo_terminal_state_count{terminal_state=\"fresh\"})",
"legendFormat": "fresh pp",
"refId": "A"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"roa\"})",
"legendFormat": "fresh roa",
"refId": "B"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"manifest\"})",
"legendFormat": "fresh mft",
"refId": "C"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"certificate\"})",
"legendFormat": "fresh crt",
"refId": "D"
},
{
"expr": "sum by (job, instance, exported_instance) (ours_rp_cir_objects_by_source_type{exported_source=\"fresh\",object_type=\"crl\"})",
"legendFormat": "fresh crl",
"refId": "E"
}
],
"title": "Fresh PP / Object Counts by Run",
"type": "timeseries"
}
],
"refresh": "5s",
"schemaVersion": 40,
"tags": [
"ours-rp",
"rpki",
"soak"
],
"templating": {
"list": []
},
"time": {
"from": "now-30m",
"to": "now"
},
"timepicker": {},
"timezone": "browser",
"title": "Ours RP Soak Overview",
"uid": "ours-rp-soak-overview",
"version": 4,
"weekStart": ""
}

View File

@@ -1,12 +0,0 @@
apiVersion: 1
providers:
- name: ours-rp-arm64
orgId: 1
folder: Ours RP ARM64
type: file
disableDeletion: false
updateIntervalSeconds: 10
allowUiUpdates: true
options:
path: /var/lib/grafana/dashboards

View File

@@ -1,10 +0,0 @@
apiVersion: 1
datasources:
- name: Prometheus
uid: Prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: true

View File

@@ -1,13 +0,0 @@
global:
scrape_interval: 5s
evaluation_interval: 5s
scrape_configs:
- job_name: ours-rp-artifact-metrics
metrics_path: /metrics
static_configs:
- targets:
- artifact-metrics:9556
labels:
rp: ours-rp
source: arm64-compose-artifact-sidecar

View File

@@ -1,95 +0,0 @@
# ours RP ARM64 Installer Guide
## Goal
This package deploys ours RP on a `linux/arm64` server with Docker Compose and continuously runs all five RIR validation.
The package includes the ours RP ARM64 runtime image, Prometheus ARM64 image and Grafana ARM64 image, so deployment does not pull application images on the target host. Runtime state, run artifacts, logs, Prometheus data and Grafana data are persisted through host bind mounts.
## Quick Start
```bash
tar -xzf ours-rp-arm64-installer-*.tar.gz
cd ours-rp-arm64-installer-*
./install.sh
cp .env.example .env # install.sh creates .env automatically if missing
vim .env
./start.sh
./status.sh
```
Defaults:
- `RIRS=afrinic,apnic,arin,lacnic,ripe`
- `MAX_RUNS=-1`
- `INTERVAL_SECS=600`
- `TAL_INPUT_MODE=file-live-ta`
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
- `HOST_DATA_DIR=/var/lib/ours-rp-arm64`
- `SOAK_RESTART_POLICY=unless-stopped`
- `MONITOR_PLATFORM=linux/arm64`
## First Start Semantics
If there is no successful run under `HOST_DATA_DIR/runs`, `start.sh` starts the core `ours-rp-soak` service first and waits for the first snapshot to succeed before starting metrics, Prometheus and Grafana.
The first snapshot refreshes live TA certificates before starting the RP process.
## Ports
Default ports:
- metrics: `http://<host>:9556/metrics`
- Prometheus: `http://<host>:9090`
- Grafana: `http://<host>:3000`
Grafana credentials come from `.env`:
```bash
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin
```
Change the password and restrict public access for production deployments.
## Data Directory
Default host directory:
```text
/var/lib/ours-rp-arm64/
state/
runs/
logs/
tmp/
prometheus/
grafana/
```
Each `runs/run_XXXX/` directory contains `report.json`, `result.ccr`, `input.cir`, `vrps.csv`, `vaps.csv`, `stage-timing.json`, logs and metadata.
## Common Commands
```bash
./status.sh
./logs.sh ours-rp-soak --tail 200
./restart.sh
./stop.sh
./cleanup.sh --keep-runs 100 --execute
./uninstall.sh
```
`uninstall.sh` keeps data by default. Use the following only when you really want to delete `HOST_DATA_DIR`:
```bash
./uninstall.sh --purge-data
```
For finite acceptance tests, for example `MAX_RUNS=3`, also set:
```bash
SOAK_RESTART_POLICY=no
```
Otherwise Compose `unless-stopped` will restart the container after it exits successfully.

View File

@@ -1,97 +0,0 @@
# ours RP ARM64 安装包使用说明
## 目标
本安装包用于在 `linux/arm64` 服务器上通过 Docker Compose 部署 ours RP,并持续运行 all5 RIR 同步验证任务。
安装包内置 ours RP ARM64 runtime、Prometheus ARM64、Grafana ARM64 镜像,部署时不需要现场拉取应用镜像。运行产物、状态数据库、日志、Prometheus 和 Grafana 数据均通过宿主机目录挂载保存。
## 快速开始
```bash
tar -xzf ours-rp-arm64-installer-*.tar.gz
cd ours-rp-arm64-installer-*
./install.sh
cp .env.example .env # 如 install.sh 已自动创建,可直接编辑现有 .env
vim .env
./start.sh
./status.sh
```
默认配置:
- `RIRS=afrinic,apnic,arin,lacnic,ripe`
- `MAX_RUNS=-1`
- `INTERVAL_SECS=600`
- `TAL_INPUT_MODE=file-live-ta`
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
- `HOST_DATA_DIR=/var/lib/ours-rp-arm64`
- `SOAK_RESTART_POLICY=unless-stopped`
- `MONITOR_PLATFORM=linux/arm64`
## 首次启动语义
如果 `HOST_DATA_DIR/runs` 下没有成功 run,`start.sh` 会先启动核心 `ours-rp-soak`,等待第一轮 snapshot 成功后再启动 metrics、Prometheus 和 Grafana。
第一轮 snapshot 会先拉取 live TA,避免 clean state 使用旧 fixture TA。
## 访问端口
默认端口:
- metrics: `http://<host>:9556/metrics`
- Prometheus: `http://<host>:9090`
- Grafana: `http://<host>:3000`
Grafana 默认账号密码来自 `.env`:
```bash
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin
```
生产部署时应修改密码并限制外部访问。
## 数据目录
默认宿主机目录:
```text
/var/lib/ours-rp-arm64/
state/
runs/
logs/
tmp/
prometheus/
grafana/
```
`runs/run_XXXX/` 中包含每轮 `report.json`、`result.ccr`、`input.cir`、`vrps.csv`、`vaps.csv`、`stage-timing.json`、日志和元数据。
## 常用命令
```bash
./status.sh
./logs.sh ours-rp-soak --tail 200
./restart.sh
./stop.sh
./cleanup.sh --keep-runs 100 --execute
./uninstall.sh
```
如果做有限轮次验收,例如 `MAX_RUNS=3`,建议同时设置:
```bash
SOAK_RESTART_POLICY=no
```
否则 Compose 的 `unless-stopped` 策略会在容器正常退出后再次拉起下一轮。
`uninstall.sh` 默认不删除数据。只有显式执行:
```bash
./uninstall.sh --purge-data
```
才会删除 `HOST_DATA_DIR`。

View File

@@ -1,68 +0,0 @@
# Operations Guide
## Install
```bash
./install.sh
```
The installer is idempotent:
- existing `.env` is kept;
- existing Docker/Compose installation is reused;
- repeated loading of packaged ours RP, Prometheus and Grafana ARM64 images is safe;
- existing data directory is reused.
## Start
```bash
./start.sh
```
Start without waiting for the first snapshot:
```bash
./start.sh --no-wait-first-run
```
## Stop and Restart
```bash
./stop.sh
./restart.sh
```
## Status Checks
```bash
./status.sh
./self-check.sh
```
Important checks:
- Docker/Compose availability;
- runtime, Prometheus and Grafana images exist;
- `HOST_DATA_DIR` is writable;
- Compose config is valid;
- latest run status;
- metrics, Prometheus and Grafana endpoints.
## Upgrade
Extract the new package, reuse the existing `.env` and `HOST_DATA_DIR`, then run:
```bash
./upgrade.sh
```
Upgrade does not delete runtime data.
## Cleanup
```bash
./cleanup.sh --keep-runs 100
./cleanup.sh --keep-runs 100 --execute
```
Cleanup is dry-run by default. Add `--execute` after reviewing the output.

View File

@@ -1,68 +0,0 @@
# 运维手册
## 安装
```bash
./install.sh
```
安装脚本是幂等的:
- 已有 `.env` 不覆盖;
- 已安装 Docker/Compose 则跳过;
- 包内 ours RP、Prometheus、Grafana ARM64 镜像重复加载是安全的;
- 数据目录已存在则复用。
## 启动
```bash
./start.sh
```
如需后台启动后不等待首轮 snapshot:
```bash
./start.sh --no-wait-first-run
```
## 停止和重启
```bash
./stop.sh
./restart.sh
```
## 状态检查
```bash
./status.sh
./self-check.sh
```
重点检查项:
- Docker/Compose 可用;
- runtime、Prometheus、Grafana 镜像存在;
- `HOST_DATA_DIR` 可写;
- Compose 配置合法;
- 最新 run 状态;
- metrics、Prometheus、Grafana endpoint。
## 升级
把新安装包解压到新目录后,可以复用旧 `.env` 和 `HOST_DATA_DIR`。执行:
```bash
./upgrade.sh
```
升级不会删除运行数据。
## 清理
```bash
./cleanup.sh --keep-runs 100
./cleanup.sh --keep-runs 100 --execute
```
默认 dry-run,确认后加 `--execute`。

View File

@@ -1,68 +0,0 @@
# Troubleshooting
## Docker or Compose Is Unavailable
Run:
```bash
docker version
docker compose version
```
If missing, run:
```bash
./install.sh
```
## ARM64 Image Cannot Run
Running ARM64 images on x86_64 requires binfmt/qemu:
```bash
docker run --rm --privileged tonistiigi/binfmt --install arm64
docker run --rm --platform linux/arm64 debian:bookworm-slim uname -m
```
Expected output: `aarch64`.
## First Snapshot Times Out
All-five snapshot can be slow, especially under QEMU. Increase timeout:
```bash
./start.sh --timeout-secs 14400
```
## Output Counts Are Too Low
Check:
```bash
grep LIVE_TA_REFRESH_BEFORE_SNAPSHOT .env
ls -l /var/lib/ours-rp-arm64/state/live-ta
tail -100 /var/lib/ours-rp-arm64/logs/live-ta-refresh-*.log
```
In `file-live-ta` mode, snapshot should wait until live TA refresh succeeds.
## Grafana Login Fails
Check `.env`:
```bash
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin
```
If Grafana has already started, changing `.env` may not reset the existing Grafana database. Stop services and back up/clean `${HOST_DATA_DIR}/grafana` if needed.
## A Finite Acceptance Test Starts an Extra Run
If `.env` sets a finite `MAX_RUNS=3` while `SOAK_RESTART_POLICY=unless-stopped`, Docker Compose restarts the soak container after it exits successfully.
For finite tests, set:
```bash
SOAK_RESTART_POLICY=no
```

View File

@@ -1,68 +0,0 @@
# 故障排查
## Docker 或 Compose 不可用
执行:
```bash
docker version
docker compose version
```
如果缺失,重新执行:
```bash
./install.sh
```
## ARM64 镜像无法运行
在 x86_64 机器上运行 ARM64 镜像需要 binfmt/qemu:
```bash
docker run --rm --privileged tonistiigi/binfmt --install arm64
docker run --rm --platform linux/arm64 debian:bookworm-slim uname -m
```
预期输出 `aarch64`。
## 首轮 snapshot 超时
all5 snapshot 可能很慢,尤其在 QEMU 环境。可以提高超时:
```bash
./start.sh --timeout-secs 14400
```
## 产物数量异常偏低
检查:
```bash
grep LIVE_TA_REFRESH_BEFORE_SNAPSHOT .env
ls -l /var/lib/ours-rp-arm64/state/live-ta
tail -100 /var/lib/ours-rp-arm64/logs/live-ta-refresh-*.log
```
`file-live-ta` 模式下,snapshot 应等待 live TA 成功刷新。
## Grafana 无法登录
确认 `.env` 中:
```bash
GRAFANA_ADMIN_USER=admin
GRAFANA_ADMIN_PASSWORD=admin
```
如果曾经启动过 Grafana,修改 `.env` 不一定重置已有 Grafana 数据库账号。可以停止服务后按需备份并清理 `${HOST_DATA_DIR}/grafana`。
## 有限轮次验收后又多跑了一轮
如果 `.env` 中设置了 `MAX_RUNS=3` 这类有限轮次,同时 `SOAK_RESTART_POLICY=unless-stopped`,Docker Compose 会在 soak 容器正常退出后重新启动容器。
有限验收建议设置:
```bash
SOAK_RESTART_POLICY=no
```

View File

@@ -1,45 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
usage() {
cat <<'USAGE'
Usage: ./install.sh [--skip-dep-install]
Install or update the ours RP ARM64 compose package idempotently.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--skip-dep-install)
export SKIP_DEP_INSTALL=1
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
load_env
install_docker_if_missing
require_cmd curl
require_cmd jq
require_cmd rsync
require_cmd gzip
require_cmd tar
create_data_dirs
load_installer_images
ensure_binfmt_if_needed
verify_runtime_image
verify_monitor_images
compose_cmd --profile core --profile sidecar --profile monitor config >/tmp/ours-rp-arm64-compose-config.yml
"$SCRIPT_DIR/self-check.sh" --quick
log "install complete"

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
load_env
compose_cmd --profile core --profile sidecar --profile monitor logs "$@"

View File

@@ -1,5 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"$SCRIPT_DIR/stop.sh" || true
"$SCRIPT_DIR/start.sh" "$@"

View File

@@ -1,265 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
INSTALLER_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ENV_FILE="${ENV_FILE:-$INSTALLER_ROOT/.env}"
ENV_EXAMPLE="$INSTALLER_ROOT/.env.example"
COMPOSE_FILE="$INSTALLER_ROOT/compose/docker-compose.yml"
log() {
printf '[ours-rp-installer] %s\n' "$*"
}
warn() {
printf '[ours-rp-installer][WARN] %s\n' "$*" >&2
}
die() {
printf '[ours-rp-installer][ERROR] %s\n' "$*" >&2
exit 1
}
require_cmd() {
command -v "$1" >/dev/null 2>&1 || die "missing command: $1"
}
load_env() {
if [[ ! -f "$ENV_FILE" ]]; then
[[ -f "$ENV_EXAMPLE" ]] || die "missing $ENV_EXAMPLE"
cp "$ENV_EXAMPLE" "$ENV_FILE"
log "created .env from .env.example"
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
HOST_DATA_DIR="${HOST_DATA_DIR:-/var/lib/ours-rp-arm64}"
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-ours-rp-arm64}"
RPKI_IMAGE="${RPKI_IMAGE:-ours-rp-runtime-arm64:dev}"
RPKI_PLATFORM="${RPKI_PLATFORM:-linux/arm64}"
MONITOR_PLATFORM="${MONITOR_PLATFORM:-linux/arm64}"
PROMETHEUS_IMAGE="${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}"
GRAFANA_IMAGE="${GRAFANA_IMAGE:-grafana/grafana:11.3.1}"
FIRST_RUN_WAIT_TIMEOUT_SECS="${FIRST_RUN_WAIT_TIMEOUT_SECS:-7200}"
}
compose_cmd() {
docker compose --env-file "$ENV_FILE" -f "$COMPOSE_FILE" -p "${COMPOSE_PROJECT_NAME:-ours-rp-arm64}" "$@"
}
create_data_dirs() {
load_env
mkdir -p \
"$HOST_DATA_DIR/state" \
"$HOST_DATA_DIR/runs" \
"$HOST_DATA_DIR/logs" \
"$HOST_DATA_DIR/tmp" \
"$HOST_DATA_DIR/prometheus" \
"$HOST_DATA_DIR/grafana"
chmod 755 "$HOST_DATA_DIR" "$HOST_DATA_DIR/state" "$HOST_DATA_DIR/runs" "$HOST_DATA_DIR/logs" "$HOST_DATA_DIR/tmp" || true
chmod 777 "$HOST_DATA_DIR/prometheus" "$HOST_DATA_DIR/grafana" || true
}
latest_run_dir() {
load_env
find "$HOST_DATA_DIR/runs" -maxdepth 1 -mindepth 1 -type d -name 'run_*' 2>/dev/null | sort | tail -1
}
latest_success_run_dir() {
load_env
find "$HOST_DATA_DIR/runs" -maxdepth 2 -type f -path '*/run-summary.json' 2>/dev/null \
| while read -r summary; do
if jq -e '.status == "success"' "$summary" >/dev/null 2>&1; then
dirname "$summary"
fi
done | sort | tail -1
}
has_success_run() {
[[ -n "$(latest_success_run_dir)" ]]
}
print_run_summary() {
local run_dir="$1"
local summary="$run_dir/run-summary.json"
local meta="$run_dir/run-meta.json"
local timing="$run_dir/stage-timing.json"
local process_time="$run_dir/process-time.txt"
local vrps_file="$run_dir/vrps.csv"
local vaps_file="$run_dir/vaps.csv"
local status="unknown"
local sync_mode="unknown"
local wall_ms="null"
local validation_ms="null"
local repo_sync_ms="null"
local max_rss_kb="null"
local publication_points="null"
local vrps="null"
local vaps="null"
local warnings="null"
[[ -f "$summary" ]] || {
warn "missing run-summary.json in $run_dir"
return 1
}
status="$(jq -r '.status // "unknown"' "$summary" 2>/dev/null || echo unknown)"
wall_ms="$(jq -r '.wallMs // .wall_ms // "null"' "$summary" 2>/dev/null || echo null)"
warnings="$(jq -r '.warningCount // .warnings // "null"' "$summary" 2>/dev/null || echo null)"
if [[ -f "$meta" ]]; then
sync_mode="$(jq -r '.sync_mode // .syncMode // "unknown"' "$meta" 2>/dev/null || echo unknown)"
status="$(jq -r --arg fallback "$status" '.status // $fallback' "$meta" 2>/dev/null || echo "$status")"
fi
if [[ -f "$timing" ]]; then
validation_ms="$(jq -r '.validation_ms // "null"' "$timing" 2>/dev/null || echo null)"
repo_sync_ms="$(jq -r '.repo_sync_ms_total // "null"' "$timing" 2>/dev/null || echo null)"
publication_points="$(jq -r '.publication_points // "null"' "$timing" 2>/dev/null || echo null)"
fi
if [[ -f "$process_time" ]]; then
max_rss_kb="$(awk -F': ' '/Maximum resident set size/ {print $2; found=1} END {if (!found) print "null"}' "$process_time")"
fi
if [[ -f "$vrps_file" ]]; then
vrps="$(( $(wc -l < "$vrps_file") > 0 ? $(wc -l < "$vrps_file") - 1 : 0 ))"
fi
if [[ -f "$vaps_file" ]]; then
vaps="$(( $(wc -l < "$vaps_file") > 0 ? $(wc -l < "$vaps_file") - 1 : 0 ))"
fi
jq -n \
--arg run "$(basename "$run_dir")" \
--arg status "$status" \
--arg syncMode "$sync_mode" \
--argjson wallMs "$wall_ms" \
--argjson validationMs "$validation_ms" \
--argjson repoSyncMs "$repo_sync_ms" \
--argjson maxRssKb "$max_rss_kb" \
--argjson vrps "$vrps" \
--argjson vaps "$vaps" \
--argjson publicationPoints "$publication_points" \
--argjson warnings "$warnings" \
'{run:$run,status:$status,syncMode:$syncMode,wallMs:$wallMs,validationMs:$validationMs,repoSyncMs:$repoSyncMs,maxRssKb:$maxRssKb,vrps:$vrps,vaps:$vaps,publicationPoints:$publicationPoints,warnings:$warnings}'
}
wait_for_new_success_run() {
local before_latest="$1"
local timeout_secs="$2"
local start_epoch now run_dir summary meta status meta_status
start_epoch="$(date +%s)"
while true; do
run_dir="$(latest_run_dir || true)"
if [[ -n "$run_dir" && "$run_dir" != "$before_latest" ]]; then
summary="$run_dir/run-summary.json"
meta="$run_dir/run-meta.json"
if [[ -f "$summary" ]]; then
status="$(jq -r '.status // "unknown"' "$summary" 2>/dev/null || echo unknown)"
if [[ "$status" == "success" ]]; then
meta_status="unknown"
if [[ -f "$meta" ]]; then
meta_status="$(jq -r '.status // "unknown"' "$meta" 2>/dev/null || echo unknown)"
fi
if [[ "$meta_status" == "success" ]]; then
print_run_summary "$run_dir" || true
return 0
fi
fi
if [[ "$status" == "failed" || "$status" == "error" ]]; then
print_run_summary "$run_dir" || true
die "run failed: $run_dir"
fi
fi
fi
now="$(date +%s)"
if (( now - start_epoch > timeout_secs )); then
die "timed out waiting for first successful run after ${timeout_secs}s"
fi
sleep 10
done
}
docker_compose_available() {
docker compose version >/dev/null 2>&1
}
install_docker_if_missing() {
if command -v docker >/dev/null 2>&1 && docker_compose_available && command -v jq >/dev/null 2>&1 && command -v rsync >/dev/null 2>&1 && command -v curl >/dev/null 2>&1; then
log "docker and docker compose are already installed"
return 0
fi
if [[ "${SKIP_DEP_INSTALL:-0}" == "1" ]]; then
die "docker/docker compose missing and SKIP_DEP_INSTALL=1"
fi
if ! command -v apt-get >/dev/null 2>&1; then
die "docker/docker compose missing; automatic install currently supports apt-get only"
fi
log "installing missing runtime packages via apt"
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y ca-certificates curl jq rsync gzip tar docker.io
if ! docker_compose_available; then
if apt-cache show docker-compose-v2 >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-v2
elif apt-cache show docker-compose-plugin >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose-plugin
elif apt-cache show docker-compose >/dev/null 2>&1; then
DEBIAN_FRONTEND=noninteractive apt-get install -y docker-compose
fi
fi
systemctl enable --now docker >/dev/null 2>&1 || true
docker_compose_available || die "docker compose is still unavailable after install"
}
load_installer_images() {
require_cmd docker
shopt -s nullglob
local image
local found=0
for image in "$INSTALLER_ROOT"/images/*.tar "$INSTALLER_ROOT"/images/*.tar.gz; do
found=1
log "loading docker image: $image"
if [[ "$image" == *.gz ]]; then
gzip -dc "$image" | docker load
else
docker load -i "$image"
fi
done
shopt -u nullglob
(( found == 1 )) || warn "no image tar found under $INSTALLER_ROOT/images"
}
ensure_binfmt_if_needed() {
require_cmd docker
load_env
local host_arch
host_arch="$(uname -m)"
if [[ "$RPKI_PLATFORM" == "linux/arm64" && "$host_arch" != "aarch64" && "$host_arch" != "arm64" ]]; then
log "host arch is $host_arch; ensuring binfmt/qemu for arm64"
docker run --rm --privileged tonistiigi/binfmt --install arm64
fi
}
verify_runtime_image() {
load_env
require_cmd docker
log "verifying runtime image $RPKI_IMAGE on $RPKI_PLATFORM"
docker image inspect "$RPKI_IMAGE" >/dev/null
docker run --rm --platform "$RPKI_PLATFORM" "$RPKI_IMAGE" /opt/ours-rp/bin/rpki --help >/tmp/ours-rp-arm64-rpki-help.txt
head -5 /tmp/ours-rp-arm64-rpki-help.txt || true
}
verify_image_platform() {
local image="$1"
local expected_platform="$2"
local role="$3"
local actual_platform
docker image inspect "$image" >/dev/null
actual_platform="$(docker image inspect --format '{{.Os}}/{{.Architecture}}' "$image" 2>/dev/null || echo unknown)"
[[ "$actual_platform" == "$expected_platform" ]] || die "$role image platform mismatch: image=$image expected=$expected_platform actual=$actual_platform"
}
verify_monitor_images() {
load_env
require_cmd docker
verify_image_platform "$PROMETHEUS_IMAGE" "$MONITOR_PLATFORM" "prometheus"
verify_image_platform "$GRAFANA_IMAGE" "$MONITOR_PLATFORM" "grafana"
}
endpoint_ok() {
local url="$1"
curl -fsS --max-time 5 "$url" >/dev/null 2>&1
}

View File

@@ -1,38 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
QUICK=0
while [[ $# -gt 0 ]]; do
case "$1" in
--quick)
QUICK=1
shift
;;
-h|--help)
echo "Usage: ./self-check.sh [--quick]"
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
load_env
require_cmd docker
require_cmd jq
docker compose version >/dev/null
[[ -f "$COMPOSE_FILE" ]] || die "missing compose file"
[[ -f "$ENV_FILE" ]] || die "missing .env"
create_data_dirs
[[ -w "$HOST_DATA_DIR" ]] || die "data dir is not writable: $HOST_DATA_DIR"
compose_cmd --profile core --profile sidecar --profile monitor config >/dev/null
verify_image_platform "$RPKI_IMAGE" "$RPKI_PLATFORM" "runtime"
verify_monitor_images
if [[ "$QUICK" == "0" ]]; then
verify_runtime_image
fi
log "self-check ok"

View File

@@ -1,58 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
WAIT_FIRST_RUN=1
TIMEOUT_SECS=""
usage() {
cat <<'USAGE'
Usage: ./start.sh [--no-wait-first-run] [--timeout-secs N]
Start ours RP. If no successful run exists, wait for the first snapshot to succeed
before starting metrics, Prometheus and Grafana.
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--no-wait-first-run)
WAIT_FIRST_RUN=0
shift
;;
--timeout-secs)
TIMEOUT_SECS="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
load_env
create_data_dirs
timeout_secs="${TIMEOUT_SECS:-$FIRST_RUN_WAIT_TIMEOUT_SECS}"
before_latest="$(latest_run_dir || true)"
had_success=0
if has_success_run; then
had_success=1
fi
log "starting core soak service"
compose_cmd --profile core up -d ours-rp-soak
if [[ "$had_success" == "0" && "$WAIT_FIRST_RUN" == "1" ]]; then
log "no previous successful run found; waiting for first run timeout=${timeout_secs}s"
wait_for_new_success_run "$before_latest" "$timeout_secs"
fi
log "starting metrics and monitor services"
compose_cmd --profile sidecar --profile monitor up -d artifact-metrics prometheus grafana
"$SCRIPT_DIR/status.sh" --brief || true

View File

@@ -1,55 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
BRIEF=0
while [[ $# -gt 0 ]]; do
case "$1" in
--brief)
BRIEF=1
shift
;;
-h|--help)
echo "Usage: ./status.sh [--brief]"
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
load_env
echo "installer_root=$INSTALLER_ROOT"
echo "host_data_dir=$HOST_DATA_DIR"
echo "image=$RPKI_IMAGE"
echo "platform=$RPKI_PLATFORM"
echo "rirs=${RIRS:-}"
echo "max_runs=${MAX_RUNS:-}"
echo "interval_secs=${INTERVAL_SECS:-}"
echo
if command -v docker >/dev/null 2>&1; then
docker version --format 'docker={{.Server.Version}}' 2>/dev/null || echo "docker=unavailable"
docker compose version 2>/dev/null || true
compose_cmd --profile core --profile sidecar --profile monitor ps || true
else
echo "docker=missing"
fi
echo
df -h "$HOST_DATA_DIR" 2>/dev/null || true
echo
latest="$(latest_run_dir || true)"
if [[ -n "$latest" ]]; then
echo "latest_run=$latest"
print_run_summary "$latest" || true
else
echo "latest_run=none"
fi
if [[ "$BRIEF" == "0" ]]; then
echo
endpoint_ok "http://127.0.0.1:${METRICS_PORT:-9556}/metrics" && echo "metrics=ok" || echo "metrics=unavailable"
endpoint_ok "http://127.0.0.1:${PROMETHEUS_PORT:-9090}/-/ready" && echo "prometheus=ok" || echo "prometheus=unavailable"
endpoint_ok "http://127.0.0.1:${GRAFANA_PORT:-3000}/api/health" && echo "grafana=ok" || echo "grafana=unavailable"
fi

View File

@@ -1,7 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
load_env
compose_cmd --profile core --profile sidecar --profile monitor stop "$@"

View File

@@ -1,32 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
PURGE_DATA=0
while [[ $# -gt 0 ]]; do
case "$1" in
--purge-data)
PURGE_DATA=1
shift
;;
-h|--help)
echo "Usage: ./uninstall.sh [--purge-data]"
exit 0
;;
*)
die "unknown option: $1"
;;
esac
done
load_env
compose_cmd --profile core --profile sidecar --profile monitor down --remove-orphans || true
if [[ "$PURGE_DATA" == "1" ]]; then
[[ "$HOST_DATA_DIR" == "/" || -z "$HOST_DATA_DIR" ]] && die "refuse to purge unsafe HOST_DATA_DIR=$HOST_DATA_DIR"
rm -rf "$HOST_DATA_DIR"
log "purged data dir $HOST_DATA_DIR"
else
log "containers removed; data kept at $HOST_DATA_DIR"
fi

View File

@@ -1,14 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=scripts/common.sh
source "$SCRIPT_DIR/scripts/common.sh"
load_env
install_docker_if_missing
load_installer_images
ensure_binfmt_if_needed
verify_runtime_image
verify_monitor_images
compose_cmd --profile core --profile sidecar --profile monitor up -d --force-recreate
"$SCRIPT_DIR/status.sh" --brief || true

33
deploy/bird/.env Normal file
View File

@@ -0,0 +1,33 @@
# Build-time image knob.
RPKI_BIRD_VERSION=3.2.1
# TCP mode target endpoint.
RPKI_BIRD_RPKI_HOST=rpki-rtr
RPKI_BIRD_RPKI_PORT=323
# SSH mode target endpoint.
RPKI_BIRD_SSH_RPKI_HOST=rpki-rtr
RPKI_RTR_SSH_PORT=22
# Config template paths in container.
RPKI_BIRD_CONFIG_TEMPLATE_PATH=/config/bird.conf.template
RPKI_BIRD_SSH_CONFIG_TEMPLATE_PATH=/config/bird.conf.ssh.template
# Observation and output knobs.
RPKI_BIRD_OBSERVE_PROTO=rpki_tcp
RPKI_BIRD_OBSERVE_MODE=interval
RPKI_BIRD_OBSERVE_DEBOUNCE_SECS=1
RPKI_BIRD_OBSERVE_INTERVAL=30
RPKI_BIRD_OBSERVE_ASPA_TABLE=rtr_aspa
RPKI_BIRD_OBSERVE_ROA4_TABLE=rtr_roa_v4
RPKI_BIRD_OBSERVE_ROA6_TABLE=rtr_roa_v6
RPKI_BIRD_OBSERVE_ASPA_COUNT=3
RPKI_BIRD_OBSERVE_ROA4_COUNT=3
RPKI_BIRD_OBSERVE_ROA6_COUNT=3
RPKI_BIRD_SHOW_ASPA=1
RPKI_BIRD_SHOW_ROA4=1
RPKI_BIRD_SHOW_ROA6=1
# Host volume mounts.
RPKI_BIRD_LOG_HOST_DIR=../../logs/bird
RPKI_BIRD_SSH_CERTS_HOST_DIR=../../certs/ssh

56
deploy/bird/Dockerfile Normal file
View File

@@ -0,0 +1,56 @@
FROM debian:bookworm-slim AS builder
ARG BIRD_VERSION=3.2.1
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential \
bison \
flex \
m4 \
perl \
ca-certificates \
wget \
xz-utils \
libreadline-dev \
libncurses-dev \
libssh-dev \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /build
RUN wget -O bird.tar.gz "https://bird.nic.cz/download/bird-${BIRD_VERSION}.tar.gz" \
&& tar -xzf bird.tar.gz \
&& mv "bird-${BIRD_VERSION}" bird
WORKDIR /build/bird
RUN ./configure \
--prefix=/usr \
--sysconfdir=/etc/bird \
--localstatedir=/run \
&& make -j"$(nproc)" \
&& make install
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
gettext-base \
tzdata \
netcat-openbsd \
libreadline8 \
libncurses6 \
libtinfo6 \
libssh-4 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/sbin/bird /usr/sbin/bird
COPY --from=builder /usr/sbin/birdc /usr/sbin/birdc
COPY --from=builder /etc/bird /etc/bird
ENV TZ=Asia/Shanghai
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh \
&& mkdir -p /run/bird
ENTRYPOINT ["/entrypoint.sh"]

90
deploy/bird/README.md Normal file
View File

@@ -0,0 +1,90 @@
# BIRD Minimal RTR Client Config
This folder provides a minimal BIRD setup for black-box interop testing
against this repository's RTR server defaults.
Server defaults in this repo:
- TCP: `0.0.0.0:323`
- SSH: `0.0.0.0:22` (when enabled on server)
## Files
- `Dockerfile`: builds a minimal BIRD2 runtime image.
- `bird.conf.example`: sample `/etc/bird/bird.conf`.
- `bird.conf.ssh.example`: sample SSH transport `/etc/bird/bird.conf`.
- `entrypoint.sh`: starts BIRD in foreground mode.
- `docker-compose.yml`: one-click local TCP test client.
- `docker-compose.ssh.yml`: compose override for SSH transport.
By default, the container uses event-driven observation and prints snapshots
only when BIRD reports RPKI-related changes.
## Docker quick start
From repository root:
```bash
docker compose -f deploy/bird/docker-compose.yml up --build
```
Use another terminal to inspect:
```bash
docker logs -f bird-rpki-client
```
If protocol state is `up`, the RTR client path is working.
Detached mode:
```bash
docker compose -f deploy/bird/docker-compose.yml up -d --build
docker logs -f bird-rpki-client
```
Stop:
```bash
docker compose -f deploy/bird/docker-compose.yml down
```
## SSH quick start
Start server in SSH mode first:
```bash
docker compose -f deploy/server/docker-compose.ssh.yml up -d --build
```
Start BIRD client with SSH override:
```bash
docker compose \
-f deploy/bird/docker-compose.yml \
-f deploy/bird/docker-compose.ssh.yml \
up --build
```
In detached mode, observe with:
```bash
docker logs -f bird-rpki-client
```
## Notes
- This setup renders `bird.conf.template` at startup and uses `RPKI_HOST` / `RPKI_PORT`
for the RTR endpoint.
- `network_mode: host` expects your RTR server to be reachable at
`host.docker.internal:323` from the container.
- Observation is controlled by env vars:
`OBSERVE_MODE` (`event` by default, `interval` as fallback),
`OBSERVE_DEBOUNCE_SECS` (default `1`),
`OBSERVE_INTERVAL` (seconds, used when `OBSERVE_MODE=interval`),
and `OBSERVE_PROTO`.
- SSH mode mounts `../../certs/ssh` into `/config/ssh` and expects:
`rtr-client.key` and `ssh_host_rsa_key.pub`.
- Entrypoint auto-generates `/run/bird/known_hosts` from
`/config/ssh/ssh_host_rsa_key.pub` for BIRD SSH host-key verification.
- If your environment does not support Docker host networking, switch to a
bridge network and replace `remote` addresses accordingly.

80
deploy/bird/README.zh.md Normal file
View File

@@ -0,0 +1,80 @@
# BIRD 最小化 RTR 客户端配置
本目录提供最小化 BIRD 配置,用于与本仓库 RTR Server 做黑盒互通测试。
本仓库默认 RTR 监听地址:
- TCP: `0.0.0.0:323`
- SSH: `0.0.0.0:22`(仅在 server SSH 模式启用时)
## 文件说明
- `Dockerfile`: 构建最小 BIRD 运行镜像(包含 SSH transport 支持)。
- `bird.conf.example`: `/etc/bird/bird.conf` 的 TCP 示例。
- `bird.conf.ssh.example`: `/etc/bird/bird.conf` 的 SSH transport 示例。
- `entrypoint.sh`: 前台启动 BIRD,并周期输出协议快照。
- `docker-compose.yml`: TCP 一键启动。
- `docker-compose.ssh.yml`: SSH 覆盖配置。
容器默认每 30 秒向日志输出一次 RPKI 协议状态快照。
## Docker 快速启动(TCP)
在仓库根目录执行:
```bash
docker compose -f deploy/bird/docker-compose.yml up --build
```
另开终端查看日志:
```bash
docker logs -f bird-rpki-client
```
如果协议状态显示 `up`,说明 RTR 客户端链路正常。
后台模式:
```bash
docker compose -f deploy/bird/docker-compose.yml up -d --build
docker logs -f bird-rpki-client
```
停止:
```bash
docker compose -f deploy/bird/docker-compose.yml down
```
## SSH 快速启动
先启动 server 的 SSH 模式:
```bash
docker compose -f deploy/server/docker-compose.ssh.yml up -d --build
```
再启动 BIRD SSH 客户端:
```bash
docker compose \
-f deploy/bird/docker-compose.yml \
-f deploy/bird/docker-compose.ssh.yml \
up --build
```
查看日志:
```bash
docker logs -f bird-rpki-client
```
## 说明
- 当前 compose 使用 `network_mode: host`,默认通过 `host.docker.internal` 访问 server。
- 观测频率由环境变量控制:`OBSERVE_INTERVAL`(秒,默认 `30`)和 `OBSERVE_PROTO`。
- SSH 模式会将 `../../certs/ssh` 挂载到容器 `/config/ssh`,并使用:
`rtr-client.key`、`ssh_host_rsa_key.pub`。
- 入口脚本会基于 `/config/ssh/ssh_host_rsa_key.pub` 自动生成
`/run/bird/known_hosts`,用于 BIRD 的 SSH 主机密钥校验。
- 如果你运行在 Docker Desktop(非 Linux 原生 host network 场景),建议改为自定义 bridge 网络并将 `remote` 地址改成可达的 server 容器名或宿主机地址。

View File

@@ -0,0 +1,31 @@
log stderr all;
debug protocols all;
router id 192.0.2.2;
roa4 table rtr_roa_v4;
roa6 table rtr_roa_v6;
aspa table rtr_aspa;
protocol device {
}
protocol rpki rpki_ssh {
roa4 { table rtr_roa_v4; };
roa6 { table rtr_roa_v6; };
aspa { table rtr_aspa; };
remote "host.docker.internal" port 22;
min version 2;
max version 2;
refresh 3600;
retry 600;
expire 7200;
transport ssh {
user "rpki-rtr";
bird private key "/config/ssh/rtr-client.key";
remote public key "/run/bird/known_hosts";
};
}

28
deploy/bird/bird.conf Normal file
View File

@@ -0,0 +1,28 @@
log stderr all;
router id 192.0.2.2;
roa4 table rtr_roa_v4;
roa6 table rtr_roa_v6;
aspa table rtr_aspa;
protocol device {
}
protocol rpki rpki_tcp {
roa4 { table rtr_roa_v4; };
roa6 { table rtr_roa_v6; };
aspa { table rtr_aspa; };
remote "host.docker.internal" port 323;
min version 2;
max version 2;
refresh 3600;
retry 600;
expire 7200;
transport tcp {
authentication none;
};
}

View File

@@ -0,0 +1,28 @@
log stderr all;
router id 192.0.2.2;
roa4 table rtr_roa_v4;
roa6 table rtr_roa_v6;
aspa table rtr_aspa;
protocol device {
}
protocol rpki rpki_tcp {
roa4 { table rtr_roa_v4; };
roa6 { table rtr_roa_v6; };
aspa { table rtr_aspa; };
remote "host.docker.internal" port 323;
min version 2;
max version 2;
refresh 3600;
retry 600;
expire 7200;
transport tcp {
authentication none;
};
}

View File

@@ -0,0 +1,30 @@
log stderr all;
router id 192.0.2.2;
roa4 table rtr_roa_v4;
roa6 table rtr_roa_v6;
aspa table rtr_aspa;
protocol device {
}
protocol rpki rpki_ssh {
roa4 { table rtr_roa_v4; };
roa6 { table rtr_roa_v6; };
aspa { table rtr_aspa; };
remote "host.docker.internal" port 22;
min version 2;
max version 2;
refresh 3600;
retry 600;
expire 7200;
transport ssh {
user "rpki-rtr";
bird private key "/config/ssh/rtr-client.key";
remote public key "/run/bird/known_hosts";
};
}

View File

@@ -0,0 +1,30 @@
log stderr all;
router id 192.0.2.2;
roa4 table rtr_roa_v4;
roa6 table rtr_roa_v6;
aspa table rtr_aspa;
protocol device {
}
protocol rpki rpki_ssh {
roa4 { table rtr_roa_v4; };
roa6 { table rtr_roa_v6; };
aspa { table rtr_aspa; };
remote "${RPKI_HOST}" port ${RPKI_PORT};
min version 2;
max version 2;
refresh 3600;
retry 600;
expire 7200;
transport ssh {
user "rpki-rtr";
bird private key "/config/ssh/rtr-client.key";
remote public key "/run/bird/known_hosts";
};
}

View File

@@ -0,0 +1,28 @@
log stderr all;
router id 192.0.2.2;
roa4 table rtr_roa_v4;
roa6 table rtr_roa_v6;
aspa table rtr_aspa;
protocol device {
}
protocol rpki rpki_tcp {
roa4 { table rtr_roa_v4; };
roa6 { table rtr_roa_v6; };
aspa { table rtr_aspa; };
remote "${RPKI_HOST}" port ${RPKI_PORT};
min version 2;
max version 2;
refresh 3600;
retry 600;
expire 7200;
transport tcp {
authentication none;
};
}

View File

@@ -0,0 +1,17 @@
services:
bird-rpki-client:
environment:
BIRD_CONFIG_TEMPLATE_PATH: "${RPKI_BIRD_SSH_CONFIG_TEMPLATE_PATH:-/config/bird.conf.ssh.template}"
RPKI_HOST: "${RPKI_BIRD_SSH_RPKI_HOST:-rpki-rtr}"
RPKI_PORT: "${RPKI_RTR_SSH_PORT:-22}"
OBSERVE_PROTO: "rpki_ssh"
volumes:
- ./bird.conf.ssh.template:/config/bird.conf.ssh.template:ro
- ${RPKI_BIRD_SSH_CERTS_HOST_DIR:-../../certs/ssh}:/config/ssh:ro
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,41 @@
services:
bird-rpki-client:
build:
context: .
dockerfile: Dockerfile
args:
BIRD_VERSION: "${RPKI_BIRD_VERSION:-3.2.1}"
container_name: bird-rpki-client
restart: unless-stopped
environment:
BIRD_CONFIG_TEMPLATE_PATH: "${RPKI_BIRD_CONFIG_TEMPLATE_PATH:-/config/bird.conf.template}"
RPKI_HOST: "${RPKI_BIRD_RPKI_HOST:-rpki-rtr}"
RPKI_PORT: "${RPKI_BIRD_RPKI_PORT:-323}"
OBSERVE_PROTO: "${RPKI_BIRD_OBSERVE_PROTO:-rpki_tcp}"
OBSERVE_MODE: "${RPKI_BIRD_OBSERVE_MODE:-interval}"
OBSERVE_DEBOUNCE_SECS: "${RPKI_BIRD_OBSERVE_DEBOUNCE_SECS:-1}"
OBSERVE_INTERVAL: "${RPKI_BIRD_OBSERVE_INTERVAL:-30}"
OBSERVE_ASPA_TABLE: "${RPKI_BIRD_OBSERVE_ASPA_TABLE:-rtr_aspa}"
OBSERVE_ROA4_TABLE: "${RPKI_BIRD_OBSERVE_ROA4_TABLE:-rtr_roa_v4}"
OBSERVE_ROA6_TABLE: "${RPKI_BIRD_OBSERVE_ROA6_TABLE:-rtr_roa_v6}"
OBSERVE_ASPA_COUNT: "${RPKI_BIRD_OBSERVE_ASPA_COUNT:-3}"
OBSERVE_ROA4_COUNT: "${RPKI_BIRD_OBSERVE_ROA4_COUNT:-3}"
OBSERVE_ROA6_COUNT: "${RPKI_BIRD_OBSERVE_ROA6_COUNT:-3}"
SHOW_ASPA: "${RPKI_BIRD_SHOW_ASPA:-1}"
SHOW_ROA4: "${RPKI_BIRD_SHOW_ROA4:-1}"
SHOW_ROA6: "${RPKI_BIRD_SHOW_ROA6:-1}"
volumes:
- ./bird.conf.template:/config/bird.conf.template:ro
- ${RPKI_BIRD_LOG_HOST_DIR:-../../logs/bird}:/app/logs
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

218
deploy/bird/entrypoint.sh Normal file
View File

@@ -0,0 +1,218 @@
#!/bin/sh
set -eu
mkdir -p /run/bird
SOCK_PATH="/run/bird/bird.ctl"
PROTO="${OBSERVE_PROTO:-rpki_tcp}"
INTERVAL="${OBSERVE_INTERVAL:-30}"
MODE="${OBSERVE_MODE:-event}"
DEBOUNCE_SECS="${OBSERVE_DEBOUNCE_SECS:-1}"
RPKI_HOST="${RPKI_HOST:-host.docker.internal}"
RPKI_PORT="${RPKI_PORT:-323}"
BIRD_CONFIG_PATH="${BIRD_CONFIG_PATH:-/config/bird.conf}"
BIRD_CONFIG_TEMPLATE_PATH="${BIRD_CONFIG_TEMPLATE_PATH:-/config/bird.conf.template}"
ASPA_TABLE="${OBSERVE_ASPA_TABLE:-rtr_aspa}"
ROA4_TABLE="${OBSERVE_ROA4_TABLE:-rtr_roa_v4}"
ROA6_TABLE="${OBSERVE_ROA6_TABLE:-rtr_roa_v6}"
ASPA_COUNT="${OBSERVE_ASPA_COUNT:-3}"
ROA4_COUNT="${OBSERVE_ROA4_COUNT:-3}"
ROA6_COUNT="${OBSERVE_ROA6_COUNT:-3}"
SHOW_ASPA="${SHOW_ASPA:-1}"
SHOW_ROA4="${SHOW_ROA4:-1}"
SHOW_ROA6="${SHOW_ROA6:-1}"
SSH_HOST_PUBKEY_PATH="${SSH_HOST_PUBKEY_PATH:-/config/ssh/ssh_host_rsa_key.pub}"
SSH_KNOWN_HOSTS_PATH="${SSH_KNOWN_HOSTS_PATH:-/run/bird/known_hosts}"
LOG_DIR="${LOG_DIR:-/app/logs}"
LOG_NAME="${LOG_NAME:-${HOSTNAME:-bird-rpki-client}}"
STDOUT_LOG="${LOG_DIR}/${LOG_NAME}.stdout.log"
STDERR_LOG="${LOG_DIR}/${LOG_NAME}.stderr.log"
mkdir -p "$LOG_DIR"
exec >>"$STDOUT_LOG" 2>>"$STDERR_LOG"
if [ -r "$BIRD_CONFIG_TEMPLATE_PATH" ]; then
RENDERED_CONFIG_PATH="/run/bird/bird.generated.conf"
envsubst '${RPKI_HOST} ${RPKI_PORT}' < "$BIRD_CONFIG_TEMPLATE_PATH" > "$RENDERED_CONFIG_PATH"
BIRD_CONFIG_PATH="$RENDERED_CONFIG_PATH"
echo "[entrypoint] rendered config : $BIRD_CONFIG_TEMPLATE_PATH -> $BIRD_CONFIG_PATH"
fi
ensure_ssh_known_hosts() {
if [ -s "$SSH_KNOWN_HOSTS_PATH" ]; then
return
fi
if [ ! -r "$SSH_HOST_PUBKEY_PATH" ]; then
echo "[entrypoint] WARNING: SSH host key file not found: $SSH_HOST_PUBKEY_PATH"
return
fi
set -- $(awk 'NF >= 2 { print $1, $2; exit }' "$SSH_HOST_PUBKEY_PATH")
if [ $# -ne 2 ]; then
echo "[entrypoint] WARNING: invalid SSH host key format in $SSH_HOST_PUBKEY_PATH"
return
fi
key_type="$1"
key_data="$2"
if echo "$key_type" | grep -q '^ssh-'; then
{
echo "$RPKI_HOST $key_type $key_data"
echo "[$RPKI_HOST]:$RPKI_PORT $key_type $key_data"
} > "$SSH_KNOWN_HOSTS_PATH"
else
cp "$SSH_HOST_PUBKEY_PATH" "$SSH_KNOWN_HOSTS_PATH"
fi
chmod 600 "$SSH_KNOWN_HOSTS_PATH" || true
echo "[entrypoint] generated known_hosts: $SSH_KNOWN_HOSTS_PATH"
}
print_first_n_objects() {
table_name="$1"
max_objects="$2"
birdc -s "$SOCK_PATH" show route table "$table_name" all 2>/dev/null | awk -v max="$max_objects" '
BEGIN {
count = 0
}
# 直接跳过空行
/^[[:space:]]*$/ {
next
}
# 保留 birdc 的表头
/^BIRD / {
print
next
}
/^Table / {
print
next
}
# 非缩进且不是表头,视为一个新对象的开始
/^[^[:space:]]/ {
count++
if (count > max) {
exit
}
print
next
}
# 缩进内容,只有在已进入前 max 个对象时才打印
{
if (count > 0 && count <= max) {
print
}
}
' || true
}
print_snapshot() {
echo "==== $(date +"%Y-%m-%dT%H:%M:%S%:z") RPKI snapshot ($PROTO) ===="
birdc -s "$SOCK_PATH" show protocols all "$PROTO" || true
if [ "$SHOW_ASPA" = "1" ]; then
echo "---- ASPA table ($ASPA_TABLE, first ${ASPA_COUNT} objects) ----"
print_first_n_objects "$ASPA_TABLE" "$ASPA_COUNT"
fi
if [ "$SHOW_ROA4" = "1" ]; then
echo "---- ROA4 table ($ROA4_TABLE, first ${ROA4_COUNT} objects) ----"
print_first_n_objects "$ROA4_TABLE" "$ROA4_COUNT"
fi
if [ "$SHOW_ROA6" = "1" ]; then
echo "---- ROA6 table ($ROA6_TABLE, first ${ROA6_COUNT} objects) ----"
print_first_n_objects "$ROA6_TABLE" "$ROA6_COUNT"
fi
}
is_rpki_related_event() {
line="$1"
echo "$line" | grep -Eiq "$PROTO|$ASPA_TABLE|$ROA4_TABLE|$ROA6_TABLE|rpki|rtr"
}
echo "[entrypoint] starting bird"
echo "[entrypoint] config : $BIRD_CONFIG_PATH"
echo "[entrypoint] observe proto : $PROTO"
echo "[entrypoint] observe mode : $MODE"
echo "[entrypoint] observe interval : $INTERVAL (used when mode=interval)"
echo "[entrypoint] debounce secs : $DEBOUNCE_SECS (used when mode=event)"
echo "[entrypoint] target : $RPKI_HOST:$RPKI_PORT"
echo "[entrypoint] show aspa : $SHOW_ASPA ($ASPA_TABLE, first $ASPA_COUNT objects)"
echo "[entrypoint] show roa4 : $SHOW_ROA4 ($ROA4_TABLE, first $ROA4_COUNT objects)"
echo "[entrypoint] show roa6 : $SHOW_ROA6 ($ROA6_TABLE, first $ROA6_COUNT objects)"
if [ "$PROTO" = "rpki_ssh" ] || grep -q 'transport[[:space:]]\+ssh' "$BIRD_CONFIG_PATH"; then
ensure_ssh_known_hosts
fi
if nc -zvw3 "$RPKI_HOST" "$RPKI_PORT"; then
echo "[entrypoint] TCP connectivity to $RPKI_HOST:$RPKI_PORT OK"
else
echo "[entrypoint] WARNING: cannot connect to $RPKI_HOST:$RPKI_PORT before BIRD starts"
fi
bird -f -c "$BIRD_CONFIG_PATH" -s "$SOCK_PATH" &
BIRD_PID="$!"
sleep 1
case "$INTERVAL" in
''|*[!0-9]*)
INTERVAL=0
;;
esac
case "$MODE" in
event)
case "$DEBOUNCE_SECS" in
''|*[!0-9]*)
DEBOUNCE_SECS=1
;;
esac
echo "[entrypoint] waiting for BIRD monitor events and only printing on RPKI-related changes"
LAST_PRINT_TS=0
if birdc -s "$SOCK_PATH" monitor all 2>/dev/null | while IFS= read -r line; do
[ -n "$line" ] || continue
if ! is_rpki_related_event "$line"; then
continue
fi
now_ts="$(date +%s)"
if [ $((now_ts - LAST_PRINT_TS)) -lt "$DEBOUNCE_SECS" ]; then
continue
fi
LAST_PRINT_TS="$now_ts"
echo "[monitor] $line"
print_snapshot
done; then
:
else
echo "[entrypoint] WARNING: birdc monitor failed, fallback to interval polling"
MODE="interval"
fi
;;
esac
if [ "$MODE" = "interval" ] && [ "$INTERVAL" -gt 0 ]; then
while kill -0 "$BIRD_PID" 2>/dev/null; do
print_snapshot
sleep "$INTERVAL"
done
fi
wait "$BIRD_PID"

37
deploy/client/.env Normal file
View File

@@ -0,0 +1,37 @@
# Target RTR server addresses for client compose files
# build-run.sh auto-detects amd64/arm64 unless IMAGE_ARCH/RPKI_RTR_IMAGE_ARCH is set before running it.
# Direct docker compose uses RPKI_RTR_IMAGE_ARCH from this file; set it to arm64 on ARM hosts.
RPKI_RTR_CLIENT_IMAGE_BASE=rpki-rtr-debug-client
RPKI_RTR_CLIENT_IMAGE_TAG=v1.3
RPKI_RTR_IMAGE_ARCH=arm64
# RPKI_RTR_TARGET_PLATFORM=linux/amd64
# RPKI_RTR_CLIENT_IMAGE=rpki-rtr-debug-client-amd64:v1.3
# TCP example: 10.0.0.12:323
# TLS example: rpki.example.com:324
# SSH example: 10.0.0.12:22
RPKI_RTR_TCP_SERVER_ADDR=rpki-rtr:323
RPKI_RTR_TLS_SERVER_ADDR=rpki-rtr:324
RPKI_RTR_SSH_SERVER_ADDR=rpki-rtr:22
# RTR protocol version used as client command second argument (supported: 0,1,2)
RPKI_RTR_PROTOCOL_VERSION=2
# TLS server name used by --server-name in TLS mode
# Must match server certificate SAN dNSName.
RPKI_RTR_TLS_SERVER_NAME=localhost
RPKI_RTR_TLS_CA_CERT_PATH=/app/certs/client-ca.crt
RPKI_RTR_TLS_CLIENT_CERT_PATH=/app/certs/client-good.crt
RPKI_RTR_TLS_CLIENT_KEY_PATH=/app/certs/client-good.key
RPKI_RTR_TLS_CERTS_HOST_DIR=../../certs/tls
# Shared client logs mount on host.
RPKI_RTR_CLIENT_LOG_HOST_DIR=../../logs/client
# SSH mode examples:
# RPKI_RTR_SSH_SERVER_ADDR=10.0.0.12:2222
RPKI_RTR_CLIENT_KEYS_VOLUME=../../certs/ssh:/app/certs:ro
RPKI_RTR_CLIENT_KEY_PATH=/app/certs/rtr-client.key
RPKI_RTR_SSH_SERVER_PUBKEY_PATH=/app/certs/ssh_host_rsa_key.pub
RPKI_RTR_SSH_USERNAME=rpki-rtr
RPKI_RTR_SSH_PASSWORD=

26
deploy/client/Dockerfile Normal file
View File

@@ -0,0 +1,26 @@
FROM rust:1.89-bookworm AS builder
WORKDIR /build
RUN apt-get update \
&& apt-get install -y --no-install-recommends clang libclang-dev pkg-config \
&& rm -rf /var/lib/apt/lists/*
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release --bin rtr_debug_client
FROM debian:bookworm-slim AS runtime
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates tzdata \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
ENV TZ=Asia/Shanghai
COPY --from=builder /build/target/release/rtr_debug_client /usr/local/bin/rtr_debug_client
COPY --chmod=755 deploy/client/entrypoint.sh /usr/local/bin/rtr-debug-client-entrypoint.sh
ENTRYPOINT ["/usr/local/bin/rtr-debug-client-entrypoint.sh"]

314
deploy/client/build-run.sh Normal file
View File

@@ -0,0 +1,314 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
CALLER_TARGET_PLATFORM="${TARGET_PLATFORM:-${RPKI_RTR_TARGET_PLATFORM:-}}"
CALLER_IMAGE_ARCH="${IMAGE_ARCH:-${RPKI_RTR_IMAGE_ARCH:-}}"
CALLER_CLIENT_IMAGE="${CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE:-}}"
load_env_defaults() {
local line key value
[[ -f "${SCRIPT_DIR}/.env" ]] || return
while IFS= read -r line || [[ -n "${line}" ]]; do
line="${line%$'\r'}"
[[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]] && continue
[[ "${line}" == *"="* ]] || continue
key="${line%%=*}"
value="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
[[ "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
if [[ -z "${!key+x}" ]]; then
export "${key}=${value}"
fi
done < "${SCRIPT_DIR}/.env"
}
detect_image_arch() {
case "$(uname -m)" in
x86_64|amd64) printf '%s\n' amd64 ;;
aarch64|arm64) printf '%s\n' arm64 ;;
*) die "unsupported host architecture '$(uname -m)'; set RPKI_RTR_IMAGE_ARCH=amd64 or arm64" ;;
esac
}
platform_for_image_arch() {
case "$1" in
amd64) printf '%s\n' linux/amd64 ;;
arm64) printf '%s\n' linux/arm64 ;;
*) die "unsupported image arch '$1', expected amd64 or arm64" ;;
esac
}
image_arch_from_platform() {
case "$1" in
linux/amd64|amd64) printf '%s\n' amd64 ;;
linux/arm64|arm64) printf '%s\n' arm64 ;;
*) return 1 ;;
esac
}
load_env_defaults
MODE="${MODE:-tcp}"
ACTION="up"
DETACH="${DETACH:-1}"
BUILD="${BUILD:-1}"
CLIENTS="${CLIENTS:-single}"
TARGET_PLATFORM="${CALLER_TARGET_PLATFORM}"
IMAGE_ARCH="${CALLER_IMAGE_ARCH}"
CLIENT_IMAGE="${CALLER_CLIENT_IMAGE}"
BUILDX_PUSH="${BUILDX_PUSH:-0}"
BUILDX_BUILDER="${BUILDX_BUILDER:-}"
SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}"
usage() {
cat <<'EOF'
Usage:
deploy/client/build-run.sh [action] [options]
Actions:
up Build and run debug client (default)
build Build debug client image
buildx Build debug client image with docker buildx
rebuild Rebuild image without cache, then run client
down|stop Stop client compose stack
restart Stop, then build and run client
logs Follow client logs
ps Show client compose status
Options:
--mode MODE base|tcp|tls|ssh (default: tcp)
--clients MODE single|multi (default: single)
--platform VALUE Target platform for buildx (default: host architecture)
--image-arch VALUE
Image architecture suffix for default tag: amd64|arm64
--client-image TAG
Client image tag for buildx and compose
--push Push buildx image instead of loading it locally
--builder NAME buildx builder name
--skip-base-pull Do not pull Dockerfile base images before buildx
--foreground Run single client in foreground
--no-build Start without --build
-h|--help Show this help
EOF
}
die() {
echo "error: $*" >&2
exit 1
}
has_compose() {
docker compose version >/dev/null 2>&1
}
has_buildx() {
docker buildx version >/dev/null 2>&1
}
pull_base_images() {
[[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return
local platform image
local -a platforms images
IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}"
images=(rust:1.89-bookworm debian:bookworm-slim)
for platform in "${platforms[@]}"; do
for image in "${images[@]}"; do
docker pull --platform "${platform}" "${image}"
done
done
}
compose_files() {
if [[ "${CLIENTS}" == "multi" ]]; then
[[ "${MODE}" == "tcp" || "${MODE}" == "base" ]] || die "--clients multi is only defined for tcp/base mode"
printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.clients.yml"
return
fi
case "${MODE}" in
base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;;
tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;;
tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;;
ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;;
*) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;;
esac
}
compose_args() {
local -a args files
args=()
[[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env")
mapfile -t files < <(compose_files)
args+=("${files[@]}")
printf '%s\n' "${args[@]}"
}
compose_run() {
local -a args
mapfile -t args < <(compose_args)
docker compose "${args[@]}" "$@"
}
up_client() {
local -a up_args
up_args=(up)
[[ "${CLIENTS}" == "multi" || "${DETACH}" == "1" ]] && up_args+=(-d)
[[ "${BUILD}" == "1" && "${CLIENTS}" != "multi" ]] && up_args+=(--build)
compose_run "${up_args[@]}"
}
build_client() {
if [[ "${CLIENTS}" == "multi" ]]; then
docker build "$@" -f "${SCRIPT_DIR}/Dockerfile" -t "${CLIENT_IMAGE}" "${REPO_ROOT}"
else
compose_run build "$@"
fi
}
buildx_client() {
has_buildx || die "docker buildx is not available"
if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then
die "multi-platform buildx output requires --push"
fi
local -a output_args builder_args
[[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load)
[[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=()
pull_base_images
docker buildx build "${builder_args[@]}" \
--platform "${TARGET_PLATFORM}" \
-f "${SCRIPT_DIR}/Dockerfile" \
-t "${CLIENT_IMAGE}" \
"${output_args[@]}" \
"${REPO_ROOT}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
up|build|buildx|rebuild|down|stop|restart|logs|ps)
ACTION="$1"
shift
;;
--mode)
[[ $# -ge 2 ]] || die "--mode requires a value"
MODE="$2"
shift 2
;;
--clients)
[[ $# -ge 2 ]] || die "--clients requires a value"
CLIENTS="$2"
shift 2
;;
--platform)
[[ $# -ge 2 ]] || die "--platform requires a value"
TARGET_PLATFORM="$2"
shift 2
;;
--image-arch)
[[ $# -ge 2 ]] || die "--image-arch requires a value"
IMAGE_ARCH="$2"
shift 2
;;
--client-image)
[[ $# -ge 2 ]] || die "--client-image requires a value"
CLIENT_IMAGE="$2"
shift 2
;;
--push)
BUILDX_PUSH=1
shift
;;
--builder)
[[ $# -ge 2 ]] || die "--builder requires a value"
BUILDX_BUILDER="$2"
shift 2
;;
--skip-base-pull)
SKIP_BASE_IMAGE_PULL=1
shift
;;
--foreground)
DETACH=0
shift
;;
--no-build)
BUILD=0
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown argument '$1'"
;;
esac
done
if [[ -z "${TARGET_PLATFORM}" ]]; then
[[ -n "${IMAGE_ARCH}" ]] || IMAGE_ARCH="$(detect_image_arch)"
TARGET_PLATFORM="$(platform_for_image_arch "${IMAGE_ARCH}")"
elif [[ -z "${IMAGE_ARCH}" ]]; then
if [[ "${TARGET_PLATFORM}" != *,* ]] && IMAGE_ARCH="$(image_arch_from_platform "${TARGET_PLATFORM}")"; then
:
else
IMAGE_ARCH="$(detect_image_arch)"
fi
fi
if [[ -z "${CLIENT_IMAGE}" ]]; then
if [[ "${TARGET_PLATFORM}" == *,* ]]; then
CLIENT_IMAGE="${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}"
else
CLIENT_IMAGE="${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${IMAGE_ARCH}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}"
fi
fi
[[ "${CLIENTS}" == "single" || "${CLIENTS}" == "multi" ]] || die "unsupported clients '${CLIENTS}', expected single|multi"
export RPKI_RTR_CLIENT_IMAGE="${CLIENT_IMAGE}"
if [[ "${ACTION}" == "buildx" ]]; then
has_buildx || die "docker buildx is not available"
else
has_compose || die "docker compose is not available"
fi
cd "${REPO_ROOT}"
case "${ACTION}" in
up)
up_client
;;
build)
build_client
;;
buildx)
buildx_client
;;
rebuild)
build_client --no-cache
up_client
;;
down|stop)
compose_run down
;;
restart)
compose_run down
up_client
;;
logs)
compose_run logs -f
;;
ps)
compose_run ps
;;
esac

View File

@@ -0,0 +1,52 @@
version: "3.9"
services:
rtr-client-1:
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
networks:
- rpki_net
rtr-client-2:
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
networks:
- rpki_net
rtr-client-3:
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
networks:
- rpki_net
rtr-client-4:
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
networks:
- rpki_net
rtr-client-5:
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,36 @@
version: "3.9"
services:
rtr-debug-client:
build:
context: ../..
dockerfile: deploy/client/Dockerfile
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command:
[
"${RPKI_RTR_SSH_SERVER_ADDR:-rpki-rtr:22}",
"${RPKI_RTR_PROTOCOL_VERSION:-2}",
"reset",
"--ssh",
"--ssh-user",
"${RPKI_RTR_SSH_USERNAME:-rpki-rtr}",
"--ssh-password",
"${RPKI_RTR_SSH_PASSWORD}",
"--ssh-server-key",
"${RPKI_RTR_SSH_SERVER_PUBKEY_PATH:-/app/certs/ssh_host_rsa_key.pub}",
"--keep-after-error",
"--summary-only"
]
volumes:
- ${RPKI_RTR_CLIENT_KEYS_VOLUME:-../../certs/ssh:/app/certs:ro}
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
stdin_open: true
tty: true
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,36 @@
version: "3.9"
services:
rtr-debug-client:
build:
context: ../..
dockerfile: deploy/client/Dockerfile
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command:
[
"${RPKI_RTR_SSH_SERVER_ADDR:-rpki-rtr:22}",
"${RPKI_RTR_PROTOCOL_VERSION:-2}",
"reset",
"--ssh",
"--ssh-user",
"${RPKI_RTR_SSH_USERNAME:-rpki-rtr}",
"--ssh-key",
"${RPKI_RTR_CLIENT_KEY_PATH:-/app/certs/rtr-client.key}",
"--ssh-server-key",
"${RPKI_RTR_SSH_SERVER_PUBKEY_PATH:-/app/certs/ssh_host_rsa_key.pub}",
"--keep-after-error",
"--summary-only"
]
volumes:
- ${RPKI_RTR_CLIENT_KEYS_VOLUME:-../../certs/ssh:/app/certs:ro}
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
stdin_open: true
tty: true
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,19 @@
services:
rtr-debug-client:
build:
context: ../..
dockerfile: deploy/client/Dockerfile
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
stdin_open: true
tty: true
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,38 @@
version: "3.9"
services:
rtr-debug-client:
build:
context: ../..
dockerfile: deploy/client/Dockerfile
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command:
[
"${RPKI_RTR_TLS_SERVER_ADDR:-rpki-rtr:324}",
"${RPKI_RTR_PROTOCOL_VERSION:-2}",
"reset",
"--tls",
"--ca-cert",
"${RPKI_RTR_TLS_CA_CERT_PATH:-/app/certs/client-ca.crt}",
"--server-name",
"${RPKI_RTR_TLS_SERVER_NAME:-localhost}",
"--client-cert",
"${RPKI_RTR_TLS_CLIENT_CERT_PATH:-/app/certs/client-good.crt}",
"--client-key",
"${RPKI_RTR_TLS_CLIENT_KEY_PATH:-/app/certs/client-good.key}",
"--keep-after-error",
"--summary-only"
]
volumes:
- ${RPKI_RTR_TLS_CERTS_HOST_DIR:-../../certs/tls}:/app/certs:ro
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
stdin_open: true
tty: true
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,19 @@
services:
rtr-debug-client:
build:
context: ../..
dockerfile: deploy/client/Dockerfile
image: ${RPKI_RTR_CLIENT_IMAGE:-${RPKI_RTR_CLIENT_IMAGE_BASE:-rpki-rtr-debug-client}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_CLIENT_IMAGE_TAG:-latest}}
command: ["${RPKI_RTR_TCP_SERVER_ADDR:-rpki-rtr:323}", "${RPKI_RTR_PROTOCOL_VERSION:-2}", "reset", "--keep-after-error", "--summary-only"]
volumes:
- ${RPKI_RTR_CLIENT_LOG_HOST_DIR:-../../logs/client}:/app/logs
restart: no
stdin_open: true
tty: true
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,10 @@
#!/bin/sh
set -eu
mkdir -p /app/logs
log_name="${HOSTNAME:-rtr-debug-client}"
stdout_log="/app/logs/${log_name}.stdout.log"
stderr_log="/app/logs/${log_name}.stderr.log"
exec /usr/local/bin/rtr_debug_client "$@" >>"$stdout_log" 2>>"$stderr_log"

4
deploy/frr/.env Normal file
View File

@@ -0,0 +1,4 @@
# RTR server endpoint used by deploy/frr/frr.conf.template.
RPKI_FRR_RPKI_HOST=127.0.0.1
RPKI_FRR_RPKI_PORT=323
RPKI_FRR_RPKI_PREFERENCE=1

78
deploy/frr/README.md Normal file
View File

@@ -0,0 +1,78 @@
# FRR Minimal RTR Client Config
中文文档: [README.zh.md](./README.zh.md)
This folder provides a minimal FRR setup for black-box interop testing
against this repository's RTR server defaults.
Server defaults in this repo:
- TCP: `0.0.0.0:323`
- TLS: `0.0.0.0:324`
Reference:
- `src/main.rs`
## Files
- `daemons.example`: sample `/etc/frr/daemons`
- `frr.conf.example`: static sample `/etc/frr/frr.conf`
- `frr.conf.template`: Docker template used to generate `/etc/frr/frr.conf`
- `entrypoint.sh`: renders `frr.conf.template` from environment variables before starting FRR
- `.env`: default Docker environment values
## How to apply on an FRR host
1. Copy `daemons.example` to `/etc/frr/daemons`.
2. Copy `frr.conf.example` to `/etc/frr/frr.conf`.
3. Restart FRR:
```bash
sudo systemctl restart frr
```
## Verify
```bash
vtysh -c "show rpki configuration"
vtysh -c "show rpki cache-server"
vtysh -c "show rpki cache-connection"
vtysh -c "show rpki prefix-table"
```
If `show rpki cache-connection` is connected and `show rpki prefix-table`
contains VRPs, the RTR client path is working.
## Docker quick start
From repository root:
```bash
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection"
docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table"
```
The Docker example defaults to `127.0.0.1:323`. Override the RTR server
endpoint with environment variables:
```bash
RPKI_FRR_RPKI_HOST=10.0.0.12 \
RPKI_FRR_RPKI_PORT=323 \
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
```
Stop:
```bash
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml down
```
## Notes
- This setup targets RTR over TCP (`rpki cache tcp`).
- Docker mode renders `frr.conf.template` into `/etc/frr/frr.conf` at startup.
- Keep protocol-level conformance checks in Rust tests and
`src/bin/rtr_debug_client`.
- `network_mode: host` is kept for the minimal FRR container example, but the
RTR server address is controlled by `RPKI_FRR_RPKI_HOST` and
`RPKI_FRR_RPKI_PORT`.

83
deploy/frr/README.zh.md Normal file
View File

@@ -0,0 +1,83 @@
# FRR 最小化 RTR 客户端配置
本目录提供一个 FRR 最小配置,用于和本仓库 RTR Server 做黑盒互通测试。
本仓库默认 RTR 监听地址:
- TCP:`0.0.0.0:323`
- TLS:`0.0.0.0:324`
参考实现:
- `src/main.rs`
## 文件说明
- `daemons.example`:示例 `/etc/frr/daemons`
- `frr.conf.example`:静态示例 `/etc/frr/frr.conf`
- `frr.conf.template`:Docker 启动时生成 `/etc/frr/frr.conf` 的模板
- `entrypoint.sh`:启动 FRR 前根据环境变量渲染 `frr.conf.template`
- `.env`:Docker 示例使用的默认环境变量
## 在 FRR 主机上应用
1. 复制 `daemons.example` 到 `/etc/frr/daemons`
2. 复制 `frr.conf.example` 到 `/etc/frr/frr.conf`
3. 重启 FRR:
```bash
sudo systemctl restart frr
```
## 验证命令
```bash
vtysh -c "show rpki configuration"
vtysh -c "show rpki cache-server"
vtysh -c "show rpki cache-connection"
vtysh -c "show rpki prefix-table"
```
当 `show rpki cache-connection` 显示已连接,且 `show rpki prefix-table` 出现 VRP 时,说明 RTR 客户端链路工作正常。
## Docker 快速启动
在仓库根目录执行:
```bash
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
docker exec -it frr-rpki-client vtysh -c "show rpki cache-connection"
docker exec -it frr-rpki-client vtysh -c "show rpki prefix-table"
```
Docker 示例默认连接 `127.0.0.1:323`。如果 RTR Server 在其他地址,可以通过环境变量覆盖:
```bash
RPKI_FRR_RPKI_HOST=10.0.0.12 \
RPKI_FRR_RPKI_PORT=323 \
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml up -d
```
停止:
```bash
docker compose --env-file deploy/frr/.env -f deploy/frr/docker-compose.yml down
```
## 可测试范围
- FRR 作为 RTR Client 与本仓库 Server 的 TCP 建链能力
- FRR 侧基础会话状态可见性(`cache-server` / `cache-connection`)
- VRP 下发与导入是否成功(`prefix-table` 是否有条目)
- 基于服务端数据变化触发的前缀表更新(可通过替换 `data` 后观察)
- 黑盒互通回归:用于确认“路由器客户端视角”功能可用
## 不覆盖范围
- 不替代协议级单元测试/集成测试(PDU 细节、异常路径、边界条件)
- 不替代 `src/bin/rtr_debug_client` 的逐报文调试能力
- 默认示例以 TCP 为主,TLS/mTLS 需按你的证书与 FRR 配置单独扩展
## 说明
- 当前示例主要针对 RTR over TCP(`rpki cache tcp`)
- Docker 模式会在启动时把 `frr.conf.template` 渲染为 `/etc/frr/frr.conf`
- `network_mode: host` 仍用于最小化 FRR 容器示例,但 RTR Server 地址由 `RPKI_FRR_RPKI_HOST` 和 `RPKI_FRR_RPKI_PORT` 控制

View File

@@ -0,0 +1,7 @@
# Minimal FRR daemons config for RPKI testing
zebra=yes
bgpd=yes
# Enable bgpd RPKI module
bgpd_options=" -A 127.0.0.1 -M rpki"

View File

@@ -0,0 +1,16 @@
services:
frr-rpki-client:
image: quay.io/frrouting/frr:10.2.2
container_name: frr-rpki-client
restart: unless-stopped
network_mode: host
privileged: true
environment:
RPKI_FRR_RPKI_HOST: "${RPKI_FRR_RPKI_HOST:-127.0.0.1}"
RPKI_FRR_RPKI_PORT: "${RPKI_FRR_RPKI_PORT:-323}"
RPKI_FRR_RPKI_PREFERENCE: "${RPKI_FRR_RPKI_PREFERENCE:-1}"
entrypoint: ["/bin/sh", "/config/entrypoint.sh"]
volumes:
- ./daemons.example:/etc/frr/daemons:ro
- ./frr.conf.template:/config/frr.conf.template:ro
- ./entrypoint.sh:/config/entrypoint.sh:ro

24
deploy/frr/entrypoint.sh Normal file
View File

@@ -0,0 +1,24 @@
#!/bin/sh
set -eu
: "${RPKI_FRR_RPKI_HOST:=127.0.0.1}"
: "${RPKI_FRR_RPKI_PORT:=323}"
: "${RPKI_FRR_RPKI_PREFERENCE:=1}"
: "${RPKI_FRR_CONFIG_TEMPLATE_PATH:=/config/frr.conf.template}"
: "${RPKI_FRR_CONFIG_PATH:=/etc/frr/frr.conf}"
escape_sed() {
printf '%s' "$1" | sed 's/[&|\\]/\\&/g'
}
host="$(escape_sed "${RPKI_FRR_RPKI_HOST}")"
port="$(escape_sed "${RPKI_FRR_RPKI_PORT}")"
preference="$(escape_sed "${RPKI_FRR_RPKI_PREFERENCE}")"
sed \
-e "s|\${RPKI_FRR_RPKI_HOST}|${host}|g" \
-e "s|\${RPKI_FRR_RPKI_PORT}|${port}|g" \
-e "s|\${RPKI_FRR_RPKI_PREFERENCE}|${preference}|g" \
"${RPKI_FRR_CONFIG_TEMPLATE_PATH}" > "${RPKI_FRR_CONFIG_PATH}"
exec /usr/lib/frr/docker-start

View File

@@ -0,0 +1,22 @@
frr version 10.2
frr defaults traditional
hostname rpki-lab
service integrated-vtysh-config
!
debug rpki
!
rpki
rpki polling_period 10
rpki timeout 10
rpki retry_interval 10
rpki expire_interval 7200
rpki cache tcp 127.0.0.1 323 preference 1
exit
!
router bgp 65001
bgp router-id 192.0.2.1
!
address-family ipv4 unicast
exit-address-family
!
line vty

View File

@@ -0,0 +1,22 @@
frr version 10.2
frr defaults traditional
hostname rpki-lab
service integrated-vtysh-config
!
debug rpki
!
rpki
rpki polling_period 10
rpki timeout 10
rpki retry_interval 10
rpki expire_interval 7200
rpki cache tcp ${RPKI_FRR_RPKI_HOST} ${RPKI_FRR_RPKI_PORT} preference ${RPKI_FRR_RPKI_PREFERENCE}
exit
!
router bgp 65001
bgp router-id 192.0.2.1
!
address-family ipv4 unicast
exit-address-family
!
line vty

54
deploy/server/.env Normal file
View File

@@ -0,0 +1,54 @@
# Core runtime knobs.
# build-run.sh auto-detects amd64/arm64 unless IMAGE_ARCH/RPKI_RTR_IMAGE_ARCH is set before running it.
# Direct docker compose uses RPKI_RTR_IMAGE_ARCH from this file; set it to arm64 on ARM hosts.
RPKI_RTR_SERVER_IMAGE_BASE=rpki-rtr
RPKI_RTR_SERVER_IMAGE_TAG=v1.3
RPKI_RTR_IMAGE_ARCH=arm64
# RPKI_RTR_TARGET_PLATFORM=linux/amd64
# RPKI_RTR_SERVER_IMAGE=rpki-rtr-amd64:v1.3
# Data source directories on host.
RPKI_RTR_CCR_HOST_DIR=../../data
RPKI_RTR_SLURM_HOST_DIR=../../data
# In-container data source directories.
RPKI_RTR_CCR_DIR=/app/data
RPKI_RTR_SLURM_DIR=/app/slurm
RPKI_RTR_REPORT_HOST_DIR=../../report
# In-container runtime paths.
RPKI_RTR_DB_PATH=/app/rtr-db
RPKI_RTR_REPORT_DIR=/app/report
# Persistent directories on host.
RPKI_RTR_DB_HOST_DIR=../../rtr-db
RPKI_RTR_LOG_HOST_DIR=../../logs/server
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS=300
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS=300
RPKI_RTR_REPORT_HISTORY_LIMIT=10
RPKI_RTR_TIMEZONE=Asia/Shanghai
RPKI_RTR_MAX_DELTA=10
RPKI_RTR_MAX_CONNECTIONS=100000
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES=128
RPKI_RTR_ADMIN_ADDR=0.0.0.0:8323
RPKI_RTR_ADMIN_TOKEN=qwert
RUST_LOG=info
# TLS mode knobs.
RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH=false
RPKI_RTR_TLS_CERT_PATH=/app/certs/server-dns.crt
RPKI_RTR_TLS_KEY_PATH=/app/certs/server-dns.key
RPKI_RTR_TLS_CLIENT_CA_PATH=/app/certs/client-ca.crt
RPKI_RTR_TLS_CERTS_HOST_DIR=../../certs/tls
# SSH mode knobs.
RPKI_RTR_SSH_HOST_PORT=2222
RPKI_RTR_SSH_CONTAINER_PORT=22
RPKI_RTR_SSH_AUTH_MODE=key
RPKI_RTR_SSH_USERNAME=rpki-rtr
RPKI_RTR_SSH_SUBSYSTEM_NAME=rpki-rtr
RPKI_RTR_SSH_HOST_KEY_PATH=/app/certs/ssh_host_rsa_key
RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH=/app/certs/rtr-authorized_keys
RPKI_RTR_SSH_CERTS_HOST_DIR=../../certs/ssh

View File

@@ -0,0 +1,99 @@
# Deployment (Docker Compose)
This project runs `src/main.rs` as a long-running server that:
1. loads latest `.ccr` from a configured directory,
2. applies optional SLURM filtering,
3. starts RTR server.
The container runs `rpki` directly as PID 1.
## Files
- `deploy/server/Dockerfile`
- `deploy/server/docker-compose.yml`
## Runtime Paths in Container
- CCR directory: `/app/data`
- RocksDB directory: `/app/rtr-db`
- SLURM directory: `/app/slurm`
- Report directory: `/app/report`
- TLS cert directory (optional): `/app/certs`
## Path Configuration via `.env`
- `RPKI_RTR_CCR_HOST_DIR`: host CCR directory mounted into container
- `RPKI_RTR_SLURM_HOST_DIR`: host SLURM directory mounted into container
- `RPKI_RTR_CCR_DIR`: in-container CCR directory path
- `RPKI_RTR_SLURM_DIR`: in-container SLURM directory path
- `RPKI_RTR_DB_HOST_DIR`: host RocksDB directory
- `RPKI_RTR_LOG_HOST_DIR`: host log directory
- `RPKI_RTR_REPORT_HOST_DIR`: host directory receiving split RTR JSON reports
- `RPKI_RTR_DB_PATH`: in-container RocksDB directory
- `RPKI_RTR_REPORT_DIR`: in-container report directory
## Runtime Configuration via `.env`
- Core: `RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS`, `RPKI_RTR_MAX_DELTA`, `RPKI_RTR_MAX_CONCURRENT_HANDSHAKES`, `RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS`, `RPKI_RTR_REPORT_HISTORY_LIMIT`, `RPKI_RTR_TIMEZONE`, `RPKI_RTR_ADMIN_ADDR`, `RPKI_RTR_ADMIN_TOKEN`, `RUST_LOG`
- TCP mode: `RPKI_RTR_MAX_CONNECTIONS`
- TLS mode: `RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH`, `RPKI_RTR_TLS_CERT_PATH`, `RPKI_RTR_TLS_KEY_PATH`, `RPKI_RTR_TLS_CLIENT_CA_PATH`, `RPKI_RTR_TLS_CERTS_HOST_DIR`
- SSH mode: `RPKI_RTR_SSH_HOST_PORT`, `RPKI_RTR_SSH_CONTAINER_PORT`, `RPKI_RTR_SSH_AUTH_MODE`, `RPKI_RTR_SSH_USERNAME`, `RPKI_RTR_SSH_SUBSYSTEM_NAME`, `RPKI_RTR_SSH_HOST_KEY_PATH`, `RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH`, `RPKI_RTR_SSH_CERTS_HOST_DIR`
## Start
```bash
docker compose -f deploy/server/docker-compose.yml up -d --build
```
## Stop
```bash
docker compose -f deploy/server/docker-compose.yml down
```
## Logs
```bash
docker compose -f deploy/server/docker-compose.yml logs -f rpki-rtr
```
The admin API can also stream the redirected log file:
```bash
curl -N "http://127.0.0.1:8323/admin/rtr/logs/tail?stream=stdout&lines=200" \
-H "Authorization: Bearer $RPKI_RTR_ADMIN_TOKEN"
```
It reads `/app/logs/${HOSTNAME}.stdout.log` or `.stderr.log` by default. Set
`RPKI_RTR_LOG_DIR` and `RPKI_RTR_LOG_NAME` to override that lookup.
## Runtime Report
The server writes split JSON reports. Each report file uses a local-time
timestamp suffix and each category keeps `RPKI_RTR_REPORT_HISTORY_LIMIT` files,
defaulting to 10.
- `rtr-source-*.json`: CCR and SLURM source metadata, latest refresh status,
data quality counts, cache snapshot counts, and delta counts. Written on
startup and source refresh events.
- `rtr-clients-*.json`: active client connection counts and counts by transport
(`tcp`, `tls`, `ssh`). Written on startup, whenever the active connection
count changes.
- `rtr-runtime-*.json`: service start time, uptime, process RSS, and
non-sensitive runtime configuration. Written on startup and every
`RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS`, defaulting to 300 seconds.
Timestamps in logs and report JSON files use `RPKI_RTR_TIMEZONE`, which
defaults to `Asia/Shanghai`. Use IANA timezone names such as `Asia/Shanghai`,
`Europe/London`, `America/New_York`, or `UTC`; `Shanghai` is accepted as a
convenience alias for `Asia/Shanghai`.
## Runtime Admin Config
The admin endpoint is disabled by default. Set `RPKI_RTR_ADMIN_ADDR` to enable
`POST /admin/rtr/config`. If the address is not loopback, `RPKI_RTR_ADMIN_TOKEN`
must also be set and requests must include `Authorization: Bearer <token>`.
The endpoint accepts partial JSON updates. See `docs/rtr-admin-api.md` for the
complete request/response schema, examples, and runtime apply semantics.

85
deploy/server/Dockerfile Normal file
View File

@@ -0,0 +1,85 @@
FROM rust:1.89-bookworm AS builder
WORKDIR /build
ENV CARGO_BUILD_JOBS=2
RUN set -eux; \
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
Types: deb
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian
Suites: bookworm bookworm-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian-security
Suites: bookworm-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
RUN apt-get update \
&& apt-get install -y --fix-missing --no-install-recommends \
-o Acquire::Retries=10 \
-o Acquire::http::Timeout=60 \
build-essential \
cmake \
pkg-config \
clang \
libclang-dev \
libssl-dev \
&& rm -rf /var/lib/apt/lists/*
COPY Cargo.toml Cargo.lock ./
COPY src ./src
RUN cargo build --release --bin rpki_rtr
FROM debian:bookworm-slim AS runtime
RUN set -eux; \
cat > /etc/apt/sources.list.d/debian.sources <<'EOF'
Types: deb
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian
Suites: bookworm bookworm-updates
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
Types: deb
URIs: http://mirrors.tuna.tsinghua.edu.cn/debian-security
Suites: bookworm-security
Components: main
Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
EOF
RUN apt-get update \
&& apt-get install -y --fix-missing --no-install-recommends \
-o Acquire::Retries=10 \
-o Acquire::http::Timeout=60 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /build/target/release/rpki_rtr /usr/local/bin/rpki_rtr
COPY --chmod=755 deploy/server/entrypoint.sh /usr/local/bin/rpki-rtr-entrypoint.sh
RUN mkdir -p /app/data /app/rtr-db /app/certs /app/slurm /app/logs /app/report
ENV RPKI_RTR_ENABLE_TLS=false \
RPKI_RTR_TCP_ADDR=0.0.0.0:323 \
RPKI_RTR_TLS_ADDR=0.0.0.0:324 \
RPKI_RTR_DB_PATH=/app/rtr-db \
RPKI_RTR_CCR_DIR=/app/data \
RPKI_RTR_SLURM_DIR=/app/slurm \
RPKI_RTR_REPORT_DIR=/app/report \
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS=300 \
RPKI_RTR_REPORT_HISTORY_LIMIT=10 \
RPKI_RTR_REFRESH_INTERVAL_SECS=300 \
RPKI_RTR_ADMIN_ADDR="" \
RPKI_RTR_ADMIN_TOKEN=""
EXPOSE 323 324
ENTRYPOINT ["/usr/local/bin/rpki-rtr-entrypoint.sh"]

292
deploy/server/build-run.sh Normal file
View File

@@ -0,0 +1,292 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "${SCRIPT_DIR}/../.." && pwd)"
CALLER_TARGET_PLATFORM="${TARGET_PLATFORM:-${RPKI_RTR_TARGET_PLATFORM:-}}"
CALLER_IMAGE_ARCH="${IMAGE_ARCH:-${RPKI_RTR_IMAGE_ARCH:-}}"
CALLER_SERVER_IMAGE="${SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE:-}}"
load_env_defaults() {
local line key value
[[ -f "${SCRIPT_DIR}/.env" ]] || return
while IFS= read -r line || [[ -n "${line}" ]]; do
line="${line%$'\r'}"
[[ -z "${line}" || "${line}" =~ ^[[:space:]]*# ]] && continue
[[ "${line}" == *"="* ]] || continue
key="${line%%=*}"
value="${line#*=}"
key="${key#"${key%%[![:space:]]*}"}"
key="${key%"${key##*[![:space:]]}"}"
[[ "${key}" =~ ^[A-Za-z_][A-Za-z0-9_]*$ ]] || continue
if [[ -z "${!key+x}" ]]; then
export "${key}=${value}"
fi
done < "${SCRIPT_DIR}/.env"
}
detect_image_arch() {
case "$(uname -m)" in
x86_64|amd64) printf '%s\n' amd64 ;;
aarch64|arm64) printf '%s\n' arm64 ;;
*) die "unsupported host architecture '$(uname -m)'; set RPKI_RTR_IMAGE_ARCH=amd64 or arm64" ;;
esac
}
platform_for_image_arch() {
case "$1" in
amd64) printf '%s\n' linux/amd64 ;;
arm64) printf '%s\n' linux/arm64 ;;
*) die "unsupported image arch '$1', expected amd64 or arm64" ;;
esac
}
image_arch_from_platform() {
case "$1" in
linux/amd64|amd64) printf '%s\n' amd64 ;;
linux/arm64|arm64) printf '%s\n' arm64 ;;
*) return 1 ;;
esac
}
load_env_defaults
MODE="${MODE:-tcp}"
ACTION="up"
DETACH="${DETACH:-1}"
BUILD="${BUILD:-1}"
TARGET_PLATFORM="${CALLER_TARGET_PLATFORM}"
IMAGE_ARCH="${CALLER_IMAGE_ARCH}"
SERVER_IMAGE="${CALLER_SERVER_IMAGE}"
BUILDX_PUSH="${BUILDX_PUSH:-0}"
BUILDX_BUILDER="${BUILDX_BUILDER:-}"
SKIP_BASE_IMAGE_PULL="${SKIP_BASE_IMAGE_PULL:-0}"
usage() {
cat <<'EOF'
Usage:
deploy/server/build-run.sh [action] [options]
Actions:
up Build and start server (default)
build Build server image with docker compose
buildx Build server image with docker buildx
rebuild Rebuild image without cache, then start server
down|stop Stop server compose stack
restart Stop, then build and start server
logs Follow server logs
ps Show server compose status
Options:
--mode MODE base|tcp|tls|ssh (default: tcp)
--platform VALUE Target platform for buildx (default: host architecture)
--image-arch VALUE
Image architecture suffix for default tag: amd64|arm64
--server-image TAG
Server image tag for buildx and compose
--push Push buildx image instead of loading it locally
--builder NAME buildx builder name
--skip-base-pull Do not pull Dockerfile base images before buildx
--foreground Run server in foreground
--no-build Start without --build
-h|--help Show this help
EOF
}
die() {
echo "error: $*" >&2
exit 1
}
has_compose() {
docker compose version >/dev/null 2>&1
}
has_buildx() {
docker buildx version >/dev/null 2>&1
}
pull_base_images() {
[[ "${SKIP_BASE_IMAGE_PULL}" == "1" ]] && return
local platform image
local -a platforms images
IFS=',' read -r -a platforms <<< "${TARGET_PLATFORM}"
images=(rust:1.89-bookworm debian:bookworm-slim)
for platform in "${platforms[@]}"; do
for image in "${images[@]}"; do
docker pull --platform "${platform}" "${image}"
done
done
}
compose_files() {
case "${MODE}" in
base) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.yml" ;;
tcp) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tcp.yml" ;;
tls) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.tls.yml" ;;
ssh) printf '%s\n' -f "${SCRIPT_DIR}/docker-compose.ssh.yml" ;;
*) die "unsupported mode '${MODE}', expected base|tcp|tls|ssh" ;;
esac
}
compose_args() {
local -a args files
args=()
[[ -f "${SCRIPT_DIR}/.env" ]] && args+=(--env-file "${SCRIPT_DIR}/.env")
mapfile -t files < <(compose_files)
args+=("${files[@]}")
printf '%s\n' "${args[@]}"
}
compose_run() {
local -a args
mapfile -t args < <(compose_args)
docker compose "${args[@]}" "$@"
}
up_server() {
local -a up_args
up_args=(up)
[[ "${DETACH}" == "1" ]] && up_args+=(-d)
[[ "${BUILD}" == "1" ]] && up_args+=(--build)
compose_run "${up_args[@]}"
}
buildx_server() {
has_buildx || die "docker buildx is not available"
if [[ "${TARGET_PLATFORM}" == *,* && "${BUILDX_PUSH}" != "1" ]]; then
die "multi-platform buildx output requires --push"
fi
local -a output_args builder_args
[[ "${BUILDX_PUSH}" == "1" ]] && output_args=(--push) || output_args=(--load)
[[ -n "${BUILDX_BUILDER}" ]] && builder_args=(--builder "${BUILDX_BUILDER}") || builder_args=()
pull_base_images
docker buildx build "${builder_args[@]}" \
--platform "${TARGET_PLATFORM}" \
-f "${SCRIPT_DIR}/Dockerfile" \
-t "${SERVER_IMAGE}" \
"${output_args[@]}" \
"${REPO_ROOT}"
}
while [[ $# -gt 0 ]]; do
case "$1" in
up|build|buildx|rebuild|down|stop|restart|logs|ps)
ACTION="$1"
shift
;;
--mode)
[[ $# -ge 2 ]] || die "--mode requires a value"
MODE="$2"
shift 2
;;
--platform)
[[ $# -ge 2 ]] || die "--platform requires a value"
TARGET_PLATFORM="$2"
shift 2
;;
--image-arch)
[[ $# -ge 2 ]] || die "--image-arch requires a value"
IMAGE_ARCH="$2"
shift 2
;;
--server-image)
[[ $# -ge 2 ]] || die "--server-image requires a value"
SERVER_IMAGE="$2"
shift 2
;;
--push)
BUILDX_PUSH=1
shift
;;
--builder)
[[ $# -ge 2 ]] || die "--builder requires a value"
BUILDX_BUILDER="$2"
shift 2
;;
--skip-base-pull)
SKIP_BASE_IMAGE_PULL=1
shift
;;
--foreground)
DETACH=0
shift
;;
--no-build)
BUILD=0
shift
;;
-h|--help)
usage
exit 0
;;
*)
die "unknown argument '$1'"
;;
esac
done
if [[ -z "${TARGET_PLATFORM}" ]]; then
[[ -n "${IMAGE_ARCH}" ]] || IMAGE_ARCH="$(detect_image_arch)"
TARGET_PLATFORM="$(platform_for_image_arch "${IMAGE_ARCH}")"
elif [[ -z "${IMAGE_ARCH}" ]]; then
if [[ "${TARGET_PLATFORM}" != *,* ]] && IMAGE_ARCH="$(image_arch_from_platform "${TARGET_PLATFORM}")"; then
:
else
IMAGE_ARCH="$(detect_image_arch)"
fi
fi
if [[ -z "${SERVER_IMAGE}" ]]; then
if [[ "${TARGET_PLATFORM}" == *,* ]]; then
SERVER_IMAGE="${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}"
else
SERVER_IMAGE="${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${IMAGE_ARCH}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}"
fi
fi
export RPKI_RTR_SERVER_IMAGE="${SERVER_IMAGE}"
if [[ "${ACTION}" == "buildx" ]]; then
has_buildx || die "docker buildx is not available"
else
has_compose || die "docker compose is not available"
fi
cd "${REPO_ROOT}"
case "${ACTION}" in
up)
up_server
;;
build)
compose_run build
;;
buildx)
buildx_server
;;
rebuild)
compose_run build --no-cache
up_server
;;
down|stop)
compose_run down
;;
restart)
compose_run down
up_server
;;
logs)
compose_run logs -f
;;
ps)
compose_run ps
;;
esac

View File

@@ -0,0 +1,54 @@
version: "3.9"
services:
rpki-rtr:
build:
context: ../..
dockerfile: deploy/server/Dockerfile
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
container_name: rpki-rtr-ssh
restart: no
ports:
- "323:323"
- "${RPKI_RTR_SSH_HOST_PORT:-2222}:${RPKI_RTR_SSH_CONTAINER_PORT:-22}"
- "8323:8323"
environment:
RPKI_RTR_ENABLE_TLS: "false"
RPKI_RTR_ENABLE_SSH: "true"
RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
RPKI_RTR_SSH_ADDR: "0.0.0.0:${RPKI_RTR_SSH_CONTAINER_PORT:-22}"
RPKI_RTR_SSH_HOST_KEY_PATH: "${RPKI_RTR_SSH_HOST_KEY_PATH:-/app/certs/ssh_host_rsa_key}"
RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH: "${RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH:-/app/certs/rtr-authorized_keys}"
RPKI_RTR_SSH_USERNAME: "${RPKI_RTR_SSH_USERNAME:-rpki-rtr}"
RPKI_RTR_SSH_SUBSYSTEM_NAME: "${RPKI_RTR_SSH_SUBSYSTEM_NAME:-rpki-rtr}"
# SSH auth mode: key | password | both
RPKI_RTR_SSH_AUTH_MODE: "${RPKI_RTR_SSH_AUTH_MODE:-key}"
# Optional: enable password authentication in addition to publickey
# RPKI_RTR_SSH_PASSWORD: "test-password"
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}"
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
RUST_LOG: "${RUST_LOG:-info}"
volumes:
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
- ${RPKI_RTR_SSH_CERTS_HOST_DIR:-../../certs/ssh}:/app/certs:ro
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,44 @@
version: "3.9"
services:
rpki-rtr:
build:
context: ../..
dockerfile: deploy/server/Dockerfile
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
container_name: rpki-rtr-tcp
restart: no
ports:
- "323:323"
- "8323:8323"
environment:
RPKI_RTR_ENABLE_TLS: "false"
RPKI_RTR_ENABLE_SSH: "false"
RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-60}"
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
RPKI_RTR_MAX_CONNECTIONS: "${RPKI_RTR_MAX_CONNECTIONS:-100000}"
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
RUST_LOG: "${RUST_LOG:-info}"
volumes:
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,48 @@
services:
rpki-rtr:
build:
context: ../..
dockerfile: deploy/server/Dockerfile
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
container_name: rpki-rtr-tls
restart: no
ports:
# - "323:323"
- "324:324"
- "8323:8323"
environment:
RPKI_RTR_ENABLE_TLS: "true"
RPKI_RTR_ENABLE_SSH: "false"
# RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
RPKI_RTR_TLS_ADDR: "0.0.0.0:324"
RPKI_RTR_TLS_CERT_PATH: "${RPKI_RTR_TLS_CERT_PATH:-/app/certs/server-dns.crt}"
RPKI_RTR_TLS_KEY_PATH: "${RPKI_RTR_TLS_KEY_PATH:-/app/certs/server-dns.key}"
RPKI_RTR_TLS_CLIENT_CA_PATH: "${RPKI_RTR_TLS_CLIENT_CA_PATH:-/app/certs/client-ca.crt}"
RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH: "${RPKI_RTR_ENFORCE_TLS_CLIENT_SAN_IP_MATCH:-false}"
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}"
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
RUST_LOG: "${RUST_LOG:-info}"
volumes:
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
- ${RPKI_RTR_TLS_CERTS_HOST_DIR:-../../certs/tls}:/app/certs:ro
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,58 @@
version: "3.9"
services:
rpki-rtr:
build:
context: ../..
dockerfile: deploy/server/Dockerfile
image: ${RPKI_RTR_SERVER_IMAGE:-${RPKI_RTR_SERVER_IMAGE_BASE:-rpki-rtr}-${RPKI_RTR_IMAGE_ARCH:-amd64}:${RPKI_RTR_SERVER_IMAGE_TAG:-latest}}
container_name: rpki-rtr
restart: no
ports:
- "323:323"
- "324:324"
- "8323:8323"
# SSH mode example:
# - "22:22"
environment:
RPKI_RTR_ENABLE_TLS: "false"
RPKI_RTR_TCP_ADDR: "0.0.0.0:323"
RPKI_RTR_TLS_ADDR: "0.0.0.0:324"
RPKI_RTR_DB_PATH: "${RPKI_RTR_DB_PATH:-/app/rtr-db}"
RPKI_RTR_CCR_DIR: "${RPKI_RTR_CCR_DIR:-/app/data}"
RPKI_RTR_SLURM_DIR: "${RPKI_RTR_SLURM_DIR:-/app/slurm}"
RPKI_RTR_REPORT_DIR: "${RPKI_RTR_REPORT_DIR:-/app/report}"
RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS: "${RPKI_RTR_RUNTIME_REPORT_INTERVAL_SECS:-300}"
RPKI_RTR_REPORT_HISTORY_LIMIT: "${RPKI_RTR_REPORT_HISTORY_LIMIT:-10}"
RPKI_RTR_TIMEZONE: "${RPKI_RTR_TIMEZONE:-Asia/Shanghai}"
RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS: "${RPKI_RTR_SOURCE_REFRESH_INTERVAL_SECS:-300}"
RPKI_RTR_MAX_DELTA: "${RPKI_RTR_MAX_DELTA:-10}"
RPKI_RTR_MAX_CONCURRENT_HANDSHAKES: "${RPKI_RTR_MAX_CONCURRENT_HANDSHAKES:-128}"
RPKI_RTR_ADMIN_ADDR: "${RPKI_RTR_ADMIN_ADDR:-}"
RPKI_RTR_ADMIN_TOKEN: "${RPKI_RTR_ADMIN_TOKEN:-}"
RUST_LOG: "${RUST_LOG:-info}"
# SSH mode example:
# RPKI_RTR_ENABLE_SSH: "true"
# RPKI_RTR_SSH_ADDR: "0.0.0.0:22"
# RPKI_RTR_SSH_PORT: "22"
# RPKI_RTR_SSH_HOST_KEY_PATH: "/app/certs/ssh_host_rsa_key"
# RPKI_RTR_SSH_AUTHORIZED_KEYS_PATH: "/app/certs/rtr-authorized_keys"
# RPKI_RTR_SSH_USERNAME: "rpki-rtr"
# RPKI_RTR_SSH_SUBSYSTEM_NAME: "rpki-rtr"
# Optional: enable password auth in addition to publickey
# RPKI_RTR_SSH_PASSWORD: "test-password"
volumes:
- ${RPKI_RTR_CCR_HOST_DIR:-../../data}:${RPKI_RTR_CCR_DIR:-/app/data}:ro
- ${RPKI_RTR_DB_HOST_DIR:-../../rtr-db}:${RPKI_RTR_DB_PATH:-/app/rtr-db}
- ${RPKI_RTR_SLURM_HOST_DIR:-../../data}:${RPKI_RTR_SLURM_DIR:-/app/slurm}
- ${RPKI_RTR_LOG_HOST_DIR:-../../logs/server}:/app/logs
- ${RPKI_RTR_REPORT_HOST_DIR:-../../report}:${RPKI_RTR_REPORT_DIR:-/app/report}
# TLS mode example:
# - ../../certs/ssh:/app/certs:ro
networks:
- rpki_net
networks:
rpki_net:
name: rpki_net
driver: bridge

View File

@@ -0,0 +1,10 @@
#!/bin/sh
set -eu
mkdir -p /app/logs
log_name="${HOSTNAME:-rpki-rtr}"
stdout_log="/app/logs/${log_name}.stdout.log"
stderr_log="/app/logs/${log_name}.stderr.log"
exec /usr/local/bin/rpki_rtr "$@" >>"$stdout_log" 2>>"$stderr_log"

View File

@@ -1,94 +0,0 @@
ARG BUILDER_IMAGE=ours-rp-base-rust-amd64:1-bookworm
ARG RUNTIME_IMAGE=ours-rp-base-debian-arm64:bookworm-slim
FROM --platform=$BUILDPLATFORM ${BUILDER_IMAGE} AS builder
WORKDIR /src
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
clang \
cmake \
g++-aarch64-linux-gnu \
gcc-aarch64-linux-gnu \
git \
libclang-dev \
libc6-dev-arm64-cross \
make \
perl \
pkg-config \
python3 \
&& rm -rf /var/lib/apt/lists/* \
&& rustup target add aarch64-unknown-linux-gnu
ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc \
CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc \
CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ \
AR_aarch64_unknown_linux_gnu=aarch64-linux-gnu-ar \
CARGO_BUILD_TARGET=aarch64-unknown-linux-gnu \
PKG_CONFIG_ALLOW_CROSS=1
COPY . .
RUN --mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
--mount=type=cache,target=/src/target \
cargo build --release --target aarch64-unknown-linux-gnu \
--bin rpki \
--bin rpki_daemon \
--bin db_stats \
--bin rpki_artifact_metrics \
&& mkdir -p /build-out/bin \
&& cp \
target/aarch64-unknown-linux-gnu/release/rpki \
target/aarch64-unknown-linux-gnu/release/rpki_daemon \
target/aarch64-unknown-linux-gnu/release/db_stats \
target/aarch64-unknown-linux-gnu/release/rpki_artifact_metrics \
/build-out/bin/
FROM --platform=$TARGETPLATFORM ${RUNTIME_IMAGE} AS runtime
LABEL org.opencontainers.image.title="ours-rp-runtime" \
org.opencontainers.image.description="Ours RP runtime image for ARM64 Docker Compose deployment"
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
bash \
ca-certificates \
coreutils \
curl \
findutils \
iputils-ping \
jq \
procps \
python3 \
rsync \
time \
tzdata \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /opt/ours-rp
COPY --from=builder /build-out/bin/ /opt/ours-rp/bin/
COPY scripts/soak/run_soak.sh /opt/ours-rp/run_soak.sh
COPY scripts/soak/portable-soak.env.example /opt/ours-rp/portable-soak.env.example
COPY tests/fixtures/tal/ /opt/ours-rp/fixtures/tal/
COPY tests/fixtures/ta/ /opt/ours-rp/fixtures/ta/
COPY fixtures/live_20260619/tal/ /opt/ours-rp/fixtures/live_20260619/tal/
COPY fixtures/live_20260619/ta/ /opt/ours-rp/fixtures/live_20260619/ta/
RUN chmod +x /opt/ours-rp/run_soak.sh /opt/ours-rp/bin/* \
&& mkdir -p /var/lib/ours-rp/state /var/lib/ours-rp/runs /var/lib/ours-rp/logs /var/lib/ours-rp/tmp
ENV PACKAGE_ROOT=/opt/ours-rp \
ENV_FILE=/opt/ours-rp/.env \
RUN_ROOT=/var/lib/ours-rp \
BIN_DIR=/opt/ours-rp/bin \
FIXTURE_DIR=/opt/ours-rp/fixtures \
RUST_BACKTRACE=1
VOLUME ["/var/lib/ours-rp"]
CMD ["/opt/ours-rp/run_soak.sh"]

310
docs/rtr-admin-api.md Normal file
View File

@@ -0,0 +1,310 @@
# RTR Admin API
本文档描述 RTR server 当前提供的 HTTP 管理接口。
## 启用方式
Admin API 默认关闭。设置 `RPKI_RTR_ADMIN_ADDR` 后启用:
```env
RPKI_RTR_ADMIN_ADDR=127.0.0.1:8323
RPKI_RTR_ADMIN_TOKEN=change-me
```
如果在 Docker 中需要从宿主机访问,容器内建议监听:
```env
RPKI_RTR_ADMIN_ADDR=0.0.0.0:8323
RPKI_RTR_ADMIN_TOKEN=change-me
```
并在 compose 中映射端口:
```yaml
ports:
- "8323:8323"
```
安全规则:
- `RPKI_RTR_ADMIN_ADDR` 为空时,不启动 Admin API。
- 非 loopback 地址,例如 `0.0.0.0:8323`,必须配置 `RPKI_RTR_ADMIN_TOKEN`,否则 admin server 会拒绝启动。
- 设置 token 后,请求必须带 `Authorization: Bearer <token>`。
## 通用 Headers
```http
Authorization: Bearer change-me
Content-Type: application/json
```
GET 请求不需要 `Content-Type`。
## GET /admin/rtr/health
用于检查 Admin API 是否可达,以及当前启用了哪些管理能力。
```bash
curl http://127.0.0.1:8323/admin/rtr/health \
-H "Authorization: Bearer change-me"
```
响应示例:
```json
{
"status": "ok",
"config_api": true,
"source_reload_api": true,
"slurm_api": true,
"logs_api": true
}
```
## GET /admin/rtr/logs/tail
实时读取 RTR 服务日志,效果类似 `tail -f`。默认读取 stdout 日志,先返回最近 200 行,然后持续输出新增内容。
```bash
curl -N "http://127.0.0.1:8323/admin/rtr/logs/tail?stream=stdout&lines=200&follow=true" \
-H "Authorization: Bearer change-me"
```
Query 参数:
| 参数 | 默认值 | 说明 |
| --- | --- | --- |
| `stream` | `stdout` | 可选 `stdout` 或 `stderr`。 |
| `lines` | `200` | 首次返回的历史行数,范围会限制在 `1..=5000`。 |
| `follow` | `true` | `true` 时保持连接并持续输出新增日志;`false` 时只返回当前 tail 内容。 |
日志文件路径按容器 entrypoint 的规则解析:
- 目录:`RPKI_RTR_LOG_DIR`,默认 `/app/logs`。
- 文件名前缀:`RPKI_RTR_LOG_NAME`,未设置时使用 `HOSTNAME`,再未设置时为 `rpki-rtr`。
- stdout 文件:`<name>.stdout.log`。
- stderr 文件:`<name>.stderr.log`。
## GET /admin/rtr/config
查询当前运行中的 runtime 配置。
```bash
curl http://127.0.0.1:8323/admin/rtr/config \
-H "Authorization: Bearer change-me"
```
响应示例:
```json
{
"status": "ok",
"config": {
"max_delta": 10,
"prune_delta_by_snapshot_size": true,
"source_refresh_interval_seconds": 300,
"runtime_report_interval_seconds": 300,
"report_history_limit": 10,
"timezone": "Asia/Shanghai",
"timing": {
"refresh": 3600,
"retry": 600,
"expire": 7200
}
}
}
```
## POST /admin/rtr/config
运行中动态修改 RTR server 的部分 runtime 配置。请求体是 JSON object,支持部分更新,只需要传要修改的字段。
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| `max_delta` | integer | 每个 RTR 版本最多保留的 delta 条数,必须 `>= 1`。 |
| `prune_delta_by_snapshot_size` | boolean | 是否按 snapshot wire size 裁剪 delta window。 |
| `source_refresh_interval_seconds` | integer | CCR/SLURM source refresh 间隔,单位秒,必须 `>= 1`。 |
| `runtime_report_interval_seconds` | integer | `rtr-runtime-*.json` 周期写入间隔,单位秒,必须 `>= 1`。 |
| `report_history_limit` | integer | 每类 report 文件滚动保留数量,必须 `>= 1`。 |
| `timezone` | string | IANA 时区名,例如 `Asia/Shanghai`、`UTC`、`Europe/London`。 |
| `timing.refresh` | integer | RTR EndOfData `refresh`,单位秒。 |
| `timing.retry` | integer | RTR EndOfData `retry`,单位秒。 |
| `timing.expire` | integer | RTR EndOfData `expire`,必须大于 `refresh` 和 `retry`。 |
示例:
```bash
curl -X POST http://127.0.0.1:8323/admin/rtr/config \
-H "Content-Type: application/json" \
-H "Authorization: Bearer change-me" \
-d '{"max_delta": 6}'
```
成功响应:
```json
{
"status": "ok",
"config": {
"max_delta": 6,
"prune_delta_by_snapshot_size": true,
"source_refresh_interval_seconds": 300,
"runtime_report_interval_seconds": 300,
"report_history_limit": 10,
"timezone": "Asia/Shanghai",
"timing": {
"refresh": 3600,
"retry": 600,
"expire": 7200
}
}
}
```
## GET /admin/rtr/slurm/files
列出 `RPKI_RTR_SLURM_DIR` 下的 SLURM 文件。只返回合法的 `*.slurm` 和 `*.slurm.disabled` 文件,不返回 `.backup/`。
```bash
curl http://127.0.0.1:8323/admin/rtr/slurm/files \
-H "Authorization: Bearer change-me"
```
响应示例:
```json
{
"status": "ok",
"files": [
{
"name": "local-policy.slurm",
"path": "/app/slurm/local-policy.slurm",
"enabled": true,
"size_bytes": 168,
"modified_unix_seconds": 1782100000
},
{
"name": "old-policy.slurm.disabled",
"path": "/app/slurm/old-policy.slurm.disabled",
"enabled": false,
"size_bytes": 168,
"modified_unix_seconds": 1782100100
}
]
}
```
## GET /admin/rtr/slurm/files/{name}
读取单个 SLURM 文件,前端编辑页面可以直接使用这个接口加载内容。
```bash
curl http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm \
-H "Authorization: Bearer change-me"
```
响应示例:
```json
{
"status": "ok",
"file": {
"name": "local-policy.slurm",
"path": "/app/slurm/local-policy.slurm",
"enabled": true,
"size_bytes": 168,
"modified_unix_seconds": 1782100000,
"content": "{ \"slurmVersion\": 1, \"validationOutputFilters\": { \"prefixFilters\": [], \"bgpsecFilters\": [] }, \"locallyAddedAssertions\": { \"prefixAssertions\": [], \"bgpsecAssertions\": [] } }"
}
}
```
## POST /admin/rtr/slurm/files
新增或覆盖一个 SLURM 文件。文件名放在请求体中。
```json
{
"name": "local-policy.slurm",
"content": "{ \"slurmVersion\": 1, \"validationOutputFilters\": { \"prefixFilters\": [], \"bgpsecFilters\": [] }, \"locallyAddedAssertions\": { \"prefixAssertions\": [], \"bgpsecAssertions\": [] } }",
"reload": true
}
```
## PUT /admin/rtr/slurm/files/{name}
新增或覆盖指定 SLURM 文件。
```bash
curl -X PUT http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm \
-H "Content-Type: application/json" \
-H "Authorization: Bearer change-me" \
-d '{
"content": "{ \"slurmVersion\": 1, \"validationOutputFilters\": { \"prefixFilters\": [], \"bgpsecFilters\": [] }, \"locallyAddedAssertions\": { \"prefixAssertions\": [], \"bgpsecAssertions\": [] } }",
"reload": true
}'
```
## DELETE /admin/rtr/slurm/files/{name}
删除指定 SLURM 文件。删除前会先备份。
```bash
curl -X DELETE "http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm?reload=true" \
-H "Authorization: Bearer change-me"
```
## POST /admin/rtr/slurm/files/{name}/disable
将启用文件改名为失效文件:
```text
local-policy.slurm -> local-policy.slurm.disabled
```
```bash
curl -X POST "http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm/disable?reload=true" \
-H "Authorization: Bearer change-me"
```
## POST /admin/rtr/slurm/files/{name}/enable
将失效文件改名为启用文件:
```text
local-policy.slurm.disabled -> local-policy.slurm
```
```bash
curl -X POST "http://127.0.0.1:8323/admin/rtr/slurm/files/local-policy.slurm.disabled/enable?reload=true" \
-H "Authorization: Bearer change-me"
```
## POST /admin/rtr/slurm/reload
不修改文件,只立即触发一次 CCR/SLURM source reload。
```bash
curl -X POST http://127.0.0.1:8323/admin/rtr/slurm/reload \
-H "Authorization: Bearer change-me"
```
## SLURM 文件规则
- `*.slurm`:启用,会被 source refresh 加载。
- `*.slurm.disabled`:失效,不会被加载。
- `.backup/`:接口自动生成的备份目录。
- 文件名只能是 basename,不能包含 `/`、`\` 或 `..`。
- 文件名只能包含 ASCII 字母、数字、`.`、`_`、`-`。
- 写入内容必须是合法 JSON,并且能被解析为合法 SLURM 文件。
- 单个请求体默认限制为 5 MiB。
- 覆盖、删除前会写入 `.backup/` 备份。
## 错误响应
| HTTP 状态码 | 含义 |
| --- | --- |
| `400 Bad Request` | JSON 非法、配置值非法、SLURM 文件名非法、目标文件不存在、reload 失败等。 |
| `401 Unauthorized` | 配置了 token,但请求缺少或使用了错误的 `Authorization`。 |
| `404 Not Found` | 路径或方法不支持。 |
| `413 Payload Too Large` | 请求体超过限制。 |

View File

@@ -1,80 +0,0 @@
{
"created_at_utc": "2026-06-19T08:08:03Z",
"items": [
{
"rir": "afrinic",
"ta_bytes": 1216,
"ta_download": "200 1216 1.351147",
"ta_elapsed_s": 1.227,
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/afrinic-ta.cer",
"ta_sha256": "43a26fd28bafb9398e5b2ab19e036b450bd04f4973a7f5ad151cebdee0edac36",
"ta_uri": "https://rpki.afrinic.net/repository/AfriNIC.cer",
"tal_bytes": 496,
"tal_download": "200 496 1.361326",
"tal_elapsed_s": 1.238,
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/afrinic.tal",
"tal_sha256": "2838ef30ea27ce5705abf5f5adb131d8c35b1f50858338a2f3c84bb207c2fa35",
"tal_url": "https://rpki.afrinic.net/tal/afrinic.tal"
},
{
"rir": "apnic",
"ta_bytes": 1222,
"ta_download": "200 1222 1.012321",
"ta_elapsed_s": 0.921,
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/apnic-ta.cer",
"ta_sha256": "2014230ad49b2777ac2bde0948ddfa4b8f207114c549e26d755de88c3593e3af",
"ta_uri": "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
"tal_bytes": 532,
"tal_download": "200 466 1.007155",
"tal_elapsed_s": 0.917,
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/apnic.tal",
"tal_sha256": "472e551f7c551c2e999e582b7c9437d3bee4900fe53afff62aeb28d4940ade94",
"tal_url": "https://tal.apnic.net/apnic.tal"
},
{
"rir": "arin",
"ta_bytes": 1143,
"ta_download": "200 1143 0.816714",
"ta_elapsed_s": 0.743,
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/arin-ta.cer",
"ta_sha256": "5b3c2f6f04abd19261084487a43c156f778b0b8926d63801d48c7a93ed349492",
"ta_uri": "https://rrdp.arin.net/arin-rpki-ta.cer",
"tal_bytes": 1258,
"tal_download": "200 1258 0.848581",
"tal_elapsed_s": 0.774,
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/arin.tal",
"tal_sha256": "1f8bdb03bcc30a3b8e11fd9a87102fba250c22137a3c8baa9c81b139cb412639",
"tal_url": "https://www.arin.net/resources/manage/rpki/arin.tal"
},
{
"rir": "lacnic",
"ta_bytes": 1166,
"ta_download": "200 1166 1.025273",
"ta_elapsed_s": 0.932,
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/lacnic-ta.cer",
"ta_sha256": "f44bc51008fd6998de7597b72a79b07bf3ebcb0f14daa7c7c022c0e9d66e0ad0",
"ta_uri": "https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer",
"tal_bytes": 502,
"tal_download": "200 502 1.729122",
"tal_elapsed_s": 1.565,
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/lacnic.tal",
"tal_sha256": "d44bb9394ab009c8b53e5efebf2a1c9450bab61a27efe00de5a3e4587a3a2f6a",
"tal_url": "https://www.lacnic.net/innovaportal/file/4983/1/lacnic.tal"
},
{
"rir": "ripe",
"ta_bytes": 1036,
"ta_download": "200 1036 1.060992",
"ta_elapsed_s": 4.536,
"ta_path": "rpki_2/rpki/fixtures/live_20260619/ta/ripe-ncc-ta.cer",
"ta_sha256": "3e3f7e4efc8d0cea03d9cc1fde6e168b45c26d7b3272e14abd8da4871886e539",
"ta_uri": "https://rpki.ripe.net/ta/ripe-ncc-ta.cer",
"tal_bytes": 482,
"tal_download": "200 482 1.092082",
"tal_elapsed_s": 0.992,
"tal_path": "rpki_2/rpki/fixtures/live_20260619/tal/ripe-ncc.tal",
"tal_sha256": "59ca27ef93f23682749fcefe7c6d70fbc723343549ff9e4d3996acaff79817fb",
"tal_url": "https://tal.rpki.ripe.net/ripe-ncc.tal"
}
]
}

View File

@@ -1,10 +0,0 @@
rsync://rpki.afrinic.net/repository/AfriNIC.cer
https://rpki.afrinic.net/repository/AfriNIC.cer
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxsAqAhWIO+ON2Ef9oRDM
pKxv+AfmSLIdLWJtjrvUyDxJPBjgR+kVrOHUeTaujygFUp49tuN5H2C1rUuQavTH
vve6xNF5fU3OkTcqEzMOZy+ctkbde2SRMVdvbO22+TH9gNhKDc9l7Vu01qU4LeJH
k3X0f5uu5346YrGAOSv6AaYBXVgXxa0s9ZvgqFpim50pReQe/WI3QwFKNgpPzfQL
6Y7fDPYdYaVOXPXSKtx7P4s4KLA/ZWmRL/bobw/i2fFviAGhDrjqqqum+/9w1hEl
L/vqihVnV18saKTnLvkItA/Bf5i11Yhw2K7qv573YWxyuqCknO/iYLTR1DToBZcZ
UQIDAQAB

View File

@@ -1,10 +0,0 @@
https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8
qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI
7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr
LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf
i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H
cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou
0wIDAQAB

View File

@@ -1,10 +0,0 @@
https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx9RWSL61YAAYumEiU8z8
qH2ETVIL01ilxZlzIL9JYSORMN5Cmtf8V2JblIealSqgOTGjvSjEsiV73s67zYQI
7C/iSOb96uf3/s86NqbxDiFQGN8qG7RNcdgVuUlAidl8WxvLNI8VhqbAB5uSg/Mr
LeSOvXRja041VptAxIhcGzDMvlAJRwkrYK/Mo8P4E2rSQgwqCgae0ebY1CsJ3Cjf
i67C1nw7oXqJJovvXJ4apGmEv8az23OLC6Ki54Ul/E6xk227BFttqFV3YMtKx42H
cCcDVZZy01n7JjzvO8ccaXmHIgR7utnqhBRNNq5Xc5ZhbkrUsNtiJmrZzVlgU6Ou
0wIDAQAB

View File

@@ -1,19 +0,0 @@
# THIS TRUST ANCHOR LOCATOR IS PROVIDED BY THE AMERICAN REGISTRY FOR
# INTERNET NUMBERS (ARIN) "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL ARIN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS PUBLIC KEY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
rsync://rpki.arin.net/repository/arin-rpki-ta.cer
https://rrdp.arin.net/arin-rpki-ta.cer
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3lZPjbHvMRV5sDDqfLc/685th5FnreHMJjg8
pEZUbG8Y8TQxSBsDebbsDpl3Ov3Cj1WtdrJ3CIfQODCPrrJdOBSrMATeUbPC+JlNf2SRP3UB+VJFgtTj
0RN8cEYIuhBW5t6AxQbHhdNQH+A1F/OJdw0q9da2U29Lx85nfFxvnC1EpK9CbLJS4m37+RlpNbT1cba+
b+loXpx0Qcb1C4UpJCGDy7uNf5w6/+l7RpATAHqqsX4qCtwwDYlbHzp2xk9owF3mkCxzl0HwncO+sEHH
eaL3OjtwdIGrRGeHi2Mpt+mvWHhtQqVG+51MHTyg+nIjWFKKGx1Q9+KDx4wJStwveQIDAQAB

View File

@@ -1,4 +0,0 @@
https://rrdp.lacnic.net/ta/rta-lacnic-rpki.cer
rsync://repository.lacnic.net/rpki/lacnic/rta-lacnic-rpki.cer
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqZEzhYK0+PtDOPfub/KRc3MeWx3neXx4/wbnJWGbNAtbYqXg3uU5J4HFzPgk/VIppgSKAhlO0H60DRP48by9gr5/yDHu2KXhOmnMg46sYsUIpfgtBS9+VtrqWziJfb+pkGtuOWeTnj6zBmBNZKK+5AlMCW1WPhrylIcB+XSZx8tk9GS/3SMQ+YfMVwwAyYjsex14Uzto4GjONALE5oh1M3+glRQduD6vzSwOD+WahMbc9vCOTED+2McLHRKgNaQf0YJ9a1jG9oJIvDkKXEqdfqDRktwyoD74cV57bW3tBAexB7GglITbInyQAsmdngtfg2LUMrcROHHP86QPZINjDQIDAQAB

View File

@@ -1,10 +0,0 @@
https://rpki.ripe.net/ta/ripe-ncc-ta.cer
rsync://rpki.ripe.net/ta/ripe-ncc-ta.cer
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0URYSGqUz2myBsOzeW1j
Q6NsxNvlLMyhWknvnl8NiBCs/T/S2XuNKQNZ+wBZxIgPPV2pFBFeQAvoH/WK83Hw
A26V2siwm/MY2nKZ+Olw+wlpzlZ1p3Ipj2eNcKrmit8BwBC8xImzuCGaV0jkRB0G
Z0hoH6Ml03umLprRsn6v0xOP0+l6Qc1ZHMFVFb385IQ7FQQTcVIxrdeMsoyJq9eM
kE6DoclHhF/NlSllXubASQ9KUWqJ0+Ot3QCXr4LXECMfkpkVR2TZT+v5v658bHVs
6ZxRD1b6Uk1uQKAyHUbn/tXvP8lrjAibGzVsXDT2L0x4Edx+QdixPgOji3gBMyL2
VwIDAQAB

2734
model.txt

File diff suppressed because it is too large Load Diff

View File

@@ -1,220 +0,0 @@
# Ours RP Prometheus / Grafana Monitor
本目录提供本地开发监控栈,用于采集 `rpki_artifact_metrics` 暴露的 ours RP soak 指标。
## 前置条件
1. Docker + Docker Compose v2;
2. 宿主机已启动 `rpki_artifact_metrics`,并监听 Docker 网桥可访问的地址,例如 `0.0.0.0:9556`;
3. Prometheus 容器通过 `host.docker.internal:9556` 访问宿主 sidecar。
Linux Docker 下 compose 已配置:
```yaml
extra_hosts:
- host.docker.internal:host-gateway
```
## 启动
```bash
cd rpki_2/rpki/monitor
docker compose up -d
```
默认镜像使用官方 Docker Hub 镜像:
```text
prom/prometheus:v2.55.1
grafana/grafana:11.3.1
```
如需切到其它镜像源:
```bash
PROMETHEUS_IMAGE=<mirror>/prom/prometheus:v2.55.1 \
GRAFANA_IMAGE=<mirror>/grafana/grafana:11.3.1 \
docker compose up -d
```
默认端口:
- Prometheus: <http://localhost:9090>
- Grafana: <http://localhost:3000>
- Grafana 默认账号密码:`admin` / `admin`
如端口冲突:
```bash
PROMETHEUS_PORT=19090 GRAFANA_PORT=13000 docker compose up -d
```
Prometheus 默认保留 7 天数据;可通过 `PROMETHEUS_RETENTION` 覆盖:
```bash
PROMETHEUS_RETENTION=7d docker compose up -d
```
## 长期稳定性测试
portable soak package 内置 `run_24h_soak_with_metrics.sh`,用于连续运行 ours RP、启动 metrics sidecar、启动本监控栈,并每小时生成报告:
```bash
cd /path/to/portable-soak
SOAK_DURATION_SECS=0 \
HOURLY_REPORT_INTERVAL_SECS=3600 \
SOAK_RETAIN_RUNS=100 \
CLEAN_TMP_AFTER_RUN=1 \
PROMETHEUS_RETENTION=7d \
STOP_MONITOR_STACK_ON_EXIT=0 \
FEISHU_WEBHOOK_SCRIPT=/home/yuyr/.codex/skills/user/feishu-webhook/scripts/send_feishu_text.py \
./run_24h_soak_with_metrics.sh
```
`SOAK_DURATION_SECS=0` 表示持续运行不自动停止;如需 24 小时自然停止,可设置为 `86400`,脚本会等当前 run 完成后退出,不会直接 kill 半轮验证。
关键产物:
- `runs/run_xxxx/`:最近 100 个 run 原始产物;
- `hourly_reports/hour_*.md`:小时级报告;
- `hourly_reports/hourly_summary.jsonl`:小时级结构化汇总;
- `incident_runs/run_xxxx/`:异常 run 固化副本;
- `logs/metrics.*`、`logs/24h-soak.*`、`logs/hourly-reporter.*`:运行日志。
短周期联调可把 `SOAK_DURATION_SECS` 和 `HOURLY_REPORT_INTERVAL_SECS` 调小,并设置 `FEISHU_DRY_RUN=1` 避免真实飞书通知。
## 停止
```bash
cd rpki_2/rpki/monitor
docker compose down
```
保留数据 volume。若要清理数据:
```bash
docker compose down -v
```
## 典型本地联调命令
先启动 APNIC soak 和 metrics sidecar,例如:
```bash
# soak .env 关键配置
MAX_RUNS=-1
RIRS=apnic
RETAIN_RUNS=5
INTERVAL_SECS=0
# metrics sidecar
rpki_artifact_metrics \
--run-root /path/to/portable-soak \
--listen 0.0.0.0:9556 \
--poll-secs 5 \
--instance local-apnic-continuous
```
再启动监控栈:
```bash
cd rpki_2/rpki/monitor
docker compose up -d
```
## 验证
Prometheus target:
```bash
curl -s 'http://localhost:9090/api/v1/targets' | python3 -m json.tool
```
Prometheus query:
```bash
curl -G 'http://localhost:9090/api/v1/query' \
--data-urlencode 'query=up{job="ours-rp-artifact-metrics"}'
curl -G 'http://localhost:9090/api/v1/query' \
--data-urlencode 'query=ours_rp_run_completed_total{status="success"}'
```
Grafana health:
```bash
curl -s http://localhost:3000/api/health | python3 -m json.tool
```
Grafana dashboard:
- 打开 <http://localhost:3000/d/ours-rp-soak-overview/ours-rp-soak-overview>
## 主要指标
- `ours_rp_metrics_service_up`
- `ours_rp_run_completed_total`
- `ours_rp_run_duration_seconds`
- `ours_rp_run_max_rss_bytes`
- `ours_rp_vrps{kind="total|unique"}`:`total` 为去重前 VRP 条目数,`unique` 按 `(ASN, IP Prefix, Max Length)` 去重。
- `ours_rp_vaps`
- `ours_rp_publication_points`
- `ours_rp_repo_sync_phase_count`
- `ours_rp_large_publication_points{object_count_gt="10|50|100|..."}`
- `ours_rp_cir_objects`
- `ours_rp_ccr_state_items`
## Inter-RP 持续对比监控
`rpki_inter_rp_metrics` 用于汇总三方 RP 的最新产物:
- ours RP:读取当前 portable soak 的 `runs/run_xxxx/run-summary.json`、`result.ccr`、CSV 产物;
- Routinator:读取远端200同步来的 `routinator/latest/run-meta.json`、`vrps.csv`、`vaps.csv`;
- rpki-client 9.8:读取远端200同步来的 `rpki-client/latest/run-meta.json`、`vrps.csv`、`vaps.csv`、`result.ccr`。
远端231 启动 sidecar 示例:
```bash
rpki_inter_rp_metrics \
--ours-run-root /root/rpki_20260608_2_feature062_24h_20260608T075547Z/portable-soak \
--peer-root /root/inter-rp-aggregator/synced-from-200 \
--listen 0.0.0.0:9557 \
--poll-secs 30 \
--instance remote231-inter-rp
```
Prometheus 已新增 `ours-rp-inter-rp-metrics` scrape job,默认访问 `host.docker.internal:9557`。
远端200 runner 与远端231同步脚本位于:
```text
scripts/inter_rp/run_remote200_rp_loops.sh
scripts/inter_rp/run_single_rp_with_rss.sh
scripts/inter_rp/sync_remote200_to_231.sh
scripts/inter_rp/run_inter_rp_metrics_sidecar.sh
scripts/inter_rp/inter-rp.env.example
```
如需从本机独立开关远端200上的 Routinator 或 rpki-client,使用:
```bash
scripts/inter_rp/control_remote200_rp.sh status all
scripts/inter_rp/control_remote200_rp.sh stop routinator
scripts/inter_rp/control_remote200_rp.sh start routinator
scripts/inter_rp/control_remote200_rp.sh restart rpki-client
```
默认远端为 `root@43.110.128.200`,可通过 `REMOTE_HOST=...` 覆盖;脚本只管理指定 RP 的 loop 和当前子进程,不会自动影响另一个 RP。
关键指标:
- `inter_rp_run_wall_seconds{rp="ours-rp|routinator|rpki-client"}`
- `inter_rp_run_max_rss_bytes{rp="...",kind="aggregate_peak"}`
- `inter_rp_vrps{rp="..."}`:按 `(ASN, IP Prefix, Max Length)` 去重。
- `inter_rp_vaps{rp="..."}`:按 `(Customer ASN, Providers)` 去重,Routinator 使用 `--enable-aspa` JSON 输出转换,rpki-client 使用 `-j` JSON 输出转换。
- `inter_rp_ccr_digest_match{left="ours-rp",right="rpki-client",state="overall|mfts|vrps|vaps|tas|rks"}`
- `inter_rp_sync_age_seconds`
Grafana dashboard:
- <http://localhost:3000/d/ours-rp-inter-rp/ours-rp-inter-rp>

View File

@@ -1,38 +0,0 @@
services:
prometheus:
image: ${PROMETHEUS_IMAGE:-prom/prometheus:v2.55.1}
container_name: ours-rp-prometheus
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --storage.tsdb.retention.time=${PROMETHEUS_RETENTION:-7d}
- --web.enable-lifecycle
extra_hosts:
- host.docker.internal:host-gateway
ports:
- "${PROMETHEUS_PORT:-9090}:9090"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- prometheus-data:/prometheus
restart: unless-stopped
grafana:
image: ${GRAFANA_IMAGE:-grafana/grafana:11.3.1}
container_name: ours-rp-grafana
depends_on:
- prometheus
ports:
- "${GRAFANA_PORT:-3000}:3000"
environment:
GF_SECURITY_ADMIN_USER: ${GRAFANA_ADMIN_USER:-admin}
GF_SECURITY_ADMIN_PASSWORD: ${GRAFANA_ADMIN_PASSWORD:-admin}
GF_USERS_ALLOW_SIGN_UP: "false"
volumes:
- grafana-data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
restart: unless-stopped
volumes:
prometheus-data:
grafana-data:

View File

@@ -1,826 +0,0 @@
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": null,
"links": [],
"liveNow": false,
"panels": [
{
"id": 1,
"title": "Ours Only Repo Count",
"type": "stat",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 0,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 0
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"expr": "max(inter_rp_repo_sync_overlap_total{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_ours\"})",
"legendFormat": "only ours",
"refId": "A",
"instant": true
}
]
},
{
"id": 2,
"title": "Routinator Only Repo Count",
"type": "stat",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 6,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 0
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"expr": "max(inter_rp_repo_sync_overlap_total{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_routinator\"})",
"legendFormat": "only routinator",
"refId": "A",
"instant": true
}
]
},
{
"id": 3,
"title": "Ours vs Routinator VAP Diff",
"type": "stat",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 12,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 0
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"expr": "max(inter_rp_vaps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"})",
"legendFormat": "vap diff",
"refId": "A",
"instant": true
}
]
},
{
"id": 4,
"title": "Ours vs Routinator VRP Diff",
"type": "stat",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 4,
"w": 6,
"x": 18,
"y": 0
},
"fieldConfig": {
"defaults": {
"unit": "short",
"decimals": 0
},
"overrides": []
},
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto",
"wideLayout": true
},
"targets": [
{
"expr": "max(inter_rp_vrps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"})",
"legendFormat": "vrp diff",
"refId": "A",
"instant": true
}
]
},
{
"id": 5,
"title": "Wall Time by RP",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 4
},
"fieldConfig": {
"defaults": {
"unit": "s",
"min": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_run_wall_seconds{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"legendFormat": "{{rp}}",
"refId": "A"
}
]
},
{
"id": 6,
"title": "Max RSS Aggregate Peak by RP",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 4
},
"fieldConfig": {
"defaults": {
"unit": "bytes",
"min": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_run_max_rss_bytes{exported_instance=~\".*inter-rp\",kind=\"aggregate_peak\",rp=~\"ours-rp|routinator\"}",
"legendFormat": "{{rp}}",
"refId": "A"
}
]
},
{
"id": 7,
"title": "VRPs by RP (unique ASN/Prefix/MaxLen)",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 12
},
"fieldConfig": {
"defaults": {
"unit": "none",
"min": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_vrps{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"legendFormat": "{{rp}}",
"refId": "A"
}
]
},
{
"id": 8,
"title": "VAPs / ASPAs by RP (unique Customer/Providers)",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 12
},
"fieldConfig": {
"defaults": {
"unit": "none",
"min": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_vaps{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"legendFormat": "{{rp}}",
"refId": "A"
}
]
},
{
"id": 9,
"title": "Latest RP Runs",
"type": "table",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 20
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"options": {
"showHeader": true,
"sortBy": []
},
"targets": [
{
"expr": "inter_rp_run_seq{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"format": "table",
"instant": true,
"legendFormat": "{{rp}} seq",
"refId": "A"
},
{
"expr": "inter_rp_run_success{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"format": "table",
"instant": true,
"legendFormat": "{{rp}} success",
"refId": "B"
},
{
"expr": "inter_rp_run_wall_seconds{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"format": "table",
"instant": true,
"legendFormat": "{{rp}} wall",
"refId": "C"
}
]
},
{
"id": 10,
"title": "Output Count Diffs (unique)",
"type": "table",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 20
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": []
},
"options": {
"showHeader": true,
"sortBy": []
},
"targets": [
{
"expr": "inter_rp_vrps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
"format": "table",
"instant": true,
"legendFormat": "vrps ours-rp-routinator",
"refId": "A"
},
{
"expr": "inter_rp_vaps_diff{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
"format": "table",
"instant": true,
"legendFormat": "vaps ours-rp-routinator",
"refId": "B"
}
]
},
{
"id": 15,
"title": "VRP Diff Trend by Class",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 28
},
"fieldConfig": {
"defaults": {
"unit": "none",
"min": 0,
"decimals": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_vrps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"total\"}",
"legendFormat": "total diff",
"refId": "A"
},
{
"expr": "inter_rp_vrps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_ours\"}",
"legendFormat": "only ours",
"refId": "B"
},
{
"expr": "inter_rp_vrps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_routinator\"}",
"legendFormat": "only routinator",
"refId": "C"
}
]
},
{
"id": 16,
"title": "VAP Diff Trend by Class",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 28
},
"fieldConfig": {
"defaults": {
"unit": "none",
"min": 0,
"decimals": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "multi",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_vaps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"total\"}",
"legendFormat": "total diff",
"refId": "A"
},
{
"expr": "inter_rp_vaps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_ours\"}",
"legendFormat": "only ours",
"refId": "B"
},
{
"expr": "inter_rp_vaps_diff_by_class{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\",class=\"only_routinator\"}",
"legendFormat": "only routinator",
"refId": "C"
}
]
},
{
"id": 11,
"title": "Artifact Age by RP",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 24,
"x": 0,
"y": 36
},
"fieldConfig": {
"defaults": {
"unit": "s",
"min": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_artifact_age_seconds{exported_instance=~\".*inter-rp\",rp=~\"ours-rp|routinator\"}",
"legendFormat": "{{rp}}",
"refId": "A"
}
]
},
{
"id": 12,
"title": "Repo Sync Availability by RP",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 44
},
"fieldConfig": {
"defaults": {
"unit": "none",
"min": 0,
"decimals": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_repo_sync_total{exported_instance=~\".*inter-rp\",state=~\"available|failed\",rp=~\"ours-rp|routinator\"}",
"legendFormat": "{{rp}} {{state}}",
"refId": "A"
}
]
},
{
"id": 13,
"title": "Repo Sync Overlap Classes",
"type": "timeseries",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 8,
"w": 12,
"x": 12,
"y": 44
},
"fieldConfig": {
"defaults": {
"unit": "none",
"min": 0,
"decimals": 0
},
"overrides": []
},
"options": {
"legend": {
"calcs": [
"lastNotNull"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"expr": "inter_rp_repo_sync_overlap_total{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
"legendFormat": "{{class}}",
"refId": "A"
}
]
},
{
"id": 14,
"title": "Repo Sync Diff URIs",
"type": "table",
"datasource": {
"type": "prometheus",
"uid": "Prometheus"
},
"gridPos": {
"h": 9,
"w": 24,
"x": 0,
"y": 52
},
"fieldConfig": {
"defaults": {
"unit": "none",
"decimals": 0
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "uri"
},
"properties": [
{
"id": "custom.width",
"value": 760
}
]
},
{
"matcher": {
"id": "byName",
"options": "class"
},
"properties": [
{
"id": "custom.width",
"value": 140
}
]
},
{
"matcher": {
"id": "byRegexp",
"options": "^(mft|crl|crt|roa|aspa)$"
},
"properties": [
{
"id": "custom.align",
"value": "right"
},
{
"id": "custom.width",
"value": 80
}
]
}
]
},
"options": {
"showHeader": true,
"sortBy": []
},
"targets": [
{
"expr": "inter_rp_repo_sync_diff_info{exported_instance=~\".*inter-rp\",left=\"ours-rp\",right=\"routinator\"}",
"format": "table",
"instant": true,
"legendFormat": "{{class}} #{{rank}}",
"refId": "A"
}
],
"transformations": [
{
"id": "organize",
"options": {
"excludeByName": {
"Time": true,
"Value": true,
"__name__": true,
"exported_instance": true,
"instance": true,
"job": true,
"left": true,
"right": true,
"rank": true,
"routinator_duration": true
},
"indexByName": {
"class": 0,
"uri": 1,
"mft": 2,
"crl": 3,
"crt": 4,
"roa": 5,
"aspa": 6
},
"renameByName": {
"class": "class",
"uri": "uri",
"mft": "mft",
"crl": "crl",
"crt": "crt",
"roa": "roa",
"aspa": "aspa"
}
}
}
]
}
],
"refresh": "10s",
"schemaVersion": 40,
"tags": [
"rpki",
"inter-rp",
"routinator"
],
"templating": {
"list": []
},
"time": {
"from": "now-6h",
"to": "now"
},
"timezone": "browser",
"title": "Ours RP vs Routinator",
"uid": "ours-rp-inter-rp",
"version": 4
}

Some files were not shown because too many files have changed in this diff Show More