myxce/all_README.md
2026-08-21 15:43:15 +00:00

595 lines
No EOL
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# all.sh 使用与实现说明
本文件记录 `all.sh` 的功能、使用方法和内部实现细节,便于后续维护与扩展。
## 1. 概述
`all.sh` 是 XCE 项目的统一管理脚本,提供两个能力:
1. **构建**:以 `nix develop` + `meson` 构建开发版 / 生产版 / 清理构建产物,并输出编译耗时统计。
2. **服务管理**:启动 / 停止 / 重启 / 查看状态四个核心程序:
- `xce_controller`(controller,控制器,默认端口 9990)
- `xce_scheduler`(scheduler,调度器,默认连接 localhost:9990)
- `xcecd`(worker,工作节点守护进程,需要 root)
- `xce_apiserver`(apiserver,REST API,默认 HTTP 端口 8080,连接 controller)
所有日志统一输出到 `logs/`,PID 统一记录在 `run/`。
## 2. 快速使用
```bash
# ---------- 构建 ----------
./all.sh build -c # 彻底清理构建产物(删除 build/ 与 build-prod/)
./all.sh build -d # 开发构建(debug + ASan,带编译耗时统计)
./all.sh build -p # 生产构建(release,无 sanitizer,带编译耗时统计)
# ---------- 集群配置 ----------
./all.sh config deploy # 构建+部署 cluster.bin(从 src/configs)并重启 controller
./all.sh config backup # 备份所有服务配置到 ~/all_configs
# ---------- 服务管理 ----------
./all.sh start # 启动全部服务(controller scheduler worker apiserver)
./all.sh start controller scheduler # 只启动指定服务
./all.sh stop [svc...] # 停止指定服务,缺省为全部
./all.sh restart [svc...] # 重启指定服务,缺省为全部
./all.sh status [svc...] # 查看状态(名称 / 状态 / PID / 二进制 / 日志),缺省为全部
```
服务清单(`svc` 可取值):`controller` / `scheduler` / `worker` / `apiserver`。
## 3. 配置区(customize 区)
脚本顶部 `# customize begin ... # customize end` 是可定制区域:
| 变量 | 说明 |
|------|------|
| `g_code_base` | 项目根目录,默认 `/home/jsxing/allwork/git/myxce` |
| `g_services` | 服务注册表:`名称|二进制绝对路径` 数组 |
| `g_deploy_dir` | 统一部署根目录,默认 `${g_code_base}/deploy`(所有配置/logs/run 都收在这里) |
| `g_conf_dir` | 生成配置目录,默认 `${g_deploy_dir}/conf`(worker.yaml 等) |
| `g_run_dir` | PID/证书/数据目录,默认 `${g_deploy_dir}/run` |
| `g_log_dir` | 日志目录,默认 `${g_deploy_dir}/logs` |
| `g_loglevel` | 全局日志级别:`info` / `debug` / `debug2` / `debug3`,默认 `info` |
| `g_worker_bin_dir` | 作业 worker 二进制(`xcecd_worker`)安装目录,默认 `/opt/xce/bin`;必须选一个所有作业执行用户都能到达的位置(仓库路径在 `/home/<user>` 下通常 750,其他用户无法进入,作业会以 exit 126 立即退出,见 9.4)。由 `svc_prepare_worker` 经 sudo 安装、`svc_env_worker` 以 `XCE_SERVER_DIR` 注入 |
### 3.1 日志级别 `g_loglevel`
一个开关控制所有组件的日志详细度。修改后 **restart 对应服务** 即生效(配置会在启动前自动同步):
| `g_loglevel` | controller | scheduler | worker (xcecd) | apiserver |
|---|---|---|---|---|
| `info`(默认) | 无 flag | 无 flag | worker.yaml `log_level: "info"` | logger.yaml `level: "info"` |
| `debug` | `-v` | `-v` | `log_level: "debug"` | `level: "debug"` |
| `debug2` | `-vv` | `-vv` | `log_level: "debug2"` | `level: "debug"`(zap 无 debug2) |
| `debug3` | `-vvv` | `-vvv` | `log_level: "debug3"` | `level: "debug"` |
实现要点:
- **controller / scheduler**:`svc_args_*` 通过 `$(_verbose_flags)` 注入 `-v`/`-vv`/`-vvv`(argp `--verbose`,`verbose_to_loglevel()` 映射到 debug/debug2/debug3)。
- **worker**:`svc_prepare_worker` 把 `$(_worker_loglevel)` 写进 `${g_conf_dir}/worker.yaml` 的 `log_level` 字段。注意 xcecd 传了 `-c` 后会**忽略 `-l/-p/-m`**,日志级别/目录完全由 worker.yaml 决定;文件已存在时也会 `sed` 同步级别。
- **apiserver**:`svc_prepare_apiserver` 把 `$(_apiserver_loglevel)` 写进 `<topdir>/config/logger.yaml` 的 `level`(zap 只支持 `debug/info/warn/error/fatal`,故 debug2/3 映射为 `debug`);文件已存在时同样同步。
### 服务注册表
```bash
g_services=(
"controller|${g_code_base}/src/build/controller/xce_controller"
"scheduler|${g_code_base}/src/build/scheduler/xce_scheduler"
"worker|${g_code_base}/src/build/worker/xcecd"
"apiserver|${g_code_base}/src/build/frontend/apiserver/xce_apiserver"
)
```
新增服务只需在此追加一行 `"名字|二进制路径"`,并补上对应的 `svc_args_<名字>` 函数(见下)。
## 4. 构建实现
构建统一使用:
```bash
nix develop --extra-experimental-features "nix-command flakes" \
--command bash -c "meson setup <build目录> <参数> && meson compile -C <build目录>"
```
> 注意:`meson setup` 和 `meson compile` 必须都跑在 `nix develop` 环境内,因此被放进同一个 `--command bash -c`。
### 4.1 build -d(开发版)
- 构建目录:`src/build/`
- 参数:`--buildtype=debug -Db_sanitize=address`(调试符号 + AddressSanitizer)
- 输出:`output is in ${src_dir}/build`
### 4.2 build -p(生产版)
- 构建目录:`src/build-prod/`
- 参数:`--buildtype=release`(开启优化,无 sanitizer)
- 输出:`output is in ${src_dir}/build-prod`
### 4.3 build -c(清理)
- 循环删除 `src/build/` 与 `src/build-prod/` 两个目录,彻底清空 meson 产物。
### 4.4 编译耗时统计
用 `date +%s` 在 `meson compile` 前后各取一次时间戳,结束时打印:
```
Build finished in 0m 56s (56s total)
```
### 4.5 构建耗时经验值
- `-d/-p`:增量编译,只有 xceadmin 每次固定重跑(约 11s)+ 首次/源码变更时全量。初次全量约 60s。
- `-c`:彻底清理后需要完整重建。
### 4.6 config deploy(部署集群配置)
```bash
./all.sh config deploy
```
一条命令完成:
1. 定位 `xceadmin`(依次查找 `src/build`、`src/build-prod` 的 venv、PATH)。
2. 在 `nix develop` 环境中用 `xceadmin config build src/configs -o ...` 构建 `cluster.bin`(schema 校验会输出若干 "field ... ignored" 警告,属正常)。
3. 按 sha256 前 12 位生成 tag,部署到 `run/controller/ctrl-1/conf/cluster.bin.<tag>`,写入 `conf/current`。
4. 重启 controller 加载配置(日志见 `Primary config loaded: tag=...`,`ctrlstatus` 显示 `Cluster: <name>`)。
> 为什么要部署:无 cluster.bin 时 controller 处于冷启动无配置状态,**worker join 会被拒绝并返回 EINVAL**(`find_matched_hostconfig` 因 `cluster_config == NULL` 返回 NULL,见 8 节)。清空重建(`build -c`)或删掉 `run/` 后必须重新 `config deploy`。
### 4.7 config backup(备份所有服务配置)
```bash
./all.sh config backup
```
把全部服务的运行时配置备份到 `~/all_configs/`(保留目录结构,覆盖式备份):
```
~/all_configs/
├── controller/certs/ # TLS 证书 xce.crt / xce.key
├── controller/conf/ # cluster.bin.* + current
├── worker/worker.yaml # /opt/xce/conf/worker.yaml(sudo 读取)
├── apiserver/ # app.yaml / auth.yaml / logger.yaml / rbac.yaml
└── scheduler/README.txt # 说明:scheduler 无独立配置文件
```
缺失的文件会被跳过并提示(例如未启动过 apiserver 时无其配置目录)。
## 5. 服务管理实现
服务管理采用「注册表 + 每服务钩子函数」的可扩展设计。
### 5.1 每服务启动参数函数 `svc_args_<name>`
`svc_start` 通过 `svc_args_${name}` 动态调用,返回该服务的启动参数。
| 服务 | 参数 |
|------|------|
| `svc_args_controller` | `--myname ctrl-1 --peers ctrl-1=localhost:9990 --workdir <deploy>/run/controller --certdir <deploy>/run/certs --logfile <deploy>/logs/controller.log --vcpus 4 [<g_loglevel>]` |
| `svc_args_scheduler` | `-l <deploy>/logs/scheduler.log [<g_loglevel>]` |
| `svc_args_worker` | `-c <deploy>/conf/worker.yaml -n localhost01`(xcecd 传 `-c` 后忽略 `-l/-p/-m`,日志位置/级别由 worker.yaml 决定) |
| `svc_args_apiserver` | `-d <deploy>/run/apiserver` |
> 表中 `[<g_loglevel>]` 表示按 `g_loglevel` 注入的 `-v`/`-vv`/`-vvv`(见 3.1 节)。
另外还有每服务环境变量钩子 `svc_env_<name>`(可选):`svc_start` 在 sudo 启动分支里用 `sudo -n env <值> bash -c ...` 注入到进程环境。
| 钩子 | 注入的环境变量 | 说明 |
|------|--------------|------|
| `svc_env_worker` | `XCE_SERVER_DIR=${g_worker_bin_dir}` | 让 `xcecd` 用安装在 `${g_worker_bin_dir}`(默认 `/opt/xce/bin`)的 `xcecd_worker` 启动作业 worker,而不是仓库路径;否则作业会以 exit 126 立即退出(见 9.4) |
### 5.2 启动前准备钩子 `svc_prepare_<name>`
`svc_start` 在启动前检查 `svc_prepare_${name}` 是否存在,存在则先执行(如失败则中止启动)。
- `svc_prepare_controller`:确保自签名 TLS 证书存在。若 `<deploy>/run/certs/xce.crt` 或 `xce.key` 缺失,用 `openssl` 生成(RSA 2048、10 年有效期)。controller 缺少证书会启动失败(`TLS certificate not found`)。
- `svc_prepare_worker`:生成 `${g_conf_dir}/worker.yaml`(`management_list`/`controller_port`/`log_level`/`log_dir`),其中 `log_level` 跟随 `g_loglevel`、`log_dir` 指向 `${g_log_dir}`。文件已存在时也会 `sed` 同步 `log_level`,因此修改 `g_loglevel` 后重启 worker 即生效。
- 另外会把 `src/build/worker/xcecd_worker` 用 `sudo install -m 0755` 安装到 `${g_worker_bin_dir}`(默认 `/opt/xce/bin`),源更新才重装(幂等)。作业 worker 由 systemd 以**作业执行用户**身份运行,必须放在所有人可访问的目录(仓库路径在 `/home/<user>` 下通常 750,其他用户无法进入 → exit 126,见 9.4)。
- `svc_prepare_apiserver`:确保 `<deploy>/run/apiserver/config/` 下的四个配置文件齐全(缺失才生成,不会覆盖已有配置):
- `logger.yaml`:日志配置(相对 `outputPath` 会落在 `<topdir>/logs/`),`level` 跟随 `g_loglevel`(zap 只支持 debug/info/warn/error,debug2/3 映射为 debug);已存在时也会同步 `level`。
- `rbac.yaml`:权限配置;优先复制仓库样例 `src/frontend/apiserver/config/rbac.yaml`,否则生成最小空配置。
- `auth.yaml`:认证配置;优先复制仓库样例 `src/frontend/apiserver/config/auth.yaml`(`config_file` 提供者,默认 admin/admin),否则生成最小单用户配置。
- `app.yaml`:主配置(`app`/`security`/`authentication`/`rate_limit`/`whitelist`/`file_upload`/`cache` 为 schema 必填),生成默认值并将 `backend.address/port` 指向 controller(`localhost:9990`)。
> 这四份文件缺一不可,Go 二进制启动时会按顺序校验(缺失即报错退出)。
### 5.3 sudo 支持 `svc_needs_sudo`
```bash
function svc_needs_sudo() {
case "$1" in
worker) return 0 ;; # 需要 root
*) return 1 ;;
esac
}
```
`xcecd` 校验 `geteuid() != 0` 直接退出(`FATAL: xcecd should be run as root`),因此 worker 必须 root 运行。
#### 启动(sudo 分支)
```bash
sudo -n env ${envp} bash -c "echo \$\$ > '${pidfile}'; exec nohup '${bin}' ${args}" \
>>"${log}" 2>&1 < /dev/null &
```
`envp` 来自可选的 `svc_env_<name>` 钩子(如 worker 注入 `XCE_SERVER_DIR=${g_worker_bin_dir}`,见 5.1);用 `env` 显式设置,避免受 sudo `env_reset` 影响。
设计要点:
- 内层 bash 先把**自己的 PID** 写入 pid 文件,再 `exec` 进二进制。`exec` 不改变 PID,因此 pid 文件记录的是**真实的 root 进程 PID**(而不是 sudo 的 PID)。
- `nohup` 防护 SIGHUP,避免终端关闭时被杀。
- `sudo -n` 非交互式:要求 sudo 免密(NOPASSWD)或已有缓存凭证(默认缓存 15 分钟),避免后台进程挂起等密码。
#### 状态检测与停止(root 进程)
普通用户对 root 进程执行 `kill`/`kill -0` 会返回 EPERM(失败),因此对 root 服务统一走 sudo:
- `_svc_alive`:`sudo -n kill -0 <pid>` 检测存活。
- `_svc_kill`:`sudo -n kill [-9] <pid>` 发信号。
### 5.4 启动流程 `svc_start`
1. 解析二进制路径,检查二进制存在(`-x`)。
2. 防重复启动:`_svc_pid` 已检测到存活 PID 则直接返回。
3. 创建 `run/`、`logs/` 目录。
4. 执行 `svc_prepare_<name>`(若存在)。
5. 执行 `svc_check_<name>` 预检(若存在,如 controller 需要已部署 cluster.bin)。
6. 按是否 sudo 分支后台启动,写 pid 文件,`disown`:
- sudo 分支先取 `svc_env_<name>` 注入的环境变量(若存在),再 `sudo -n env ... bash -c ...`。
- 非 sudo 分支 `setsid nohup <bin> <args>` 后台启动。
7. `sleep 1` 后通过 `_svc_pid` 校验:存活则报 `started (pid ...)`;否则报 `FAILED to start` 并清理 pid 文件。
### 5.5 停止流程 `svc_stop`
1. 从 pid 文件读 PID;若无,用 `pgrep -f <二进制名>` 兜底。
2. 发送 SIGTERM(root 服务走 sudo)。
3. 最多等待 3 秒(10 次 × 0.3s),仍存活则 SIGKILL。
4. 清理 pid 文件,报 `stopped`。
> 实测:worker 在 SIGTERM 后 3 秒内不退出,会走 SIGKILL 兜底,属预期行为。
### 5.6 状态显示 `svc_status`
```
SERVICE STATE PID
controller running 200831
binary: /home/jsxing/allwork/git/xce/src/build/controller/xce_controller
log: /home/jsxing/allwork/git/xce/logs/controller.log
last: <日志最后一行>
```
- `STATE` 由 `_svc_pid`(pid 文件 + 存活校验)决定,stale pid 文件会被自动清理。
- 显示日志文件路径及其最后一行,便于快速排查。
### 5.7 参数解析与分发
入口处的 `case` 把子命令分发给各 `cmd_*` 函数:
```bash
case "${1:-}" in
build) shift; cmd_build "$@" ;;
config) shift; cmd_config "$@" ;;
start) shift; cmd_start "$@" ;;
stop) shift; cmd_stop "$@" ;;
restart) shift; cmd_restart "$@" ;;
status) shift; cmd_status "$@" ;;
*) echo "Usage: $0 {build|config|start|stop|restart|status} [args]" && exit 1 ;;
esac
```
#### 服务列表解析 `_svc_resolve`
把命令行参数解析成服务名列表:缺省返回**全部**注册服务(`_svc_names`);未知服务名会报错并提示合法值(错误写到 stderr,避免被命令替换吞掉返回值)。
#### 批量分发 `_cmd_svc_foreach`
`start`/`stop`/`restart` 三个子命令都经由它按服务逐个执行;`status` 不走这里(见下)。
```bash
function _cmd_svc_foreach() {
local action="$1"; shift # start | stop | restart
local targets rc=0
targets="$(_svc_resolve "$@")" || exit 1
local name
for name in $targets; do
if ! "svc_${action}" "$name"; then
rc=1
# fail-fast for start/restart: one bad service (e.g. controller
# without cluster.bin) should stop the whole launch so the user
# sees the precheck hint (./all.sh config deploy) immediately.
case "$action" in
start|restart) break ;;
esac
fi
done
return "$rc"
}
function cmd_start() { _cmd_svc_foreach start "$@"; }
function cmd_stop() { _cmd_svc_foreach stop "$@"; }
function cmd_restart() { _cmd_svc_foreach restart "$@"; }
```
要点:
- **动态调用**:`"svc_${action}" "$name"` 即 `svc_start` / `svc_stop` / `svc_restart`,天然支持一次操作多个服务(如 `./all.sh start controller scheduler`)。
- **fail-fast**:`start`/`restart` 遇到某个服务失败立即 `break`(如 controller 未部署 cluster.bin 时 `svc_check_controller` 失败),让用户马上看到 `./all.sh config deploy` 的提示;`stop` 不打断,尽力停止其余服务。
- **退出码**:任何服务失败都累计 `rc=1`,脚本整体返回非 0。
- **`status` 不走 foreach**:`cmd_status` 自行循环调用 `svc_status`,先打印 `SERVICE/STATE/PID` 表头。
- **`config deploy` 不走 foreach**:`config_deploy` 内部直接 `svc_restart controller`。
## 6. 配置文件与运行时目录
> 所有配置、日志、运行时状态统一在 `${g_deploy_dir}`(`deploy/`)下,不再引用系统目录或旧仓库路径。
### 6.1 controller
- 数据目录:`<deploy>/run/controller/ctrl-1/`(`data/` WAL、`conf/` 配置),由 controller 自动创建。
- TLS 证书:`<deploy>/run/certs/`,由 `svc_prepare_controller` 自动生成。
- 日志:`<deploy>/logs/controller.log`。
### 6.2 worker 配置 `${g_conf_dir}/worker.yaml`
`xcecd` 的默认配置路径是 `DEFAULT_CONFIG_PATH`(`/opt/xce/conf/worker.yaml`),但 all.sh 始终用 `-c ${g_conf_dir}/worker.yaml` 显式指定,由 `svc_prepare_worker` 自动生成:
```yaml
---
workerconf:
management_list: ["localhost"]
controller_port: 9990
log_level: "info"
log_dir: "/home/jsxing/allwork/git/myxce/deploy/logs"
```
- 样例参考:`src/worker/conf/worker.yaml`。
- 必填字段:`controller_port`(>0)、`log_dir`(非空)、`management_list`。
- 注意:传了 `-c` 后,`-l`/`-p`/`-m` 参数会被忽略(`main.c` 行为),因此 `log_dir`/`log_level` 必须写在配置里;`log_level` 跟随 `g_loglevel`(见 3.1)。
### 6.3 apiserver
- top 目录:`<deploy>/run/apiserver/`,启动参数 `-d <topdir>`(必填)。
- 配置文件:`<deploy>/run/apiserver/config/` 下需有 `app.yaml`、`logger.yaml`、`rbac.yaml`、`auth.yaml`(由 `svc_prepare_apiserver` 自动生成,见 5.2)。
- 后端连接:`app.yaml` 的 `backend` 段配置 controller 地址/端口(`localhost:9990`);若后端未配置则运行在 stub 模式。
- HTTP 端口:`app.yaml` 的 `app.http_port`(默认 8080)。
- 日志:写在自己目录 `<deploy>/run/apiserver/logs/apiserver.log`(由 logger.yaml 决定),`<deploy>/logs/apiserver.log` 仅收集启动期输出。
- 默认账号:`admin` / `admin`(来自 `auth.yaml` 样例)。
## 7. xcecli 使用(登录与查询)
`xcecli` 是访问 apiserver(REST API,端口 8080)的命令行客户端,构建产物在 `src/build/frontend/xcecli/xcecli`(root 命令名为 `xce`)。
### 7.1 登录
```bash
src/build/frontend/xcecli/xcecli login -u admin -p admin --url http://localhost:8080
```
| 参数 | 必填 | 说明 |
|------|------|------|
| `-u, --username` | ✅ | 用户名 |
| `-p, --password` | 可选 | 密码;不填则交互式提示输入(不回显) |
| `--url` | ✅ | API Server 地址,`http://IP:8080` 或 `https://IP:8443` |
- 成功输出 `login successfully`,令牌(JWT)保存到 `~/.xce/config.json`。
- 默认账号定义在 `run/apiserver/config/auth.yaml`:`admin`/`admin`、`rshen`/`Letmein`(cluster_admin)、`user1`/`user1`、`user2`/`user2`(basic_user)。
### 7.2 登录后的常用命令
```
xce login # 登录
xce logout # 退出(清除该服务器登录信息)
xce list # 列出已登录的 API Server
xce id # 查询集群信息(无集群配置时返回:no cluster found)
xce hosts list # 查询主机(无主机时返回:no host found)
xce jobs ... # 作业管理
xce queues ... # 队列管理
xce users ... # 用户管理
xce roles ... # 角色管理
xce permissions # 权限管理
xce resources # 资源管理
```
> 注:单节点冷启动未配置 `cluster.bin` 时,集群/主机等查询返回"无数据"属预期行为,认证链路本身已验证正常。
## 8. xce_test 使用(直连 controller 的测试客户端)
`xce_test` 是直接连接 `xce_controller`(端口 9990,走 TLS 协议)的测试客户端,**绕过 apiserver**,直接读写集群状态。构建产物在 `src/build/controller/xce_test`(与 `xce_controller` 一同构建)。
> 与 `xcecli` 的区别:`xcecli` 走 apiserver 的 HTTP REST API(8080);`xce_test` 直连 controller 本身(9990),是 HA soak 测试(`/xce-test-ha`、`run-ha-soak.sh`)的客户端驱动。
```
jsxing@test01:~$ xce_test
xce>client
xce/client>
xce/client>workerlist
HostName Status HostType HostModel CPUfactor MaxCPU MaxMem MaxSwap MaxTmp NDisks
test01 UNAVAIL unknown unknown 0.000000 0 0.000000 0.000000 0.000000 0
h1 UNAVAIL unknown unknown 0.000000 0 0.000000 0.000000 0.000000 0
```
### 8.1 用法(stdin 命令脚本)
```bash
printf 'set masters ctrl-1=localhost:9990
set myname probe
client
wait-primary 3000
ctrlstatus
quit
' | ASAN_OPTIONS=detect_leaks=0 src/build/controller/xce_test
```
实测输出:
```
wait-primary: primary 'ctrl-1' serving after 48 ms
Primary: ctrl-1
term: 4
committedLsn: 0
appliedLsn: 0
```
> `ASAN_OPTIONS=detect_leaks=0` 是必需的:debug/ASAN 构建在退出时会报无害的内存泄漏,污染退出码(但不影响命令输出)。
### 8.2 常用设置
| 命令 | 说明 |
|------|------|
| `set masters ctrl-1=localhost:9990` | controller 候选列表(支持 HA failover,多个用逗号分隔,格式同 `--peers`) |
| `set myname probe` | 客户端标识 |
| `set secret ...` | 认证密钥(默认 `keepSecret`) |
### 8.3 命令结构(三个上下文)
| 上下文 | 进入命令 | 可用命令 |
|--------|---------|---------|
| **client**(客户端) | `client` | `wait-primary`、`ctrlstatus`、`jobsub`/`jobrun`/`jobkill`/`jobckpt`/`jobinfo`/`joblist`、`workerlist`、`reslist`/`resval`、`qctrl`、`hello`、`expect-job(s)`、`shutdown`、`taskrun`、`waldump`/`walcompare`/`snapverify`/`snapdiff` |
| **worker**(模拟工作节点) | `worker` | `join`、`jstart`/`jfinish`/`jfail`、`resupdate` |
| **scheduler**(模拟调度器) | `scheduler` | `join`、`getconf`、`setpend` |
> 注:单节点冷启动未配置 `cluster.bin` 时,`workerlist`/`joblist`/`reslist` 等返回空属预期行为;连接与查询链路本身已验证正常。
## 9. 修复的问题记录
### 9.1 controller 在 scheduler 加入时 ASan 崩溃(已修复)
**现象**:单节点冷启动(无 `cluster.bin` 配置)时启动 scheduler,controller 立即崩溃:
```
AddressSanitizer: SEGV on unknown address 0x000000000038
#0 xcesched_push_configs ../controller/sched.c:172
#1 on_scheduler_join ../controller/sched.c:667
#2 handle_scheduler_join ../controller/join.c:165
```
**根因**:冷启动时 `c->myconf->cluster_config` 为 NULL,`xcesched_push_configs` 无条件解引用(空指针 + 偏移 0x38)。同文件 `worker.c` 的 `find_matched_hostconfig` 有防护,唯独 `sched.c` 没有。
**修复**(`src/controller/sched.c`):
```c
if (c->myconf == NULL || c->myconf->cluster_config == NULL) {
WARN("%s: cluster_config not loaded yet, skipping config push", __func__);
return 0;
}
```
配置未就绪时跳过推送、不崩溃,scheduler 之后重新加入时会收到配置。
**验证**:修复后 controller 日志出现 `cluster_config not loaded yet, skipping config push`,scheduler 成功接入,进程稳定。
### 9.2 单节点冷启动时序(正常行为)
controller 持久化了 `term` 后重启,会先进入 `CTRL_STANDBY`:
1. 启动时 `load_primary_config` 找不到配置 → "will fetch from primary after election"
2. 进入 `CTRL_STANDBY`,等待 primary
3. 约 10s liveness 超时 → 触发 PreVote failover
4. PreVote 单节点立即胜出 → 进入真实选举 → `CTRL_PRIMARY`(term+1)
5. 此期间 scheduler 的 role-2 join 会被拒绝(`standby has no primary yet`),scheduler 自动重试,成为 primary 后即接入
因此启动顺序无关紧要:scheduler / worker 连不上会重试,controller 竞选完成后自然接入。
### 9.3 worker join 返回 EINVAL(无集群配置)
**现象**:`xce_test` worker 上下文执行 `join` 报 `xce_join: Invalid argument`。
**根因**:与 9.1 同一个根因(冷启动无 `cluster.bin`)。`handle_worker_join`(join.c)先调 `find_matched_hostconfig` 找匹配主机配置,而该函数(worker.c)在 `c->myconf->cluster_config == NULL` 时返回 NULL 并设 `errno = EINVAL`,controller 据此回复 `-EINVAL`。
**修复**:部署集群配置后重启 controller——即 `./all.sh config deploy`。修复后 `join` 输出 `host join ok`,`workerlist`(client 上下文)可见 `h1` 已注册。
> 注意:`workerlist`/`joblist`/`reslist` 等查询命令只在 **client** 上下文可用,worker 上下文里没有这些命令(正常命令集划分)。
### 9.4 作业可调度但运行时立即退出(exit 126,Permission denied)
**现象**:作业能被调度(`jobs list` 可见),但一运行就结束。`xcecd.log` 显示:
```
Worker 188071 exited with code 126
```
systemd journal 给出真正原因:
```
sh[188071]: /bin/sh: 1: exec: /home/jsxing/allwork/git/myxce/src/build/worker/xcecd_worker: Permission denied
systemd[1]: xcecd-worker-1-0-...service: Main process exited, code=exited, status=126/n/a
```
**根因**:每个作业的 worker(`xcecd_worker`)由 systemd 临时单元以**作业执行用户**(如 `admin`)身份启动(`User=<execuser>`)。而 `xcecd` 通过 `XCE_SERVER_DIR`(未设置时按自身二进制路径推断)去找 `xcecd_worker`,推断结果是仓库路径 `/home/jsxing/allwork/...`。该路径的父目录 `/home/jsxing` 权限为 `750`(仅属主可进入),`admin` 无法穿越,于是 `exec` 失败 → `Permission denied` → sh 退出码 126。`namei -l` 可确认:
```
drwxr-x--- jsxing jsxing home/jsxing <- 其他用户无法进入
-rwxr-xr-x jsxing jsxing .../xcecd_worker
```
> 相关背景:`controller_connector.c` 里 bug-1507 的注释只处理了"执行用户不存在"(systemd 退出码 217 EXIT_USER),未覆盖"二进制路径不可达"(126)这一种情况。
**修复**(`all.sh`):
1. 新增 customize 变量 `g_worker_bin_dir="/opt/xce/bin"`(所有作业用户都能到达的目录,XCE 标准布局)。
2. `svc_prepare_worker` 用 `sudo install -m 0755` 把 `xcecd_worker` 装到 `${g_worker_bin_dir}`(源更新才重装,幂等)。
3. 新增 `svc_env_worker`:启动 worker 时注入 `XCE_SERVER_DIR=${g_worker_bin_dir}`(`svc_start` 对 sudo 服务通过 `sudo -n env ...` 注入),使 `dbus_start_worker` 使用安装好的二进制而不是仓库路径。
**验证**:`./all.sh restart worker` 后:
- `/opt/xce/bin/xcecd_worker` 存在且 `admin` 可执行(`sudo -u admin /opt/xce/bin/xcecd_worker -h` 正常)。
- `xcecd` 进程环境含 `XCE_SERVER_DIR=/opt/xce/bin`。
- `xcecli jobs submit sleep 60` 状态保持 `RUN` 直到跑满 60s,controller 日志 `on_job_finish ... exit code 0` 干净结束。
> 说明:worker 日志目录不是问题——`xcecd` 启动时会 `chmod(logdir, 0777)` 并预创建 `xcecd_worker.log`(0666),执行用户可写。
### 9.5 作业无法调度
修改 src/configs/*.yaml 后必须重新 ./all.sh config deploy —— cluster.bin 是编译产物,不会自动跟随源文件。
## 10. 使用注意事项
1. **sudo**:worker 需要 root。要求 `sudo -n` 可用(NOPASSWD 或缓存凭证);长期使用建议配置 `/etc/sudoers` 的 NOPASSWD。
2. **构建环境**:构建需 `nix develop` 环境(提供 meson、openssl 等工具)。
3. **日志查看**:服务启动失败时,`status` 会显示日志最后一行,完整日志在 `logs/<服务名>.log`。
4. **PID 文件**:`run/<服务名>.pid`;stale pid 文件会被自动清理。
5. **测试服务管理**:如需测试 start/stop 机制,建议用临时副本 + 无害二进制(如 `/bin/sleep` + 自定 `svc_args_<测试名>`),不要污染真实服务。
## 11. 目录结构速查
```
all.sh # 主脚本
all_README.md # 本文档
src/build/ # 开发版构建产物
controller/xce_controller
controller/xce_test
scheduler/xce_scheduler
worker/xcecd
frontend/apiserver/xce_apiserver
frontend/xcecli/xcecli
src/build-prod/ # 生产版构建产物
deploy/ # 统一部署根目录(${g_deploy_dir})
conf/ # 生成配置
worker.yaml # worker 配置(svc_prepare_worker 生成,log_dir/log_level 跟随 all.sh)
run/ # PID 文件 / 证书 / 数据
controller.pid # PID 文件
scheduler.pid
worker.pid
apiserver.pid
certs/ # TLS 证书(自动生成)
controller/ # controller 数据目录
ctrl-1/conf/ # cluster.bin.* + current(config deploy 生成)
ctrl-1/data/ # WAL / snap / ha-term
apiserver/ # apiserver top 目录
config/ # app.yaml / logger.yaml / rbac.yaml / auth.yaml(自动生成)
logs/ # apiserver 自身日志
logs/ # 服务日志
controller.log
scheduler.log
worker.log
apiserver.log
~/.xce/config.json # xcecli 登录令牌(JWT,login 后生成)
```
> 旧路径 `/opt/xce/conf/worker.yaml`(需 root 写入)已不再被 all.sh 引用;worker 配置统一在 `deploy/conf/worker.yaml`。
> 作业 worker 二进制例外:`xcecd_worker` 被 systemd 以作业执行用户身份运行,必须装在所有人可访问的位置(默认 `/opt/xce/bin/xcecd_worker`,见 `g_worker_bin_dir` 与 9.4)。
# xcecli cases
## jobs
```bash
xcecli jobs submit -J 'A[1-10]' sleep 222
xcecli jobs list
xcecli hosts list
xcecli jobs submit sleep 100
```