forked from lei.chen/myxce
add run scripts
This commit is contained in:
parent
68ab5a071a
commit
c1e9265b14
3 changed files with 1391 additions and 0 deletions
795
all.sh
Executable file
795
all.sh
Executable file
|
|
@ -0,0 +1,795 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# prepare env begin
|
||||||
|
# sudo chmod 775 /nix/var/nix/daemon-socket
|
||||||
|
# sudo systemctl restart nix-daemon
|
||||||
|
# prepare env end
|
||||||
|
|
||||||
|
# customzie begin
|
||||||
|
#g_code_base="/home/jsxing/allwork/git/xce"
|
||||||
|
g_code_base="/home/jsxing/allwork/git/myxce"
|
||||||
|
|
||||||
|
# services registry: name|binary path, managed by start/stop/restart/status
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Unified deployment root: ALL configs, logs and runtime state live under one
|
||||||
|
# directory next to all.sh, so nothing references system dirs (/opt/xce) or
|
||||||
|
# stale repo paths. Layout:
|
||||||
|
# deploy/conf/ worker.yaml + other generated configs
|
||||||
|
# deploy/run/ pid files, certs, controller workdir, apiserver topdir
|
||||||
|
# deploy/logs/ controller/scheduler/worker log files
|
||||||
|
g_deploy_dir="${g_code_base}/deploy"
|
||||||
|
g_conf_dir="${g_deploy_dir}/conf" # generated config files (worker.yaml, ...)
|
||||||
|
g_run_dir="${g_deploy_dir}/run" # pid files, certs, controller/apiserver data
|
||||||
|
g_log_dir="${g_deploy_dir}/logs" # log files
|
||||||
|
|
||||||
|
# Log verbosity for ALL components. One of: info | debug | debug2 | debug3.
|
||||||
|
# controller/scheduler: mapped to -v / -vv / -vvv (argp --verbose)
|
||||||
|
# worker (xcecd): written into worker.yaml log_level field
|
||||||
|
# apiserver: written into logger.yaml level (zap; debug2/3 -> debug)
|
||||||
|
g_loglevel="debug3"
|
||||||
|
|
||||||
|
# Per-job worker binary install dir. xcecd_worker is exec'd by systemd AS the
|
||||||
|
# job's exec user (e.g. admin), so it must live somewhere every user can reach.
|
||||||
|
# The repo path under /home/<user> is usually 750 and blocks other users ->
|
||||||
|
# exec fails with "Permission denied" (sh exit 126) and every job dies instantly.
|
||||||
|
# /opt/xce/bin is the canonical XCE layout. svc_prepare_worker installs it there
|
||||||
|
# via sudo and svc_env_worker points XCE_SERVER_DIR at it. Can be overridden.
|
||||||
|
g_worker_bin_dir="/opt/xce/bin"
|
||||||
|
# customize end
|
||||||
|
|
||||||
|
# usage begin
|
||||||
|
|
||||||
|
# ./all.sh build -c # 清理
|
||||||
|
# ./all.sh build -d # 开发构建(带耗时统计)
|
||||||
|
# ./all.sh build -p # 生产构建(带耗时统计)
|
||||||
|
# ./all.sh config deploy # 构建+部署集群配置(cluster.bin)并重启 controller
|
||||||
|
# ./all.sh config backup # 备份所有服务配置到 ~/all_configs
|
||||||
|
# ./all.sh start [svc] # 启动服务(默认全部):controller scheduler worker apiserver
|
||||||
|
# ./all.sh stop [svc] # 停止服务(默认全部)
|
||||||
|
# ./all.sh restart [svc] # 重启服务(默认全部)
|
||||||
|
# ./all.sh status [svc] # 查看服务状态(默认全部)
|
||||||
|
|
||||||
|
# -d/-p:增量编译,只有 xceadmin 每次固定重跑(~11s)+ 首次/源码变更时全量。初次全量约 60s。
|
||||||
|
# -c:彻底,重建一次即可得到干净状态。
|
||||||
|
# svc 取值为:controller / scheduler / worker,可传多个,如 ./all.sh start controller scheduler
|
||||||
|
# worker (xcecd) 需要 root,通过 sudo 启动/停止;sudo 需免密或已有缓存凭证(sudo -n)
|
||||||
|
# 无 cluster.bin 时 controller 会拒绝 worker join(EINVAL)——用 ./all.sh config deploy 部署
|
||||||
|
# 所有配置/logs/run 统一在 ${g_deploy_dir}(deploy/)下:
|
||||||
|
# worker.yaml 生成在 ${g_conf_dir}/worker.yaml(不再用 /opt/xce/conf/worker.yaml)
|
||||||
|
# controller 配置在 ${g_run_dir}/controller/ctrl-1/conf
|
||||||
|
# apiserver 配置在 ${g_run_dir}/apiserver/config
|
||||||
|
# 日志在 ${g_log_dir}/,pid 在 ${g_run_dir}/
|
||||||
|
|
||||||
|
# usage end
|
||||||
|
|
||||||
|
# log in a function, please print the calling function name
|
||||||
|
function _in() {
|
||||||
|
echo "[$(date +'%Y-%m-%d %H:%M:%S')] [${FUNCNAME[1]}] $1"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map g_loglevel -> argp --verbose flag count (-v/-vv/-vvv) for controller and
|
||||||
|
# scheduler (they call verbose_to_loglevel(); see src/lib/utils.c).
|
||||||
|
function _verbose_flags() {
|
||||||
|
case "${g_loglevel:-info}" in
|
||||||
|
debug3) echo "-vvv" ;;
|
||||||
|
debug2) echo "-vv" ;;
|
||||||
|
debug) echo "-v" ;;
|
||||||
|
*) echo "" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map g_loglevel -> worker.yaml log_level value (xcecd level_to_int()).
|
||||||
|
function _worker_loglevel() {
|
||||||
|
case "${g_loglevel:-info}" in
|
||||||
|
debug|debug2|debug3|notice|warn|error|fatal|off) echo "${g_loglevel}" ;;
|
||||||
|
*) echo "info" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Map g_loglevel -> apiserver logger.yaml level (zap; only debug/info/warn/...).
|
||||||
|
function _apiserver_loglevel() {
|
||||||
|
case "${g_loglevel:-info}" in
|
||||||
|
debug|debug2|debug3) echo "debug" ;;
|
||||||
|
*) echo "info" ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# run a command and log the command and its output
|
||||||
|
function run_cmd() {
|
||||||
|
local datestr=$(date "+%Y-%m-%d %H:%M:%S")
|
||||||
|
local cmd="$1 2>&1"
|
||||||
|
echo "[${datestr}]executing: ${1}"
|
||||||
|
|
||||||
|
echo "${cmd}" | awk '
|
||||||
|
function cmd( c )
|
||||||
|
{
|
||||||
|
while( (c|getline foo) > 0 )
|
||||||
|
printf( "%s\n", foo )
|
||||||
|
close( c );
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
cmd( $0 );
|
||||||
|
}
|
||||||
|
'
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- service management begin ----
|
||||||
|
|
||||||
|
# Return the startup arguments for a service.
|
||||||
|
# Override these in the customize section to fit your deployment.
|
||||||
|
function svc_args_controller() {
|
||||||
|
echo "--myname ctrl-1 --peers ctrl-1=localhost:9990 \
|
||||||
|
--workdir ${g_run_dir}/controller --certdir ${g_run_dir}/certs \
|
||||||
|
--logfile ${g_log_dir}/controller.log --vcpus 4 $(_verbose_flags)"
|
||||||
|
}
|
||||||
|
function svc_args_scheduler() {
|
||||||
|
echo "-l ${g_log_dir}/scheduler.log $(_verbose_flags)"
|
||||||
|
}
|
||||||
|
function svc_args_worker() {
|
||||||
|
# NOTE: when -c is given xcecd IGNORES -l/-p/-m (config file wins), so the
|
||||||
|
# log dir is fully controlled by ${g_conf_dir}/worker.yaml (log_dir field).
|
||||||
|
echo "-c ${g_conf_dir}/worker.yaml -n localhost01"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Extra environment for the worker (xcecd) process. XCE_SERVER_DIR tells xcecd
|
||||||
|
# where to find xcecd_worker for per-job workers; it must point at a directory
|
||||||
|
# every job exec user can reach (see g_worker_bin_dir). Without it xcecd infers
|
||||||
|
# the dir from its own binary path (under /home/<user>) which job users can't
|
||||||
|
# traverse -> exec fails "Permission denied" / exit 126. svc_start prepends
|
||||||
|
# these via `env` for sudo-launched services.
|
||||||
|
function svc_env_worker() {
|
||||||
|
echo "XCE_SERVER_DIR=${g_worker_bin_dir}"
|
||||||
|
}
|
||||||
|
function svc_args_apiserver() {
|
||||||
|
echo "-d ${g_run_dir}/apiserver"
|
||||||
|
}
|
||||||
|
|
||||||
|
# services that must run as root (launched/stopped via sudo)
|
||||||
|
function svc_needs_sudo() {
|
||||||
|
case "$1" in
|
||||||
|
worker) return 0 ;;
|
||||||
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional per-service prepare hook, called by svc_start before launch.
|
||||||
|
# controller: ensure the self-signed TLS cert/key exist (like deploy-ha.sh).
|
||||||
|
function svc_prepare_controller() {
|
||||||
|
local certdir="${g_run_dir}/certs"
|
||||||
|
mkdir -p "${certdir}"
|
||||||
|
if [ ! -f "${certdir}/xce.crt" ] || [ ! -f "${certdir}/xce.key" ]; then
|
||||||
|
_in "Generating self-signed TLS cert/key in ${certdir}"
|
||||||
|
openssl req -x509 -newkey rsa:2048 -nodes -days 3650 \
|
||||||
|
-keyout "${certdir}/xce.key" -out "${certdir}/xce.crt" \
|
||||||
|
-subj "/CN=xce-dev-test" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# apiserver: ensure <topdir>/config/app.yaml + logger.yaml exist
|
||||||
|
# (app.yaml schema-valid, backend -> controller on 9990). Without them the
|
||||||
|
# Go binary refuses to start.
|
||||||
|
function svc_prepare_apiserver() {
|
||||||
|
local topdir="${g_run_dir}/apiserver"
|
||||||
|
local confdir="${topdir}/config"
|
||||||
|
mkdir -p "${confdir}"
|
||||||
|
if [ ! -f "${confdir}/logger.yaml" ]; then
|
||||||
|
_in "Generating default ${confdir}/logger.yaml (level=$(_apiserver_loglevel))"
|
||||||
|
cat > "${confdir}/logger.yaml" <<EOF
|
||||||
|
logging:
|
||||||
|
level: "$(_apiserver_loglevel)"
|
||||||
|
encoding: "console"
|
||||||
|
outputPath: "apiserver.log"
|
||||||
|
file:
|
||||||
|
maxSize: 64
|
||||||
|
maxBackups: 10
|
||||||
|
maxAge: 30
|
||||||
|
compress: true
|
||||||
|
encoderConfig:
|
||||||
|
messageKey: "msg"
|
||||||
|
levelKey: "level"
|
||||||
|
timeKey: "ts"
|
||||||
|
nameKey: "logger"
|
||||||
|
callerKey: "caller"
|
||||||
|
functionKey: "func"
|
||||||
|
stacktraceKey: "stacktrace"
|
||||||
|
timeEncoder: "iso8601"
|
||||||
|
levelEncoder: "capital"
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
# keep existing logger.yaml but sync level to the configured g_loglevel
|
||||||
|
local want="$(_apiserver_loglevel)"
|
||||||
|
if ! grep -q '^ level: "'"${want}"'"' "${confdir}/logger.yaml"; then
|
||||||
|
_in "updating ${confdir}/logger.yaml level -> ${want}"
|
||||||
|
sed -i 's/^ level: ".*"/ level: "'"${want}"'"/' "${confdir}/logger.yaml"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ ! -f "${confdir}/rbac.yaml" ]; then
|
||||||
|
if [ -f "${g_code_base}/src/frontend/apiserver/config/rbac.yaml" ]; then
|
||||||
|
_in "Copying sample rbac.yaml"
|
||||||
|
cp "${g_code_base}/src/frontend/apiserver/config/rbac.yaml" \
|
||||||
|
"${confdir}/rbac.yaml"
|
||||||
|
else
|
||||||
|
_in "Generating minimal rbac.yaml"
|
||||||
|
cat > "${confdir}/rbac.yaml" <<'EOF'
|
||||||
|
permissions: []
|
||||||
|
roles: {}
|
||||||
|
users: {}
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ ! -f "${confdir}/auth.yaml" ]; then
|
||||||
|
if [ -f "${g_code_base}/src/frontend/apiserver/config/auth.yaml" ]; then
|
||||||
|
_in "Copying sample auth.yaml"
|
||||||
|
cp "${g_code_base}/src/frontend/apiserver/config/auth.yaml" \
|
||||||
|
"${confdir}/auth.yaml"
|
||||||
|
else
|
||||||
|
_in "Generating minimal auth.yaml"
|
||||||
|
cat > "${confdir}/auth.yaml" <<'EOF'
|
||||||
|
provider: config_file
|
||||||
|
config_file:
|
||||||
|
users:
|
||||||
|
- username: admin
|
||||||
|
password: admin
|
||||||
|
roles: [cluster_admin]
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
if [ ! -f "${confdir}/app.yaml" ]; then
|
||||||
|
_in "Generating default ${confdir}/app.yaml"
|
||||||
|
cat > "${confdir}/app.yaml" <<'EOF'
|
||||||
|
app:
|
||||||
|
name: "xce_apiserver"
|
||||||
|
runmode: "release"
|
||||||
|
http_port: 8080
|
||||||
|
version: "1.0.0"
|
||||||
|
|
||||||
|
security:
|
||||||
|
https:
|
||||||
|
enabled: false
|
||||||
|
port: 8443
|
||||||
|
cert_file: "config/ssl/server.crt"
|
||||||
|
key_file: "config/ssl/server.key"
|
||||||
|
|
||||||
|
authentication:
|
||||||
|
token:
|
||||||
|
type: "jwt"
|
||||||
|
secretKey: "xce-api-key"
|
||||||
|
token_expire_time: 86400
|
||||||
|
|
||||||
|
rate_limit:
|
||||||
|
global:
|
||||||
|
rate: 1000
|
||||||
|
burst: 2000
|
||||||
|
window: second
|
||||||
|
endpoints:
|
||||||
|
/xce/v1/auth/logon:
|
||||||
|
rate: 100
|
||||||
|
burst: 200
|
||||||
|
window: second
|
||||||
|
|
||||||
|
whitelist:
|
||||||
|
- "0.0.0.0/0"
|
||||||
|
- "::/0"
|
||||||
|
|
||||||
|
file_upload:
|
||||||
|
max_size: 100 MB
|
||||||
|
allowed_types:
|
||||||
|
- "application/x-tar"
|
||||||
|
- "application/zip"
|
||||||
|
|
||||||
|
job:
|
||||||
|
max_array_size: 1000
|
||||||
|
|
||||||
|
cache:
|
||||||
|
refresh_interval: 30
|
||||||
|
|
||||||
|
cors:
|
||||||
|
enabled: true
|
||||||
|
allowed_origins:
|
||||||
|
- "*"
|
||||||
|
|
||||||
|
backend:
|
||||||
|
address: "localhost"
|
||||||
|
port: 9990
|
||||||
|
from: "xce_apiserver"
|
||||||
|
key: "keepsecret$"
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# worker: generate worker.yaml under ${g_conf_dir} so the worker never reads a
|
||||||
|
# system path (/opt/xce/conf/worker.yaml) or a stale repo's log_dir. The
|
||||||
|
# log_dir field must point at our ${g_log_dir}; when -c is given xcecd ignores
|
||||||
|
# -l so this file is the single source of truth for the worker's log location.
|
||||||
|
function svc_prepare_worker() {
|
||||||
|
mkdir -p "${g_conf_dir}"
|
||||||
|
local wf="${g_conf_dir}/worker.yaml"
|
||||||
|
local lvl="$(_worker_loglevel)"
|
||||||
|
if [ -f "$wf" ]; then
|
||||||
|
# keep existing file but sync log_level to the configured g_loglevel
|
||||||
|
if ! grep -q "log_level: \"${lvl}\"" "$wf"; then
|
||||||
|
_in "updating worker.yaml log_level -> ${lvl}"
|
||||||
|
sed -i "s/log_level:.*/log_level: \"${lvl}\"/" "$wf"
|
||||||
|
fi
|
||||||
|
_in "worker config: ${wf} (log_level=${lvl})"
|
||||||
|
else
|
||||||
|
_in "Generating ${wf}"
|
||||||
|
cat > "$wf" <<EOF
|
||||||
|
---
|
||||||
|
workerconf:
|
||||||
|
management_list: ["localhost"]
|
||||||
|
controller_port: 9990
|
||||||
|
log_level: "${lvl}"
|
||||||
|
log_dir: "${g_log_dir}"
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Install xcecd_worker somewhere every job exec user can run it. The job's
|
||||||
|
# worker is launched by systemd with User=<exec user>; the repo path under
|
||||||
|
# /home/<user> is usually 750, so other users get "Permission denied"
|
||||||
|
# (exit 126) and jobs die instantly. xcecd finds the binary via
|
||||||
|
# XCE_SERVER_DIR (svc_env_worker), so it must match this install dir.
|
||||||
|
local worker_bin="${g_code_base}/src/build/worker/xcecd_worker"
|
||||||
|
local worker_dst="${g_worker_bin_dir}/xcecd_worker"
|
||||||
|
if [ -x "${worker_bin}" ]; then
|
||||||
|
if [ ! -f "${worker_dst}" ] || [ "${worker_bin}" -nt "${worker_dst}" ]; then
|
||||||
|
_in "installing ${worker_bin} -> ${worker_dst}"
|
||||||
|
sudo -n mkdir -p "${g_worker_bin_dir}"
|
||||||
|
sudo -n install -m 0755 "${worker_bin}" "${worker_dst}"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
_in "WARN: ${worker_bin} not found; jobs will fail to exec xcecd_worker" >&2
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Optional per-service precheck hook, called by svc_start BEFORE launching.
|
||||||
|
# controller: refuse to start without a deployed cluster.bin — without one the
|
||||||
|
# controller never becomes primary and apiserver's backend init fails with
|
||||||
|
# -10013 (bug-1514). Prints the active config when present.
|
||||||
|
function svc_check_controller() {
|
||||||
|
local confdir="${g_run_dir}/controller/ctrl-1/conf"
|
||||||
|
local cur ref bin
|
||||||
|
if [ ! -f "${confdir}/current" ]; then
|
||||||
|
_in "controller 未部署集群配置(缺少 ${confdir}/current)" >&2
|
||||||
|
_in "请先运行: ./all.sh config deploy # 从 src/configs 生成 cluster.bin 并部署" >&2
|
||||||
|
_in "然后再: ./all.sh start" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
ref="$(cat "${confdir}/current" 2>/dev/null)"
|
||||||
|
bin="${confdir}/${ref}"
|
||||||
|
if [ -n "$ref" ] && [ -f "$bin" ]; then
|
||||||
|
_in "controller 使用集群配置: ${bin} (current -> ${ref})"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
_in "controller 配置指针 current -> ${ref} 指向的文件不存在(${bin})" >&2
|
||||||
|
_in "请重新运行: ./all.sh config deploy" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# list registered service names
|
||||||
|
function _svc_names() {
|
||||||
|
local entry n
|
||||||
|
for entry in "${g_services[@]}"; do
|
||||||
|
n="${entry%%|*}"
|
||||||
|
echo "$n"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# resolve binary path for a service name (empty if unknown)
|
||||||
|
function _svc_bin() {
|
||||||
|
local name="$1" entry n b
|
||||||
|
for entry in "${g_services[@]}"; do
|
||||||
|
n="${entry%%|*}"; b="${entry#*|}"
|
||||||
|
[ "$n" = "$name" ] && { echo "$b"; return 0; }
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function _svc_pidfile() { echo "${g_run_dir}/$1.pid"; }
|
||||||
|
|
||||||
|
# check liveness of a service pid; uses sudo for root (sudo) services
|
||||||
|
function _svc_alive() {
|
||||||
|
local name="$1" pid="$2"
|
||||||
|
if svc_needs_sudo "$name"; then
|
||||||
|
sudo -n kill -0 "$pid" 2>/dev/null
|
||||||
|
else
|
||||||
|
kill -0 "$pid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# send a signal to a service pid; uses sudo for root (sudo) services
|
||||||
|
function _svc_kill() {
|
||||||
|
local name="$1" sig="$2" pid="$3"
|
||||||
|
if svc_needs_sudo "$name"; then
|
||||||
|
if [ -n "$sig" ]; then
|
||||||
|
sudo -n kill -"$sig" "$pid" 2>/dev/null
|
||||||
|
else
|
||||||
|
sudo -n kill "$pid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
if [ -n "$sig" ]; then
|
||||||
|
kill -"$sig" "$pid" 2>/dev/null
|
||||||
|
else
|
||||||
|
kill "$pid" 2>/dev/null
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# print the live pid of a service (or empty); cleanup stale pidfile
|
||||||
|
function _svc_pid() {
|
||||||
|
local name="$1" pidfile pid
|
||||||
|
pidfile="$(_svc_pidfile "$name")"
|
||||||
|
if [ -f "$pidfile" ]; then
|
||||||
|
pid="$(cat "$pidfile" 2>/dev/null)"
|
||||||
|
if [ -n "$pid" ] && _svc_alive "$name" "$pid"; then
|
||||||
|
echo "$pid"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
rm -f "$pidfile"
|
||||||
|
fi
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# resolve target services from args; default to all registered
|
||||||
|
function _svc_resolve() {
|
||||||
|
if [ $# -eq 0 ]; then
|
||||||
|
_svc_names
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
local name bin
|
||||||
|
for name in "$@"; do
|
||||||
|
bin="$(_svc_bin "$name")" || {
|
||||||
|
_in "unknown service: $name (valid: $(_svc_names | tr '\n' ' '))" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
echo "$name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
# start a single service (background, pidfile + log)
|
||||||
|
function svc_start() {
|
||||||
|
local name="$1" bin args pidfile log
|
||||||
|
bin="$(_svc_bin "$name")" || return 1
|
||||||
|
args="$(svc_args_${name} 2>/dev/null)" || args=""
|
||||||
|
|
||||||
|
[ -x "$bin" ] || { _in "binary not found: $bin (run ./all.sh build first)"; return 1; }
|
||||||
|
if [ -n "$(_svc_pid "$name")" ]; then
|
||||||
|
# already running: still report the active config (best-effort).
|
||||||
|
# stdout carries the "using config" line; stderr hints are muted here.
|
||||||
|
if declare -F "svc_check_${name}" >/dev/null 2>&1; then
|
||||||
|
"svc_check_${name}" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
_in "$name already running (pid $(_svc_pid "$name"))"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${g_run_dir}" "${g_log_dir}"
|
||||||
|
pidfile="$(_svc_pidfile "$name")"
|
||||||
|
log="${g_log_dir}/${name}.log"
|
||||||
|
|
||||||
|
# optional per-service prepare hook (e.g. controller TLS certs)
|
||||||
|
if declare -F "svc_prepare_${name}" >/dev/null 2>&1; then
|
||||||
|
"svc_prepare_${name}" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# optional per-service precheck hook (e.g. controller requires cluster.bin)
|
||||||
|
if declare -F "svc_check_${name}" >/dev/null 2>&1; then
|
||||||
|
"svc_check_${name}" || return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if svc_needs_sudo "$name"; then
|
||||||
|
_in "Starting ${name} as root via sudo: ${bin} ${args}"
|
||||||
|
# optional per-service env (e.g. worker: XCE_SERVER_DIR so job workers
|
||||||
|
# can exec xcecd_worker from a world-accessible dir)
|
||||||
|
local envp=""
|
||||||
|
if declare -F "svc_env_${name}" >/dev/null 2>&1; then
|
||||||
|
envp="$(svc_env_${name})"
|
||||||
|
fi
|
||||||
|
# inner bash writes its own pid, then exec's into the binary so the
|
||||||
|
# pidfile holds the real (root) process pid; nohup guards SIGHUP.
|
||||||
|
# sudo -n = non-interactive; requires NOPASSWD or a cached credential.
|
||||||
|
sudo -n env ${envp} bash -c "echo \$\$ > '${pidfile}'; exec nohup '${bin}' ${args}" \
|
||||||
|
>>"${log}" 2>&1 < /dev/null &
|
||||||
|
else
|
||||||
|
_in "Starting ${name}: ${bin} ${args}"
|
||||||
|
setsid nohup ${bin} ${args} >>"${log}" 2>&1 < /dev/null &
|
||||||
|
echo $! > "${pidfile}"
|
||||||
|
fi
|
||||||
|
disown 2>/dev/null || true
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
local pid
|
||||||
|
pid="$(_svc_pid "$name")"
|
||||||
|
if [ -n "$pid" ]; then
|
||||||
|
_in "${name} started (pid $pid, log ${log})"
|
||||||
|
else
|
||||||
|
_in "${name} FAILED to start -- check ${log}"
|
||||||
|
rm -f "${pidfile}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# stop a single service (pidfile, then pgrep fallback)
|
||||||
|
function svc_stop() {
|
||||||
|
local name="$1" bin pid pidfile
|
||||||
|
bin="$(_svc_bin "$name")" || return 1
|
||||||
|
pidfile="$(_svc_pidfile "$name")"
|
||||||
|
|
||||||
|
pid="$(_svc_pid "$name")"
|
||||||
|
if [ -z "$pid" ]; then
|
||||||
|
# fallback: match by binary name
|
||||||
|
pid="$(pgrep -f "$(basename "$bin")" 2>/dev/null | head -n1)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$pid" ]; then
|
||||||
|
_in "${name} is not running"
|
||||||
|
rm -f "${pidfile}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
_in "Stopping ${name} (pid $pid)"
|
||||||
|
_svc_kill "$name" "" "$pid"
|
||||||
|
local i
|
||||||
|
for i in $(seq 1 10); do
|
||||||
|
_svc_alive "$name" "$pid" || break
|
||||||
|
sleep 0.3
|
||||||
|
done
|
||||||
|
if _svc_alive "$name" "$pid"; then
|
||||||
|
_in "${name} did not exit, sending SIGKILL"
|
||||||
|
_svc_kill "$name" 9 "$pid"
|
||||||
|
sleep 0.3
|
||||||
|
fi
|
||||||
|
rm -f "${pidfile}"
|
||||||
|
_in "${name} stopped"
|
||||||
|
}
|
||||||
|
|
||||||
|
# restart a single service
|
||||||
|
function svc_restart() {
|
||||||
|
local name="$1"
|
||||||
|
svc_stop "$name"
|
||||||
|
svc_start "$name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# print status of a single service
|
||||||
|
function svc_status() {
|
||||||
|
local name="$1" bin pid pidfile log state
|
||||||
|
bin="$(_svc_bin "$name")" || return 1
|
||||||
|
pidfile="$(_svc_pidfile "$name")"
|
||||||
|
log="${g_log_dir}/${name}.log"
|
||||||
|
|
||||||
|
pid="$(_svc_pid "$name")"
|
||||||
|
if [ -n "$pid" ]; then
|
||||||
|
state="running"
|
||||||
|
else
|
||||||
|
state="stopped"
|
||||||
|
fi
|
||||||
|
printf "%-12s %-8s %-8s\n" "$name" "$state" "${pid:--}"
|
||||||
|
printf " binary: %s\n" "$bin"
|
||||||
|
if [ -f "$log" ]; then
|
||||||
|
printf " log: %s\n" "$log"
|
||||||
|
printf " last: %s\n" "$(tail -n1 "$log" 2>/dev/null)"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---- service management end ----
|
||||||
|
|
||||||
|
# clean
|
||||||
|
function build_clean() {
|
||||||
|
_in "Cleaning build artifacts..."
|
||||||
|
local src_dir=${g_code_base}/src
|
||||||
|
for dir in build build-prod; do
|
||||||
|
if [ -d "${src_dir}/${dir}" ]; then
|
||||||
|
run_cmd "rm -rf ${src_dir}/${dir}"
|
||||||
|
_in "Removed ${dir}/"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_dev() {
|
||||||
|
_in "Building development version..."
|
||||||
|
local src_dir=${g_code_base}/src
|
||||||
|
cd ${src_dir}
|
||||||
|
local start_ts=$(date +%s)
|
||||||
|
run_cmd "nix develop --extra-experimental-features \"nix-command flakes\" --command bash -c \"meson setup build --buildtype=debug -Db_sanitize=address && meson compile -C build\""
|
||||||
|
local end_ts=$(date +%s)
|
||||||
|
local elapsed=$((end_ts - start_ts))
|
||||||
|
_in "Build finished in $((elapsed / 60))m $((elapsed % 60))s (${elapsed}s total)"
|
||||||
|
echo "output is in ${src_dir}/build"
|
||||||
|
}
|
||||||
|
|
||||||
|
function build_prod() {
|
||||||
|
_in "Building production version..."
|
||||||
|
local src_dir=${g_code_base}/src
|
||||||
|
cd ${src_dir}
|
||||||
|
local start_ts=$(date +%s)
|
||||||
|
run_cmd "nix develop --extra-experimental-features \"nix-command flakes\" --command bash -c \"meson setup build-prod --buildtype=release && meson compile -C build-prod\""
|
||||||
|
local end_ts=$(date +%s)
|
||||||
|
local elapsed=$((end_ts - start_ts))
|
||||||
|
_in "Build finished in $((elapsed / 60))m $((elapsed % 60))s (${elapsed}s total)"
|
||||||
|
echo "output is in ${src_dir}/build-prod"
|
||||||
|
}
|
||||||
|
|
||||||
|
# deploy cluster config: build cluster.bin from src/configs via xceadmin
|
||||||
|
# (in nix develop), place it in ctrl-1/conf, set conf/current, then restart
|
||||||
|
# the controller so it loads the config. Without a deployed cluster.bin the
|
||||||
|
# controller rejects worker joins with EINVAL (no hostconfig matched).
|
||||||
|
function config_deploy() {
|
||||||
|
_in "Deploying cluster config..."
|
||||||
|
local src_dir=${g_code_base}/src
|
||||||
|
local xceadmin=""
|
||||||
|
local candidate
|
||||||
|
|
||||||
|
# xceadmin lives under whichever meson build dir; try each in turn
|
||||||
|
for candidate in \
|
||||||
|
"${src_dir}/build/xceadmin/.venv/bin/xceadmin" \
|
||||||
|
"${src_dir}/build-prod/xceadmin/.venv/bin/xceadmin" \
|
||||||
|
"$(command -v xceadmin 2>/dev/null || true)"
|
||||||
|
do
|
||||||
|
if [ -n "$candidate" ] && [ -x "$candidate" ]; then
|
||||||
|
xceadmin="$candidate"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
if [ -z "$xceadmin" ]; then
|
||||||
|
_in "xceadmin not found (run ./all.sh build first)"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
_in "Using xceadmin: ${xceadmin}"
|
||||||
|
|
||||||
|
local tmpbin="${g_run_dir}/.cluster.bin.tmp"
|
||||||
|
local confdir="${g_run_dir}/controller/ctrl-1/conf"
|
||||||
|
|
||||||
|
_in "Building cluster.bin from ${src_dir}/configs (in nix develop)"
|
||||||
|
cd ${src_dir}
|
||||||
|
run_cmd "nix develop --extra-experimental-features \"nix-command flakes\" --command bash -c \"${xceadmin} config build ${src_dir}/configs -o ${tmpbin}\""
|
||||||
|
|
||||||
|
if [ ! -s "${tmpbin}" ]; then
|
||||||
|
_in "cluster.bin build failed -- check output above"
|
||||||
|
rm -f "${tmpbin}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
mkdir -p "${confdir}"
|
||||||
|
local tag
|
||||||
|
tag="$(sha256sum "${tmpbin}" | cut -c1-12)"
|
||||||
|
mv "${tmpbin}" "${confdir}/cluster.bin.${tag}"
|
||||||
|
echo "cluster.bin.${tag}" > "${confdir}/current"
|
||||||
|
_in "Deployed cluster.bin.${tag} (current -> cluster.bin.${tag}) in ${confdir}"
|
||||||
|
|
||||||
|
_in "Restarting controller to load config"
|
||||||
|
svc_restart controller
|
||||||
|
}
|
||||||
|
|
||||||
|
# back up all services' runtime configs (controller certs/conf, worker.yaml,
|
||||||
|
# apiserver configs) to ~/all_configs, preserving the directory structure.
|
||||||
|
function config_backup() {
|
||||||
|
_in "Backing up all service configs to ~/all_configs..."
|
||||||
|
local dest=~/all_configs
|
||||||
|
local n=0
|
||||||
|
|
||||||
|
# controller: TLS certs
|
||||||
|
if [ -d "${g_run_dir}/certs" ]; then
|
||||||
|
mkdir -p "${dest}/controller/certs"
|
||||||
|
cp -f ${g_run_dir}/certs/* "${dest}/controller/certs/" 2>/dev/null
|
||||||
|
_in "controller certs -> ${dest}/controller/certs/"
|
||||||
|
n=$((n + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# controller: cluster.bin + current
|
||||||
|
if [ -d "${g_run_dir}/controller/ctrl-1/conf" ]; then
|
||||||
|
mkdir -p "${dest}/controller/conf"
|
||||||
|
cp -f ${g_run_dir}/controller/ctrl-1/conf/cluster.bin.* \
|
||||||
|
"${g_run_dir}/controller/ctrl-1/conf/current" \
|
||||||
|
"${dest}/controller/conf/" 2>/dev/null
|
||||||
|
_in "controller conf -> ${dest}/controller/conf/"
|
||||||
|
n=$((n + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# worker: ${g_conf_dir}/worker.yaml (generated by svc_prepare_worker)
|
||||||
|
mkdir -p "${dest}/worker"
|
||||||
|
if [ -f "${g_conf_dir}/worker.yaml" ]; then
|
||||||
|
cp -f "${g_conf_dir}/worker.yaml" "${dest}/worker/"
|
||||||
|
_in "worker.yaml -> ${dest}/worker/"
|
||||||
|
n=$((n + 1))
|
||||||
|
else
|
||||||
|
_in "worker.yaml (${g_conf_dir}/worker.yaml) not found, skipped"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# apiserver: 4 config files
|
||||||
|
if [ -d "${g_run_dir}/apiserver/config" ]; then
|
||||||
|
mkdir -p "${dest}/apiserver"
|
||||||
|
cp -f ${g_run_dir}/apiserver/config/*.yaml "${dest}/apiserver/" 2>/dev/null
|
||||||
|
_in "apiserver config -> ${dest}/apiserver/"
|
||||||
|
n=$((n + 1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
# scheduler: no standalone config file
|
||||||
|
mkdir -p "${dest}/scheduler"
|
||||||
|
printf 'scheduler 无独立配置文件(启动参数 -l <logfile> 已由 all.sh svc_args_scheduler 管理)\n' \
|
||||||
|
> "${dest}/scheduler/README.txt"
|
||||||
|
|
||||||
|
_in "Backup complete (${n} groups) in ${dest}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# main sub-command dispatch
|
||||||
|
function cmd_build() {
|
||||||
|
local mode="dev"
|
||||||
|
while getopts "cdp" opt; do
|
||||||
|
case $opt in
|
||||||
|
c) build_clean; exit 0 ;;
|
||||||
|
d) mode="dev" ;;
|
||||||
|
p) mode="prod" ;;
|
||||||
|
?) echo "Usage: $0 build [-c] [-d|-p]" && exit 1 ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
if [ "$mode" = "dev" ]; then
|
||||||
|
build_dev
|
||||||
|
else
|
||||||
|
build_prod
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function _cmd_svc_foreach() {
|
||||||
|
# $1 = action (start|stop|restart|status), rest = services
|
||||||
|
local action="$1"; shift
|
||||||
|
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 "$@"; }
|
||||||
|
|
||||||
|
function cmd_status() {
|
||||||
|
local targets
|
||||||
|
targets="$(_svc_resolve "$@")" || return 1
|
||||||
|
printf "%-12s %-8s %-8s\n" "SERVICE" "STATE" "PID"
|
||||||
|
local name
|
||||||
|
for name in $targets; do
|
||||||
|
svc_status "$name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function cmd_config() {
|
||||||
|
case "${1:-deploy}" in
|
||||||
|
deploy) config_deploy ;;
|
||||||
|
backup) config_backup ;;
|
||||||
|
*) echo "Usage: $0 config {deploy|backup}" && exit 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# dispatch
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
595
all_README.md
Normal file
595
all_README.md
Normal file
|
|
@ -0,0 +1,595 @@
|
||||||
|
# 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
|
||||||
|
```
|
||||||
1
all_note.md
Normal file
1
all_note.md
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
all.sh config deploy生成的配置文件worker.yaml位置在/opt/xce/conf/worker.yaml,这个不太好,我想把配置都生成到当前all.sh的同一个目录的某个目录下面。所有的配置都放到这下面,还有logs和run也放这个目录。这样就不会出现我现在worker启动不了的问题。因为worker启动配置/opt/xce/conf/worker.yaml里面引用的还是之前的目录。
|
||||||
Loading…
Add table
Add a link
Reference in a new issue