本页是运维相关当前知识的唯一主题入口,合并部署、迁移、备份、监控与事件响应。实际执行以脚本、Compose、工作流和 Services runbook 为准;生产环境、权限和外部凭据不由本仓库声称拥有。
本仓库没有生产环境。生产 Compose、CI/CD、远程 SSH、证书、registry、数据库和流量切换均是可执行但需部署方授权的控制面;disposable Compose/DinD 只证明仓库实现,不构成生产应用证据。
开发启动命令与 mode 语义由本地开发统一维护;正常 dev-lite/dev-full 都要求 APP_SUBMISSION_ROUTING_MODE=remote 和 SUBMISSION_CUTOVER_COMPLETE=true。
docker/docker-compose.yml 是基础配置;docker/docker-compose.dev.yml 只在 loopback 暴露开发端口;docker/docker-compose.prod.yml 不发布 MySQL、Redis、Nacos 或 backend 端口,前端仅作 HTTPS edge。不要直接用 PM2/Maven 启动 owner runtime,以免绕过 manifest、migration、readiness 和 rollback gate。
Nacos 的 512 MB 堆显式使用 128 MB 年轻代,避免镜像默认的 JVM_XMN=512m 占满堆空间;调整堆上限时应同时检查年轻代和容器内存限额。
Docker Verify builds each service locally (load: true, no push) and runs pinned Trivy v0.74.0 against that local image. Docker Publish pushes digest-only candidates: it does not assign normal sha-* or v* release tags while individual service gates are running.
Each service must pass HIGH/CRITICAL Trivy scanning, then be signed and receive SPDX/SLSA attestations for the same candidate digest. The promote job runs only after all nine matrix builds succeed; it validates all nine reports, source commit, digest subjects, signer identity, and verified DSSE predicates before inspecting or writing any tags. A changed backend image requires a new service release version in both services/pom.xml and its owner POM; an existing version tag may only be reused for the identical digest. Keep contract fixtures aligned with the intended release versions instead of weakening the immutable-tag guard.
Use release-manifest artifact from successful Docker Publish run as canonical deployment input. It contains complete nine-service release-manifest.txt, release-set.json, and per-service evidence. Local scripts/runbooks/promote-release.sh EVIDENCE_DIR SOURCE_SHA ghcr.io/davidhlp/ulticode supports read-only dry-run only; --execute is restricted to Docker Publish on main. The workflow uses repository-wide ulticode-release-tag-writer concurrency with cancellation disabled. If promotion fails after candidate builds succeed, rerun only the failed promote job in that same Actions run, while its nine candidate artifacts remain within their retention period; do not rerun successful build-and-push jobs, since a rebuild may produce a different digest and collide with immutable tags. The workflow is push-only and has no workflow_dispatch. Do not promote tags through local commands or other writers. This serializes repository-controlled writers, not external registry actors, and GHCR offers no compare-and-swap guarantee here. Existing manifest conflicts fail before registry inspection/writes; all planned tags are read back after promotion, including tags that already existed, before complete manifests are written. Matching existing tags are idempotent; a different digest is a hard failure. A mid-promotion failure may leave already verified tags in place, but emits no complete manifest. Never retarget or delete old tags/digests; retain previous successful release artifact for rollback.
After validating the manifest locally, provide its nine lines to the manual cd-deploy.yml or cd-rollback.yml image_refs input. Do not hand-assemble a partial candidate list. Deployment still independently verifies immutable digest references and supply-chain evidence before mutating the host.
Trivy JSON reports are uploaded per candidate even when scanning fails; a failed or skipped scan cannot produce candidate evidence or reach promotion. For vulnerability triage, inspect affected package, installed version, and fixed version.
Runtime images retain pinned base-image digests and apply apk upgrade --no-cache for Alpine security updates. Both Verify and Publish bypass cache only for the final runtime (backend) or production (frontend) stage, so cached upgrade layers cannot retain newly vulnerable packages; builder caches remain enabled. Backend dependencies use Spring Boot's BOM with narrowly scoped security overrides; verify final dependency resolution and image scan results before release.
Backend services retain Java 17 and use the Spring Boot 4 BOM for Spring Framework 7 and Tomcat 11. Existing JSON wire and persisted payload contracts remain on the documented spring-boot-jackson2 compatibility module, selected by spring.http.converters.preferred-json-mapper=jackson2; Jackson 2 security overrides use jackson-2-bom.version, not the Jackson 3 BOM property. Do not mix Spring Framework 7 into a Boot 3 parent or retain a Tomcat 10 override under Boot 4. Dependency upgrades must pass owner-context, serialization, security and RPC checks as well as all nine image scans; reverting code must not rewrite application data or applied migrations.
host-deploy 在任何 migration、Redis ACL materialization、Judge sandbox provisioning 或 Compose mutation 前检查:
- approved source commit;
- canonical migration manifest checksum;
- 必需 Compose/runbook 文件;
- 九服务 immutable digest manifest;
- 合并生产 Compose 配置、TLS/JWKS、owner DB 和必要 secret inputs;
- registry digest、Cosign、SBOM、SLSA、Trivy 证据(若由部署策略要求)。
生产 Judge 需要 JUDGE_DOCKER_HOST、DOCKER_TLS_VERIFY=1、只读 client certificate、共享绝对 workspace、固定 seccomp 和 remote/rootless daemon。生产不挂载 docker.sock、不使用 DOCKER_GID;socket 仅在显式 disposable docker/docker-compose.judge-dev.yml --profile judge-socket 下允许。
发布后先写入 secret-free PENDING_HEALTH descriptor。host-health 必须逐项检查 allowlisted 服务、前端 HTTPS 和 worker readiness;任何失败都写 FAILED 并以非零退出,不能报告部分成功为系统成功。全部通过后才标记 HEALTHY。
回滚必须存在上一 descriptor,并且 source/schema checksum 与批准输入一致;继续使用 skip_migrations=true,不执行 schema downgrade。回滚前先停止新 writer、保留未消费 outbox/PEL/inbox,确认旧 artifact 与 schema/contract 兼容,再恢复 route。不能通过重新开放跨 Owner grant 或切回已删除 writer 回滚。
GitHub Actions 由 .github/workflows/ci.yml 编排,ci-ok 是稳定汇总门禁;服务矩阵在 .github/services-matrix.json(每个 deployable 有 role/release_group/health 分类),owner migration、backup、artifact integrity、health 和 rollback 由可复用 actions/runbooks 承接。完整触发条件以 workflow 为准,避免在文档复制易漂移的 job 列表。
GitLab 直连部署入口(.gitlab-ci.yml)已于 2026-09-03 退役禁用:旧 job 对固定路径执行 git reset --hard 后直接 Compose build/up,绕过 owner migration、release descriptor、immutable image policy 与 health gate,且仓库内无该 runner 仍被授权的证据。任何未来 GitLab 适配器必须消费同一 canonical preflight/descriptor 接口(scripts/runbooks/deployment-integrity.sh),不得恢复 reset/build/up 捷径。只读查看当前 release set 使用:
./scripts/runbooks/deployment-integrity.sh describe # 人类可读
DEPLOYMENT_OUTPUT_FORMAT=json ./scripts/runbooks/deployment-integrity.sh describedescribe 输出 evidence=repository-static(非生产证据);缺 commit/schema/version/digest 时 fail closed。verify-registry 校验 services matrix 与生产 Compose 的 backend 集合双向一致,未登记的新 deployable 会失败。
- Contract 兼容:
services/docs/CONTRACT_COMPAT_GATE.md - Owner migration:
数据库迁移与 Owner 收敛 - 监控与 release annotation:
监控、SLO 与运行证据 - 生产问题与外部触发:
services/docs/SERVICES_ISSUES.md
docker/docker-compose.ha.yml 的 ha profile 是可审计的 stateful reference,不是默认生产 failover:包含 mysql-replica、Redis replica/Sentinel、nacos-2/nacos-3,但 promotion、endpoint 变更、secret rotation、Sentinel-aware client 和 RPO/RTO 仍需 operator。本仓库不承诺 active-active HA。配置保留 masteruser ulticode-replication、sentinel auth-user、sentinel auth-user mymaster ulticode-sentinel、P3-HA-001、mysql-replica 和 redis-sentinel-1 等控制面契约。
init-db/migrations/ 是唯一 Flyway migration source。命名为 V{14-digit timestamp}__Description.sql;已应用 migration 永不编辑,只新增向前 migration。命令、profile、baseline、seed 和低层原语见 ../init-db/README.md。
迁移编排固定为:
shared → auth → admin → app → notification → submission → post-owner controls
owner-migrate / owner-migration-manifest.sh 校验 owner schema/location、runtime 与 migration account 分离、依赖顺序、源文件 checksum,并以 fenced lease 防并发。runtime 账号不能拥有其他 Owner 表、global/schema ALL、GRANT OPTION 或隐式角色继承。
当前逻辑边界:Auth 持 account/credential/RBAC/refresh;App 持 profile 和 OJ/社区聚合;Admin 持治理/审计/设置/backup;Notification 持通知与投递;Submission 持提交与判题 outbox。物理独立实例不是当前前置条件,先完成 schema/account/唯一 writer。
- Expand:新增 owner table、索引、version/updated-at 和 proof;保留兼容读路径。
- Backfill:以稳定主键批量、幂等、insert-only 回填;记录 count、checksum、孤儿、冲突和 checkpoint。
- Verify:检查 missing/extra keys、NULL-safe fields、checksum、writer 状态、权限和 reader 语义。
- Cutover:停止并 drain 所有 writer,显式确认后撤销旧 grants/切换 route;不得顺便做隐式全表复制。
- Contract:观察期、备份和 rollback 证据满足后,才删除 legacy columns/tables/contracts。
Backfill 默认 dry-run;同主键字段冲突 fail closed 并导出 TSV,不覆盖较新的 owner 行。Rollback 回到已验证 artifact,先恢复 route/grant,再按 runbook 回写新增行;不 DROP、TRUNCATE 或编辑历史 migration。
Auth 只写 users account/status/authorization 字段;App 写 user_profiles(account_id, ...)。使用后续 migration 完成回填和 compatibility-column contraction;Search、Admin、Notification 通过 bounded facts/Identity contract,不做跨 Owner SQL join。软删除 account 也必须纳入 checksum 和 reconciliation。
正常 App user、contest、Problem-statistics、user-tag、generation 和 Admin Submission reads 使用 backend-submission owner facts;App 不再保留 Submission local mapper/projection/entity 或 duplicate DTOs。Submission provider 按 account_id 分组分页,单页上限 500;createdSince=null 为全量,非空为包含式增量窗口。Admin 通过 owner adapter 做 full/incremental reconciliation,验证 ordering、duplicates、nulls、count 和 failures,并以 lease/fence 防多副本重叠。
物理 contraction 与普通 Flyway 分开:先写 owner_contraction_proof,再以 backup、quiesce、parity、checksum 和 grant gates 允许 contract migration。当前 repository/disposable rehearsal 已通过(owner-schema-contraction-contract: PASS);真实 production target、traffic drain、backup authority 和 cutover 仍由部署方执行。App 不再读取 Submission-owned SQL。
Notification 是 notifications、preferences、delivery ledger 的唯一 writer。App 只发布 intent 并保留 WebSocket relay;Admin 通过 NotificationReconciliationReadPort 消费 owner facts。物理迁移使用独立 notification schema history;默认 preflight,写入要求 --execute 与一次性确认 token。Rollback 先回写目标新增行,再恢复 App grants。
每次 owner migration 保存不含 secret 的 manifest、rows/checksum、privilege snapshot、lease/report 和失败 artifact。完整五 Owner 备份、加密、恢复演练、retention 和 measured RPO/RTO 见本页“备份与恢复”章节。
../init-db/README.md../scripts/README.md../scripts/runbooks/owner-migration-manifest.sh../scripts/runbooks/owner-schema-contraction.sh../services/docs/CONTRACT_COMPAT_GATE.md
RustFS 是开发、测试、生产共同的必需基础设施,仓库不提供本地磁盘回退。应用在启动时校验
APP_STORAGE_S3_*(endpoint、region、bucket、access key、secret key、TLS 开关),缺失即启动失败;
随后在上下文刷新期间对 bucket 做有界重试探测(APP_STORAGE_STARTUP_PROBE_*,默认 30×2s),仍不可用则本次启动失败,
且此时 HTTP 端口尚未对外服务;两个 owner 的 /health/ready 同时报告 storage 组件(UP/DOWN),
不会在对象存储未经验证时返回就绪。任何情况下都不会静默改写本地目录。
运行期请求若将共享状态标为 DOWN,后续 readiness 检查会异步合并一次有界 bucket 探测;探测成功前仍返回 503,避免健康检查风暴。
- 单一私有 bucket(默认
ulticode,RUSTFS_BUCKET可覆盖),由一次性rustfs-init服务幂等创建;随后rustfs-iam-init用 bootstrap root pair 创建两个运行时用户。没有任何匿名读权限,也没有公开 bucket 策略。 - App 用户只允许
app/avatars/*,Admin 用户允许app/avatars/*与admin/backups/*;这两个运行时 pair 不能互换,root pair 只用于 bucket/IAM bootstrap 和受控迁移。 rustfs-iam-init可在同一持久卷上重复执行:已有用户走rc admin user passwd, 新用户走rc admin user add,随后启用并重新绑定策略;生产 HTTPS 通过RUSTFS_CA_BUNDLE显式传给rc alias set --ca-bundle。- 头像前缀
app/avatars/{accountId}/{uuid}.{ext}:浏览器通过后端只读代理GET /api/users/avatars/{accountId}/{name}读取(允许匿名,与公开页面的头像展示一致; 只允许该前缀、且代理校验 key 语法与账号绑定,对象名为服务端 UUID,bucket 保持私有)。 替换头像时,旧对象的删除意图与 profile 更新在同一 App 事务写入app.storage_cleanup_outbox,由StorageCleanupDispatcher持续退避重试(退避上限 1 小时, 幂等删除因此不会终止);删除前复核该 key 已不是当前头像。 - 备份前缀
admin/backups/{yyyy}/{MM}/{backupId}.sql:只能通过/admin/backups/**的ADMIN/SUPER_ADMIN端点下载与恢复。 - 凭据来自
.env/部署密钥系统:RUSTFS_ACCESS_KEY/RUSTFS_SECRET_KEY是 bootstrap root pair;RUSTFS_APP_*和RUSTFS_ADMIN_*是 prefix-scoped runtime pairs。禁止使用 RustFS 文档中的默认账号;生产不使用明文 HTTP,后端经https://rustfs:9000访问(只有 RustFS 容器挂载含私钥的RUSTFS_TLS_CERT_DIR目录; 其余服务仅挂载rustfs_cert.pem,运行时读不到 TLS 私钥),证书目录由RUSTFS_TLS_CERT_DIR提供且证书/CA 需被后端 JVM 信任。 .env.exampledeliberately leaves all RustFS credentials empty; local development must run./scripts/dev/init-env.sh, and production must provide operator-managed secrets.
- 数据卷
rustfs_data挂载到/data,容器 UID/GID 为10001:10001;容器重启后对象保留。 - 卷级备份(示例,停止写入后执行):
# Resolve the project-scoped volume by its Compose label and fail when the
# result is empty or ambiguous (never guess with `head -1`).
mapfile -t RUSTFS_VOLUMES < <(docker volume ls \
--filter label=com.docker.compose.volume=rustfs_data --format '{{.Name}}')
if [[ ${#RUSTFS_VOLUMES[@]} -ne 1 ]]; then
echo "expected exactly one rustfs_data volume, found ${#RUSTFS_VOLUMES[@]}" >&2
exit 1
fi
docker run --rm -v "${RUSTFS_VOLUMES[0]}:/data:ro" -v "$PWD:/backup" alpine \
tar czf "/backup/rustfs-data-$(date +%Y%m%d_%H%M%S).tgz" -C /data .恢复时把归档解回同一卷(保持 10001:10001 属主),再启动 RustFS 并运行下面的 smoke test 确认对象可读。
- 旧生产 Compose 的 named volume 也必须先纳入迁移:脚本会通过 Docker Compose volume
label(优先匹配
COMPOSE_PROJECT_NAME,无项目名时要求唯一匹配)解析AVATAR_UPLOAD_VOL(默认app_uploads)和BACKUP_VOLUME(默认backup_data); 卷数据目录宿主可读时直接使用挂载点,否则(典型为仅有 Docker 组权限的部署用户)改用 固定镜像经 Docker daemon 探测卷布局,并按对象惰性提取到临时目录,无需 root。 头像卷兼容卷根、uploads/avatars/和avatars/布局,备份卷读取卷根目录。 若 Docker 无法唯一解析 legacy volume(包括默认卷),脚本会 fail closed;也可用--legacy-avatar-dir/--legacy-backup-dir指向已审计的只读提取目录。源卷和旧文件始终不删除。 若历史上自定义过APP_STORAGE_PUBLIC_URL_PREFIX(例如/media),迁移脚本与部署门禁都要用同一个LEGACY_AVATAR_URL_PREFIX(默认/uploads)选行,否则这些行不会被迁移、门禁也不会拦住它们。 - 头像迁移用原始 SQL 更新
user_profiles.avatar,不会自动产生SearchDocumentChangedoutbox 事件。只要本次--apply更新了头像行,脚本就会 输出search_backfill=required并以非零状态结束,不能把迁移报告为完成;backfill 必须用新 artifact执行(旧镜像的 projection 会把对象键原样写进搜索文档):先在部署目录docker compose pull backend-app,再用一次性任务docker compose run --rm -e APP_SEARCH_BACKFILL_ENABLED=true -e APP_SEARCH_BACKFILL_INDEXES=users backend-app, 确认 runner 完成后再用--confirm-users-index-backfill(或MIGRATION_SEARCH_BACKFILL_CONFIRMED=true) 复核迁移结果。 - 旧本地文件迁移用
scripts/dev/migrate-object-storage.sh:默认 dry-run;--apply才上传; 上传后校验大小/checksum 并回读对象,校验通过后才更新数据库行(user_profiles.avatar与backups.object_key);脚本从不删除旧文件,只有在迁移报告确认全部对象已校验后,operator 才可清理旧目录。--apply更新头像行时会在app.storage_migration_state记录“已重写、未确认索引”,直到带--confirm-users-index-backfill的确认运行才写入确认时间;部署门禁读取该记录,因此先放行 未重建用户索引的部署会被拒绝。 - 生产
host-deploy在 ordered owner migrations 之后、pull/up之前执行scripts/runbooks/assert-legacy-objects-migrated.sh:存在仍指向/uploads/avatars/...的app.user_profiles行、没有object_key的admin.backupsCOMPLETED 行,或app.storage_migration_state中已重写但未确认的 头像回填记录时,动作 fail closed 并给出迁移命令;该门禁与迁移脚本 使用同一组行谓词,因此“门禁通过”等于“回填没有剩余目标”。只有显式回滚部署 (cd-rollback.yml传入rollback: 'true',还原的是早于对象存储契约的产物)跳过该门禁; 跳过 Flyway 的普通部署(skip_migrations=true)仍会执行该门禁,缺少 migration 数据库凭据时 fail closed。 - 生产 Compose 为回滚保留旧镜像的本地存储契约:
backend-app继续挂载${AVATAR_UPLOAD_VOL:-app_uploads}:/data/uploads/avatars并保留APP_STORAGE_LOCAL_ROOT_DIR,backend-admin继续挂载backup_data:/var/lib/ulticode/backup并保留BACKUP_DIR(默认值即挂载点)。当前镜像只用对象存储、忽略这些路径;早于对象存储契约的 镜像仍读本地目录,缺少这些挂载与变量会让回滚“健康”但历史文件不可见、新写入落进临时层。 两个 named volume 在docker/docker-compose.prod.yml顶层声明,重建后仍指向原卷。 - 生产
host-deploy在 ordered owner migrations 之前验证完整的 deploy service allowlist,并要求 migration subset 同时包含backend-admin与backend-app(对象键写入与头像读取路径随本次发布一起切换);随后记录原有backend-admin容器 ID,使用已验证 image refs 执行docker compose stop。 停止前先等待scripts/runbooks/assert-admin-backup-drained.sh通过;旧镜像可能没有 drain-aware executor,直接 stop 会杀掉在跑的mysqldump,因此超过ADMIN_BACKUP_DRAIN_TIMEOUT_SECONDS(默认 3660s)仍不通过就 fail closed。当前镜像的adminBackupExecutor拒绝 context close 后的新任务,并等待在途 dump/upload/state-transition 工作;ADMIN_BACKUP_STOP_GRACE_PERIOD必须覆盖该排空窗口。 本次发布还会在迁移/门禁之前一并停止backend-app:旧 App 到新镜像替换前仍会写/uploads/avatars/...行,门禁通过后再提交的行会指向不存在的对象。失败恢复会按记录 逐个docker start还原此前确实在运行的backend-admin与backend-app容器。 手工运行 migration 也必须先停止并排空所有旧 writer。 - 排水或迁移前置检查失败时,动作只恢复此前确实运行的原容器;owner migration
一旦开始,动作保持
backend-admin停止并 fail closed,不把旧 image 重新启动到 可能已部分迁移的 schema 上。应先检查 migration report,再按兼容 artifact 手工恢复。 - 特权
post-ownermigrationV20260921120000__Copy_Legacy_Backups_To_Admin.sql在对象回填前幂等地把旧ulticode.backups元数据复制到admin.backups; 随后的scripts/runbooks/reconcile-legacy-backups.sh复制一次性 Flyway copy 之后出现且未被admin.backup_deletion_tombstones标记的 source rows;删除备份 时先在 Admin 事务内持久化 tombstone(并记录该行的object_key),避免后续 reconciliation 复活已删除目标;提交后立即尝试删除对象,失败或失败上传对象会由BackupObjectLifecycle的定时 sweep 在 settle window 后依据 tombstone 有界重试(对象删除幂等, 成功后写object_deleted_at)。 Runbook 仍拒绝 metadata conflict 和 pre-cutover target-only rows,并在 parity 通过后写入admin.backup_cutover_state。Owner-scoped 的 Admin migration 只负责 创建/修复目标表;迁移不会删除旧行或旧文件。 - For an internal production endpoint such as
https://rustfs:9000, the migration script's Docker AWS CLI joins${COMPOSE_PROJECT_NAME:-ulticode}_object-storage; setMIGRATION_DOCKER_NETWORKwhen the Compose project uses a different network. Host AWS CLI mode is intentionally limited to loopback endpoints. - 部署顺序要求:升级到本版本后,尚未迁移的旧头像与旧备份在对象上传前不可用(下载/恢复返回明确的
NOT_FOUND,不会回退本地文件)。请在切换后立即执行迁移(先 dry-run 核对计划,再
--apply),并核对MIGRATION_SUMMARY全部通过后再对外确认头像与备份功能;迁移脚本幂等,可重复执行。host-deploy已把该回填作为 pull/up之前的门禁,因此首次升级应在部署中断后按提示完成迁移再重跑部署, 而不是先放行新 App。 - RustFS 不可用时:应用启动失败(或既有实例在请求路径上返回明确的存储错误),备份/恢复不会标记成功, 也不会回退到本地永久目录。
- Admin 上传头像的对象键与清理意图:App 拒绝或不可达时对象会被删除,删除失败则记入
admin.storage_cleanup_outbox并由AdminStorageCleanup定时 sweep 幂等重试,避免 App 不可用期间 每次重试都泄漏一个对象;RPC 结果未知(post-dispatch 超时)的对象以verify_owner_reference记录, sweep 先向 App 查询当前头像,仍被引用则保留(kept_at),确认未被引用才删除,App 不可达时继续挂起; 该判定只在意图超过admin.storage-cleanup.owner-check-grace-seconds(默认 900s,长于 RPC 超时与 数据库锁等待)后执行,避免 Dubbo 超时但 provider 事务随后才提交时的误删。 - 头像清理意图不会因存储长时间不可用而终止:
StorageCleanupDispatcher只增退避(上限 1 小时)地重试, 并把历史DEAD行重新排入队列。
../scripts/dev/migrate-object-storage.sh../scripts/dev/rustfs-smoke-test.sh../docker/docker-compose.yml(rustfs/rustfs-init/rustfs-iam-init服务)
应用层 Admin backup 对象的生命周期由 BackupObjectLifecycle 统一负责,不由
BackupServiceImpl、异步执行器和清理任务分别拥有。BackupServiceImpl 只编排创建请求
(插入 PENDING)、下载/恢复、投影和删除请求校验;删除状态与对象清理委托生命周期模块。
生命周期模块通过 named adminBackupExecutor 执行 PENDING → IN_PROGRESS → COMPLETED | FAILED,在对象 PUT 前持久化 planned key,
并在临时 dump 文件的所有路径无条件清理。executor rejection 也会记录受保护的 FAILED
状态;不能覆盖已经持久化的 COMPLETED。
删除由生命周期模块重新读取权威 backup row:运行中的、已有 planned key 的 backup 拒绝删除;
row delete 与 admin.backup_deletion_tombstones 写入同一 Admin 事务,对象删除只在提交后
发生。非 FAILED 终态可走 after-commit fast path;失败上传对象先写 tombstone,等
settle window 后由 adminBackupScheduler 触发有界 sweep(),幂等删除并记录成功或失败,
直到 tombstone 完成。全 Owner 数据库归档与 restore drill 仍由下述外部 Ops runbook 负责。
完整 Owner 备份由外部 Ops runbook 承接,不扩展 Admin HTTP backup API 为跨 Owner 业务接口。归档范围是 ulticode control schema 与五个 Owner schema:auth、admin、app、notification、submission。
scripts/runbooks/owner-backup-restore.sh 生成 OpenSSL 加密归档、secret-free manifest、dump SHA-256、表 rows/checksum 和 Flyway history metadata;restore-drill 只恢复到一次性 MySQL 目标,运行 migration validate、checksum reconciliation、schema/query smoke,并记录 measured RPO/RTO。密钥由 operator 提供(至少 32 字节),不进入 Git 或日志。
backup、restore-drill、prune 使用同一 fenced database lease(admin:owner-backup)并保留同机 flock 快速门禁。admin.fenced_job_leases 是临时控制状态,不计入业务 checksum 或恢复状态。Retention 只能删除匹配的归档/manifest 对。
恢复顺序:
- 确认目标是 disposable/授权环境,保存 source commit、schema checksum、manifest 和密钥引用。
- 解密并校验归档完整性、manifest、dump hash、Owner/schema/table 清单。
- 按 Owner migration history 做 validate,恢复 control/Owner 数据并做 row/checksum reconciliation。
- 运行查询、服务 readiness、队列/Inbox 和关键 API smoke;确认不会把派生 Search 索引当作业务备份。
- 若恢复 MeiliSearch,清理
search:doc-version:{index}后按 Search backfill 重建,并观察索引计数与版本单调性。
错误密钥、缺文件、checksum mismatch、schema mismatch、lease busy、恢复目标不安全或 smoke 失败必须非零退出。生产部署 rollback 使用已验证 descriptor 和 schema-compatible artifact,不通过备份脚本做 schema downgrade。真实 off-host 存储、密钥托管、保留策略和生产 restore authority 仍是外部门禁。
../scripts/runbooks/owner-backup-restore.sh../services/docs/FENCED_LEASE_RUNBOOK.md数据库迁移与 Owner 收敛部署、发布与回滚
可选的 docker/docker-compose.observability.yml 提供 loopback-only、digest-pinned 的 OpenTelemetry Collector、Prometheus、Alertmanager、Grafana、Tempo 和 Loki。HTTP Owner 暴露 metrics;无 HTTP 的 Judge/Search worker 通过 Micrometer OTLP 输出。日志携带 trace/span 关联,Grafana 可从 Loki 跳转 Tempo。
Boot 4 tracing binds OTLP export settings under management.opentelemetry.tracing.export.otlp; the existing MANAGEMENT_OTLP_TRACING_ENDPOINT and MANAGEMENT_OTLP_AUTHORIZATION environment inputs remain unchanged. The shared observability module enables the tracing integration and validates the effective endpoint/header pair before export: a configured authorization header requires HTTPS. Every service, including Core, binds OTLP metrics under management.otlp.metrics.export and defaults MANAGEMENT_OTLP_METRICS_ENABLED to false, preserving the opt-in Collector overlay. App and Core also select Jackson 2 independently for WebSocket messaging with spring.websocket.messaging.preferred-json-mapper=jackson2, keeping local and Redis-broadcast JSON contracts aligned.
生产 telemetry receiver、存储、保留周期、通知 webhook、阈值调优和真实流量 SLO 由外部运维平台负责;仓库 overlay 默认不启动,也不公开 management endpoint 或 secret。
- HTTP/RPC:延迟、错误、timeout、circuit-open、bulkhead saturation。
- Outbox/Inbox:oldest age、retry、dead、duplicate、lease expired。
- Worker:queue lag、PEL size、PEL oldest age、DLQ size、last success、consume failures。
- Reconciliation/backup:run、skip、failure、checksum/parity 和 lease 状态。
- Scheduler:active、queued、pool size、completed、rejected;pool 仅允许 1–16。
- JVM/resource:CPU、memory、thread、GC、connection pool 和 readiness。
services/docs/WORKER_SLO_RUNBOOK.md 是 Worker 指标、告警、DLQ、PEL 和恢复命令的唯一详细入口;初始阈值必须用真实 p50/p95 基线重新调优。
- 先确认服务/worker readiness、依赖健康和最近 release descriptor。
- 再检查 lag、PEL、DLQ、oldest age、last success 和错误日志的时间窗口。
- 修复下游依赖、ACL、schema 或 provider,再等待现有 reclaim/retry;不要先删 stream、PEL、ledger 或 version hash。
- 需要 replay、discard、ledger reset 或 stop-write 时,按 Worker SLO runbook 的门禁执行并保留证据。
- 记录 trace ID、commit、digest、schema checksum、affected service 和恢复结果;不要记录 token/password/private key。
./scripts/test/observability-contract.sh可选 overlay 的 Compose、Prometheus、Alertmanager、Collector、dashboard 和 release annotation 合约由 scripts/test/observability-contract.sh 维护;不要把本地 overlay 通过文档描述为生产 SLO 达标。
本仓库没有生产环境;本文定义代码、配置和 disposable rehearsal 的响应入口,不替代部署方的 on-call、变更授权、外部 secret store 或生产恢复流程。Services 架构问题、状态和触发条件唯一登记在 services/docs/SERVICES_ISSUES.md。
- 记录 UTC 时间、service/worker、commit/digest、schema checksum、trace ID 和可复现命令;脱敏日志,不记录凭据。
- 先确定是代码错误、配置漂移、依赖不可用、资源饱和、数据不一致还是外部权限缺失。
- 对写路径停止扩散:保留 Outbox/Inbox/PEL/lease,避免 flush、手工重复写或绕过 owner。
- 修复根因后按对应 runbook replay/reclaim/retry;验证幂等、版本 fence、checksum 和健康结果。
- 需要生产 mutation、credential rotation、traffic drain、cutover、failover、remote Judge 或 TLS 时,转交部署 authority。
| 症状 | 首查 | 恢复入口 |
|---|---|---|
| 登录/refresh/权限失败 | Cookie/CSRF、JWKS、Auth readiness、authz version | 安全架构 |
| Admin 返回 owner unavailable | RPC timeout/circuit/bulkhead、owner readiness、degradation status | 依赖韧性 |
| Submission pending 或 stale verdict | owner receipt、generation/attempt、judge PEL/DLQ | Worker SLO |
| Notification 延迟/重复 | Inbox、delivery ledger、lease、SMTP/Redis relay | Notification runbook |
| Search stale/缺失 | event version、tombstone ledger、PEL、Meili health | Worker SLO |
| scheduler starvation | named executor active/queued/rejected | Scheduler runbook |
| deployment/rollback 不一致 | source/schema/digest descriptor、Compose config | 部署、发布与回滚 |
| migration parity 失败 | checkpoint、TSV conflict、grant、checksum | 数据库迁移与 Owner 收敛 |
SERVICES_ISSUES.md 使用 OPEN(仓库仍可修)、DEFERRED(需真实指标/环境/授权)、CLOSED(机制已落地)和 ACCEPTED(有意取舍)。repository-actionable OPEN 的当前列表与关闭条件以 services/docs/SERVICES_ISSUES.md 为准;SVC-006–010 的外部触发条件仍需真实环境。不要把删除临时记录、disposable 通过或文档更新宣称为生产问题已解决。