私有化部署运维手册 (K8s 离线交付)
飞书 Wiki 同步子文档,revision 3。
私有化部署运维手册 (K8s 离线交付)
定位:本手册是 NanoCloud 私有化离线交付的一站式部署文档。
1. 交付模型与阅读路径
1.1 适用范围
| 项 | 说明 |
|---|---|
| 受众 | 客户现场 / 驻场运维(中文) |
| 交付形态 | 离线 tar 包(nanocloud--.tar.gz),安装全程不依赖公网 |
| 主路径 | 单机离线(airgap):profile=standalone + install.sh --airgap |
| 底座 | 包内 k3s 由 foundation/base-install.sh 安装(vendored install.sh + airgap 镜像);禁止现场裸跑 curl … get.k3s.io |
| 镜像分发 | registry-first:seed registry:2.8.3 → 集群内 registry(NodePort 30500)→ push → 节点 mirror → Helm |
| 不涵盖 | Compose 轨(仓库根 deployment/),与本手册无关 |
1.2 部署形态(profile)对照
勿跨 profile 混用步骤。
| profile | 场景 | 存储 | 入口章节 |
|---|---|---|---|
standalone | 单机无集群(主路径) | local-path | §6 |
k3s-ha | 1 server + 多 agent(工作负载多节点) | Longhorn | §7 |
k8s-bare | 已有自建集群(RKE2/kubeadm 等) | 集群默认 SC | §8 |
eks / ack / tke | 云托管 | 云盘 SC | §8 + 对应 platform/*.yaml |
勿跨 profile 混用步骤。 当前 k3s-ha 只自动加入 agent,不追加 etcd/server;它提供工作负载和
Longhorn 副本冗余,不是控制面 HA。控制面故障仍会导致 Kubernetes API 单点不可用。
1.3 角色分工
| 角色 | 谁 | 做什么 |
|---|---|---|
| 构建机 | 交付方 / 有外网环境 | package.sh 打离线包;需 Docker/buildx、可拉基础镜像 |
| 目标机 | 客户现场(通常无外网) | 解包 → 写 Site Contract → install.sh --airgap → 验收 |
| 管理入口 | 目标机或可访问集群的跳板 | 包内 bin/kubectl / bin/helm;k3s 默认 KUBECONFIG=/etc/rancher/k3s/k3s.yaml |
2. 交付物清单与校验
2.1 获取交付包
交付物由 CI release:package(或 scripts/package.sh)产出并上传 OSS(oss://zbl-nanocloud/releases//)。
物料必须在目标服务器直接下载并校验,禁止先下载到个人电脑再中转到客户环境。
现场下载使用只读 RAM 账号(凭据见交付邮件 / offline-deploy §0.1;勿用于 CI 上传)。
nanocloud--amd64.tar.gz # 必下
nanocloud--amd64.tar.gz.sha256 # 必下
release.lock.yaml # 亦含在包内 release.lock
index.json # 可选
目标服务器示例:
cd /tmp
ossutil ls "oss://zbl-nanocloud/releases//"
ossutil cp "oss://zbl-nanocloud/releases//nanocloud--amd64.tar.gz" .
ossutil cp "oss://zbl-nanocloud/releases//nanocloud--amd64.tar.gz.sha256" .
sha256sum -c nanocloud--amd64.tar.gz.sha256
2.2 校验与解包
# :为安装版本号 比如:0.4.15
# 1) 完整性校验(必须通过)
sha256sum -c nanocloud--amd64.tar.gz.sha256
# 2) 解包
mkdir -p /opt/nanocloud
tar -xzf nanocloud--amd64.tar.gz -C /opt/nanocloud
cd /opt/nanocloud/nanocloud--amd64
# 3) 包内 helm/crane 加入 PATH(sudo 不继承用户 PATH)
export PATH="$PWD/bin:$PATH"
若解包后出现大量 ._*(macOS AppleDouble):find /opt/nanocloud -name '._*' -delete。
2.3 包内容核对
chart/ platform/ foundation/ images/ scripts/ db/ release.lock
bin/helm bin/kubectl bin/crane # 完整 release 包
scripts/restore.sh # 人工审批的灾难恢复入口
foundation/k3s/{k3s, k3s-airgap-images-*.tar.gz, install.sh} # vendored;现场勿 curl get.k3s.io
foundation/storage/charts/longhorn-*.tgz # k3s-ha addon
foundation/cert/cert-manager.yaml # addon
foundation/addons.images.list # addon 镜像清单
images/{*.tar, MANIFEST.txt, SHA256SUMS} # 除四个现场手工物料外的应用+基础镜像
**0.4.33 的 MySQL 相关法务边界:**完整离线包不携带下列四个镜像归档,仅通过 images/MANUAL_IMAGES.txt 声明文件名和镜像契约。交付方已将对应归档发布到 oss://zbl-nanocloud/releases/0.4.33/manual-images/;部署前必须直接下载到目标机 /srv/nanocloud/mysql-images,校验后通过 --manual-images-dir 交给安装器。
| 禁止随包携带的归档 | 包内数量要求 | 现场来源 |
|---|---|---|
nanocloud_db-migrator_1.2.0.tar | 0 | 版本 OSS manual-images/ → /srv/nanocloud/mysql-images |
nanocloud_backup-tools_1.0.0.tar | 0 | 版本 OSS manual-images/ → /srv/nanocloud/mysql-images |
mysql_8.0.tar | 0 | 版本 OSS manual-images/ → /srv/nanocloud/mysql-images |
container-registry.oracle.com_mysql_community-router_8.4.6.tar | 0 | 版本 OSS manual-images/ → /srv/nanocloud/mysql-images |
0.4.33 下载与校验:
mkdir -p /srv/nanocloud/mysql-images
ossutil cp -r oss://zbl-nanocloud/releases/0.4.33/manual-images/ /srv/nanocloud/mysql-images/
cd /srv/nanocloud/mysql-images
sha256sum -c SHA256SUMS
/opt/nanocloud/nanocloud-0.4.33-amd64/bin/crane validate --tarball nanocloud_db-migrator_1.2.0.tar
/opt/nanocloud/nanocloud-0.4.33-amd64/bin/crane validate --tarball nanocloud_backup-tools_1.0.0.tar
/opt/nanocloud/nanocloud-0.4.33-amd64/bin/crane validate --tarball mysql_8.0.tar
/opt/nanocloud/nanocloud-0.4.33-amd64/bin/crane validate --tarball container-registry.oracle.com_mysql_community-router_8.4.6.tar
目录必须恰好包含四个声明归档;缺文件、额外错版文件、archive 校验失败或现场 Registry digest 无法读取时均应终止安装。
红线检查:
release.lock禁止空 digest(客户包勿设ALLOW_EMPTY_DIGESTS=1);空 digest = 拒绝安装,向发版方索包。- 交付包内不应残留
chart/values.local.yaml(本地联调文件)。若有会被 install.sh 自动叠加,导致镜像变:latest、组件被禁用——有则改名或删除。 - (k3s-ha)
addons.images.list/images/MANIFEST*必须含 CSI sidecar 与 engine 族(csi-attachercsi-provisionercsi-resizercsi-snapshottercsi-node-driver-registrarlivenessprobelonghorn-enginelonghorn-instance-manager等 ≥13 条longhornio/*),仅grep longhorn通过不算数——缺镜像会导致 Longhorn CSIImagePullBackOff(补齐方法见 §14.5)。
3. 环境要求与预检
3.1 目标机要求
| 项 | 要求 |
|---|---|
| OS | Ubuntu 22.04 / 24.04 或同类(systemd);内核 ≥ 4.18 |
| 架构 | 与包名一致(amd64 或 arm64),节点同构 |
| 资源 | 全栈参考 ≥ 8C / 16G;磁盘建议 ≥ 200G SSD(lab 小盘须缩小 PVC,见 §14.6) |
| Kubernetes | ≥ 1.24(自带集群时;contract-check 硬门) |
| 时钟 | NTP 同步(JWT/TLS 敏感) |
| swap | 建议关闭(os-preflight 检查) |
| 端口 | 6443(apiserver)、10250、flannel;业务 NodePort:80/443(HTTP/HTTPS)、1883/1884(MQTT)、30500(registry) |
| 权限 | root / sudo |
| 工具 | 包内自带 bin/helm、bin/kubectl、bin/crane;不依赖现场在线安装这些工具 |
3.2 预检
解包后、安装前可单独执行(install.sh 也会自动跑):
sudo bash foundation/os-preflight.sh --profile standalone # 或 k3s-ha
4. 出包(构建机)
仅交付方操作;现场可跳过本节。
在仓库 k8s/ 目录(或带 k8s/ 前缀自仓库根)执行,默认打 full 包(含 k3s airgap 段):
# 全量:构建应用/工具镜像 + 拉取基础镜像 + 出包
bash k8s/scripts/package.sh --arch amd64
# 复用本机已有镜像(不重复 build)
bash k8s/scripts/package.sh --arch amd64 --from-local
| 选项 | 含义 |
|---|---|
--arch amd64|arm64|native | 目标架构(默认 amd64) |
--version | 须与 chart/Chart.yaml 的 version / appVersion 一致 |
--kind full|foundation|platform|app | 默认 full |
--from-local | 使用本地已有镜像 |
--images-dir | 使用预置 docker-archive + MANIFEST |
--no-k3s | 不含 k3s(客户 standalone 包不要用) |
--keep-images | 打包后保留临时 images 目录 |
产物:k8s/dist/nanocloud--.tar.gz(及 .sha256)。macOS 打包设 COPYFILE_DISABLE=1,避免 ._* AppleDouble 进包。
5. 现场配置:Site Contract
cp platform/standalone.yaml /tmp/site.yaml
vi /tmp/site.yaml
5.1 必填与关键项
| 键 | 要求 |
|---|---|
network.publicAddress | 必填真实域名或 IP;占位符(example.com / CHANGE_ME)会被 preflight / chart schema 拒绝 |
images.registry | --airgap 时由安装器注入 :30500;多机建议固定为首节点 IP 或 VIP;values 各镜像引用不要重复写 registry 前缀(helper 会拼成双前缀 404) |
| 密钥 / 口令 | 可留空:首装由 Helm 生成并写入 Secret nanocloud-credentials;生产建议在 gitignored 现场文件中显式设置 |
infra.mqtt.auth.enabled | 保持 true;禁止为绕过 robot-service 就绪而关 auth |
backup.* | 生产必须开启并配置离机备份目标(见 §11);lab 可关 |
persistence.* | 磁盘不足时在首次安装前缩小(示例见 §14.6);registry PVC 不在 chart values 内 |
站点示例骨架见包内 chart/values.site.example.yaml。
**Longhorn 分层存储。**仅 k3s-ha 首装可配置 foundation.longhorn.tiering。启用时必须同时设置 platform.storageClass=longhorn-ssd 和 persistence.rustfs.storageClass=longhorn-hdd;完整配置、宿主机挂载责任边界和验收命令见 §20。
5.2 values 加载顺序(install.sh)
chart/values.yaml → platform/.yaml → chart/values.local.yaml(若存在)
→ --values 文件 →(airgap)注入 images.registry + release.lock digests
lock 注入在
--values之后,故 overlay 里写images.digests.*无法覆盖 lock;lab 需覆盖时用--allow-digest-override --digest-override。
6. 安装 A:单机 standalone(主路径)
6.1 一键安装(推荐)
cd /opt/nanocloud/nanocloud--amd64
export PATH="$PWD/bin:$PATH"
sudo env PATH="$PATH" bash scripts/install.sh \
--profile standalone \
--airgap \
--values /tmp/site.yaml \
--manual-images-dir /srv/nanocloud/mysql-images
k3s 由脚本安装(foundation/base-install.sh --airgap,使用包内二进制 + airgap 镜像,INSTALL_K3S_SKIP_DOWNLOAD=true)。
6.2 安装器内部顺序(standalone)
- 叠加 values / Site Contract preflight(弱口令、备份契约等)。
- 执行
foundation/os-preflight.sh。 - 用包内二进制和 K3s airgap 镜像安装 K3s;等待 API、Node Ready、默认 StorageClass 可用。
- 从离线包导入
registry:2.8.3,部署站点 registry(ns=nanocloud-registry,NodePort 30500)。 - 等 registry Ready,执行
push-images.sh推送全部应用、基础组件和 addon 镜像。 - 执行
configure-registry-mirror.sh,写入 registry 配置并重启 K3s。 - 在镜像推送和 registry 配置完成后安装 cert-manager;启用 kube-vip 时同样在此阶段安装。
- 注入动态解析的
images.registry与release.lockdigests,安装 Gateway API CRD 并检查升级窗。 - 执行
helm upgrade --install(PVC 走 local-path),等待 post-install 迁移与授权 Job 收敛。 - 执行 migrate/grants 与离线镜像来源硬门禁;由部署人员完成 §9 人工验收(无
helm test)。
0.4.11 原始包曾在第 5 步之前安装 cert-manager,导致其访问
quay.io并超时。任何 addon
都不得早于站点 registry 填充完成启动。
standalone 不装 Longhorn、不跑 agent join。
6.3 干跑(可选)
sudo env PATH="$PATH" bash scripts/install.sh \
--profile standalone --airgap --values /tmp/site.yaml --dry-run
6.4 常用安装选项
--manual-images-dir :指定 MANUAL_IMAGES.txt 声明的四个 MySQL 相关归档目录;0.4.33 现场统一使用 /srv/nanocloud/mysql-images,单节点和多节点均必须传入。
| 选项 / 环境变量 | 含义 |
|---|---|
--values | 现场 overlay(可重复) |
--namespace / --release | 默认均为 nanocloud |
--dry-run | 只渲染 / 预检,不落资源 |
--force-reinstall | 卸载失败/残留 release 后重装;无 release 时清 orphan 账本 CM |
--recover-failed | 将 failed/pending release 回滚到上一 good revision |
--skip-k3s | 已有集群时跳过 k3s bootstrap |
--skip-contract-check | 专家:跳过集群契约硬门(打印 WARN) |
--allow-digest-override + --digest-override | 仅 lab:lock 注入后覆盖 digests |
NANOCLOUD_DEV=1 | lab 逃逸:跳过多项预检(非生产支持标志) |
REGISTRY_ENDPOINT | 覆盖站点 registry(默认 :30500) |
LEGACY_IMPORT_IMAGES=1 | 遗留:逐节点全量 ctr import(不推荐) |
--allow-insecure-defaults已移除;本地 lab 用NANOCLOUD_DEV=1。
7. 安装 B:多节点 K3s(默认 1 控制面 + 2 工作节点,HA 按需开启)
与 standalone 的关键差异:业务 Helm 只在三台计划节点 Ready 且 Longhorn 就绪后执行。profile 名继续为 k3s-ha,但默认 controlPlaneHA=false:首节点是唯一 control-plane/embedded-etcd server,另外两台是 worker agent。需要时才显式开启 HA,把同两台节点从 agentNodes 移到 serverNodes;旧 joinNodes 仅保留为 agent 兼容别名。
存储模型:默认兼容模式仍使用 platform.storageClass: longhorn。新建私有化站点可按 §20 启用 SSD/HDD 分层:除 RustFS 外的嵌入式基础组件使用默认类 longhorn-ssd,RustFS 显式使用 longhorn-hdd。nanocloud-registry全程使用 local-path,不迁入 Longhorn;已有未分层集群不得通过修改 values 原地切换 PVC 的 StorageClass。
7.1 前置:SSH 免密(人工完成)
自动加入支持 server 与 agent。安装器只消费已有 BatchMode SSH,不配置密钥——首节点上须已通过 ssh root@ true。已有 agent 被列入 serverNodes 时,脚本会先 cordon/drain,再以完全一致的关键网络参数转为 embedded-etcd server,Ready 后自动 uncordon。
7.2 Site Contract
foundation:
k3s:
serverIP: ""
# 默认不覆盖 clusterCIDR / serviceCIDR,使用 K3s 内建 CIDR
controlPlaneHA: false
agentNodes:
- ""
- ""
# sshUser: root
# packageDir: /opt/nanocloud/nanocloud--amd64
# 按需开启三控制面 HA:
# controlPlaneHA: true
# serverNodes: ["", ""]
# agentNodes: []
环境变量等价项:默认模式使用 K3S_AGENT_NODES=,;开启 HA 时使用 K3S_CONTROL_PLANE_HA=1 与 K3S_SERVER_NODES=,,并清空 agent 列表。K3S_SERVER_IP= 指定首节点,K3S_JOIN_NODES 仍是 agent 别名。产品默认不传 Pod/Service CIDR 参数,由 K3s 使用内建 CIDR;只有客户 Site Contract 明确要求时才覆盖,且两者不得相同或重叠。
| 旋钮 | 含义 |
|---|---|
controlPlaneHA / K3S_CONTROL_PLANE_HA | 默认 false;只有显式为 true 才允许追加 embedded-etcd server |
serverNodes / K3S_SERVER_NODES | HA 模式必须恰好两个;默认模式禁止配置 |
agentNodes / K3S_AGENT_NODES | 默认模式必须恰好两个;HA 模式必须为空;旧 joinNodes 仅为别名 |
SKIP_K3S_JOIN=1 | 跳过自动加入,但 Site Contract 仍必须描述三节点,安装器仍检查 Ready 与角色 |
FOUNDATION_VIP= | 启用 kube-vip,并自动把 VIP 加入所有 server 的 API TLS SAN |
SKIP_LONGHORN=1 | 跳过 Longhorn;仅限明确不需要分布式业务存储的场景 |
7.3 首节点一键
cd /opt/nanocloud/nanocloud--amd64
export PATH="$PWD/bin:$PATH"
sudo env PATH="$PATH" bash scripts/install.sh \
--profile k3s-ha \
--airgap \
--values /tmp/site-k3s-ha.yaml \
--manual-images-dir /srv/nanocloud/mysql-images
执行顺序:首个 embedded-etcd server 以 K3s 默认 CIDR(客户显式覆盖时才传 CIDR)启动 → seed/启动现场 registry → push 全部镜像并配置首节点 mirror → 安装 cert-manager 与可选 kube-vip → 默认加入两个 agentNodes;仅在 controlPlaneHA=true 时加入两个 serverNodes → 等三节点 Ready 并校验默认 1 个或 HA 3 个 etcd 角色 → base-install --longhorn-only → Gateway CRD → Helm → migrate/grants 与镜像来源硬门禁 → 人工验收。
7.4 手工加入 server / agent(自动加入关闭时)
# 首节点记录 token
sudo cat /var/lib/rancher/k3s/server/node-token
# 加入节点:放二进制与 airgap 镜像
sudo mkdir -p /usr/local/bin /var/lib/rancher/k3s/agent/images
sudo cp foundation/k3s/k3s /usr/local/bin/k3s && sudo chmod +x /usr/local/bin/k3s
sudo cp foundation/k3s/k3s-airgap-images-* /var/lib/rancher/k3s/agent/images/
# 额外 embedded-etcd server:关键参数必须与首节点完全一致
sudo INSTALL_K3S_SKIP_DOWNLOAD=true sh foundation/k3s/install.sh server \
--server https://:6443 --token --disable=traefik \
--cluster-cidr 10.10.6.0/24 --service-cidr 10.10.7.0/24 \
--kube-controller-manager-arg node-cidr-mask-size-ipv4=26 \
--kubelet-arg max-pods=60 --tls-san
# 纯 agent
sudo INSTALL_K3S_SKIP_DOWNLOAD=true K3S_URL=https://:6443 \
K3S_TOKEN= sh foundation/k3s/install.sh --kubelet-arg max-pods=60
# 每个节点都必须写 mirror
sudo bash scripts/configure-registry-mirror.sh --registry :30500
7.5 HA / CIDR 验收
验收不能只看 Node 角色,必须覆盖控制面读写故障注入。先核对 3 个 server 的 PodCIDR 分别属于目标网段、Service ClusterIP 属于独立 Service CIDR,再停止一个 server 的 K3s,在另一 server 使用本地 kubeconfig 轮询 /readyz,确认 etcd ok 后创建临时 ConfigMap;最后恢复被停节点并确认 3 个 etcd server 全部 Ready。
kubectl get nodes -o custom-columns=NAME:.metadata.name,ETCD:.metadata.labels.node-role\.kubernetes\.io/etcd,PODCIDR:.spec.podCIDR,MAXPODS:.status.capacity.pods
kubectl get svc -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,CLUSTER-IP:.spec.clusterIP
kubectl get --raw=/readyz?verbose | grep 'etcd ok'
**故障注入保护:**测试脚本必须用 trap 或等价机制保证失败也会恢复被停服务;未完成 etcd 快照和资源导出前不得在已有站点修改集群 CIDR。
铁律:mirror 与
disable-default-registry-endpoint只是纵深防御,不是严格离线的正确性基础。
2026-08-04 实测 K3s v1.30.2 在站点 registry 返回 404 后仍可能请求registry-1.docker.io。
NanoCloud、cert-manager、Longhorn、kube-vip 等受管镜像必须直接渲染为
${SITE_REGISTRY}/...@sha256:...;发现受管 Pod(含 initContainer)不是站点 registry 前缀,安装必须失败。
8. 安装 C:既有集群与云托管
8.1 k8s-bare(已有自建集群)
cp platform/k8s-bare.yaml /tmp/site.yaml
vi /tmp/site.yaml # publicAddress + images.registry(外部 registry 显式填写)
# 端口冲突检查(有输出=冲突,改 overlay 中 gateway.traefik/mqtt nodePort)
kubectl get svc -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" "}{.spec.ports[*].nodePort}{"\n"}{end}' | grep -E '30080|30443|1883|1883'
sudo env PATH="$PATH" bash scripts/install.sh \
--profile k8s-bare --airgap --values /tmp/site.yaml
# 在线场景(可拉镜像)则去掉 --airgap,走 contract-check 硬门
要求:集群 ≥1.24、一个默认 StorageClass(WaitForFirstConsumer 可用)、kubeconfig 就绪。
注意 GatewayClass 是集群级单例——与既有 release 冲突时在 overlay 改 gateway.className(如 traefik-)独占一个 class。
8.2 云托管(eks / ack / tke)
infra 走云产品:对应 platform/.yaml overlay 关内嵌、填外部端点(RDS/OSS/MQTT 等),其余流程同 k8s-bare。细节以各 overlay 注释为准。
9. 安装后验收
Chart 已移除helm test / nanocloud-verify。安装器会做 migrate/grants 门禁;现场验收由部署同学按下列步骤手工完成。
9.1 总览
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
export PATH="/opt/nanocloud/nanocloud--amd64/bin:$PATH"
bash scripts/status.sh -n nanocloud
kubectl get pods -n nanocloud
kubectl get pods -n nanocloud-registry
helm status nanocloud -n nanocloud
9.2 数据库迁移链(六个 platform-chain 库)
当前平台迁移链包含 MySQL 三库 db_x2robot_cloud / db_x2robot_analytics / db_x2robot_v2,
以及 PostgreSQL 三库 xrai-core / db_label_service / data_pipeline。首装走 post-install baseline,
升级走 pre-upgrade 增量 SQL;六库的 Goose applied 版本均须达到交付包声明的链头(0.4.12 基线为 10000)。
Airflow 与 Prefect 是 component-self,只由 pg-ensure 建库并由组件自身管理 schema,不属于平台 Goose 链。
NS=nanocloud
kubectl exec -n $NS mysql-0 -- sh -c \
'MYSQL_PWD=$MYSQL_ROOT_PASSWORD mysql -uroot --ssl-mode=DISABLED -e "SHOW DATABASES"'
# 应看到上述 MySQL 三库;PostgreSQL 还应存在 xrai-core、db_label_service、data_pipeline、airflow、prefect
kubectl exec -n $NS mysql-0 -- sh -c \
'MYSQL_PWD=$MYSQL_ROOT_PASSWORD mysql -uroot --ssl-mode=DISABLED -N -e \
"SELECT version_id,is_applied FROM db_x2robot_cloud.goose_db_version ORDER BY id DESC LIMIT 3"'
# goose_db_version 无 comment 列;勿查 comment(ERROR 1054)
同时逐库检查 PostgreSQL 的 goose_db_version,并确认 data_pipeline.meta.collection_report_refresh_states
存在。collection-report 只检查端口或数据库存在仍可能在迁移窗口失败,必须等待这张精确业务表。
9.3 analysis-grants(analysis 开启时)
hook Job -analysis-grants 在 migrate 后为业务账号授权(ALL on analytics + SELECT on v2),带 schema gate(库不存在直接失败):
kubectl exec -n nanocloud mysql-0 -- sh -c \
'MYSQL_PWD=$MYSQL_ROOT_PASSWORD mysql -uroot --ssl-mode=DISABLED -e \
"SHOW GRANTS FOR '"'"''"'"'@'"'"'%'"'"'"'
9.4 入口与镜像前缀
# HTTP 入口(Traefik NodePort;Host = platform.|admin. + publicAddress)
curl -sS -o /dev/null -w '%{http_code}\n' -H "Host: platform." http://:30080/
curl -sS -o /dev/null -w '%{http_code}\n' -H "Host: admin." http://:30080/
# 期望 200
# 版本账本
kubectl get cm -n nanocloud nanocloud-release -o jsonpath='{.data}'; echo
# 所有受管主容器和 initContainer 必须带动态解析出的站点 registry 前缀
for ns in nanocloud cert-manager longhorn-system; do
kubectl get pods -n "$ns" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{range .spec.initContainers[*]}{" init "}{.image}{"\n"}{end}{range .spec.containers[*]}{" main "}{.image}{"\n"}{end}{end}' 2>/dev/null
done
# 启动门禁应成功,主容器不应因数据库竞态发生重启
kubectl get pods -n nanocloud -o jsonpath='{range .items[*]}{.metadata.name}{" init="}{range .status.initContainerStatuses[*]}{.name}{":"}{.state.terminated.exitCode}{" "}{end}{" restart="}{range .status.containerStatuses[*]}{.name}{":"}{.restartCount}{" "}{end}{"\n"}'
# collection-report 最近调度应连续 Complete
kubectl get cronjob,job -n nanocloud | grep collection-report
# APISIX HMAC Consumer Secret(输出包含 app_secret,请勿粘贴到工单或群聊)
# 获取并解码 hmac-consumers.json
kubectl -n nanocloud get secret apisix-secret -o jsonpath='{.data.hmac-consumers\.json}' | base64 -d; echo
# 解码后用 jq 解析并格式化 JSON(需要 jq)
kubectl -n nanocloud get secret apisix-secret -o jsonpath='{.data.hmac-consumers\.json}' | base64 -d | jq .
# 如遇 JSONPath 转义兼容问题,可改用 go-template
kubectl -n nanocloud get secret apisix-secret -o go-template='{{index .data "hmac-consumers.json"}}' | base64 -d | jq .
9.5 验收勾选清单
-
包 sha256 校验通过;
release.lock无空 digest -
os-preflight无 FAIL -
k3s Ready;
nanocloud-registry/registryAvailable;images/pushed.manifest存在 -
(k3s-ha)全部计划节点 Ready,Longhorn CSI 族 Pod Running;未分层模式 default SC =
longhorn;分层模式 default SC =longhorn-ssd,且 RustFS PVC =longhorn-hdd -
各节点
/etc/rancher/k3s/registries.yaml指向站点 registry -
nanocloudns 工作负载就绪;helm status= deployed;三个受管 namespace 的主容器/initContainer 镜像前缀正确 -
六个 platform-chain 库 Goose 链头正确;Airflow/Prefect schema 就绪;analysis grants(若启用)
-
initContainer 全部成功;新建业务 Pod 主容器
restartCount=0;collection-report 最近任务连续 Complete -
管理员登录成功;用户、租户及租户绑定数据符合 Site Contract/初始化预期
-
部署同学手工验收通过(§9.1–§9.4)
-
Host 访问 platform / admin 均 200
9.6 日志部署与验收
日志能力由 Helm Chart 随平台统一部署,不需要在每个业务服务中增加 Sidecar。启用后,每个 Kubernetes 节点运行一个 Fluent Bit DaemonSet Pod,只采集当前 NanoCloud namespace 的容器标准输出,并写入 MinIO/S3。日志按日期和业务服务分层,最终对象路径为 nanocloud-logs/YYYYMMDD//.json.gz,例如 nanocloud-logs/20260807/xrai-core/xxx.json.gz。
**默认资源边界。**每节点请求 20m CPU / 32Mi 内存,上限 100m CPU / 96Mi 内存;内存缓冲上限为 32Mi,节点磁盘缓冲上限为 256Mi,上传并发为 1。磁盘缓冲位于独立 hostPath,仅在 MinIO 短时不可用时吸收积压,不占用业务容器的内存限制;达到上限后采集器按自身存储策略处理积压,不应阻塞业务进程写标准输出。
**保留策略。**Chart 的 post-install / post-upgrade 生命周期 Job 会创建 nanocloud-logs bucket,并配置 7 天对象过期和 1 天未完成分片清理。无需额外部署 CronJob 清理日志;旧目录结构中的对象也受同一 bucket 生命周期管理。
**离线依赖与 S3 选择。**日志采集器以 Fluent Bit 镜像随 airgap bundle 交付,不要求现场通过公网安装宿主机依赖;相关镜像必须进入镜像清单、写入 release.lock 并从站点 registry 拉取。s3.endpoint="" 时使用平台内嵌 MinIO;不使用内嵌 MinIO 时必须填写外部 MinIO/S3 endpoint,并通过独立 Secret 提供凭证,不要只修改 bucket 名称或在 values 中写明文密钥。
logging:
enabled: true
bucket: nanocloud-logs
retentionDays: 7
memoryBufferLimit: 32Mi
localBufferSize: 256Mi
uploadConcurrency: 1
s3:
endpoint: "" # 空表示使用平台内嵌 MinIO
manageBucketLifecycle: true
PKG=/opt/nanocloud/nanocloud--amd64
NS=nanocloud
RELEASE=nanocloud
export PATH="$PKG/bin:$PATH"
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
# 三节点场景应为 DESIRED/CURRENT/READY=3;Pod 必须分别落在三个节点
kubectl get nodes -o wide
kubectl -n "$NS" get daemonset "${RELEASE}-log-agent" -o wide
kubectl -n "$NS" get pods -l app.kubernetes.io/name=log-agent -o wide
# 镜像必须来自动态现场 registry 且带 @sha256,不得回退公网
kubectl -n "$NS" get daemonset "${RELEASE}-log-agent" \
-o jsonpath='{.spec.template.spec.containers[0].image}'; echo
# 配置中的对象路径应为 /%Y%m%d/$TAG[1]/$UUID.json.gz
kubectl -n "$NS" get configmap "${RELEASE}-fluent-bit" \
-o jsonpath='{.data.fluent-bit\.conf}' | grep s3_key_format
# 分别检查每个 DaemonSet Pod;每个节点都应出现上传成功记录
for pod in $(kubectl -n "$NS" get pods \
-l app.kubernetes.io/name=log-agent -o name); do
echo "===== $pod ====="
kubectl -n "$NS" logs "$pod" --since=15m \
| grep 'Successfully uploaded object' | tail -5
done
# 先核对 Chart 渲染的目标规则:ID=nanocloud-log-retention、Status=Enabled、Days=7
kubectl -n "$NS" get configmap "${RELEASE}-log-lifecycle" \
-o jsonpath='{.data.lifecycle\.json}'; echo
还必须从 MinIO 读取实际对象和实际 ILM,不能只检查 ConfigMap。以下临时 Pod 使用当前交付包锁定的 mc 镜像,凭据通过 Secret 引用注入,不在命令或日志中显示。
cd "$PKG"
SITE_REGISTRY=$(helm get values "$RELEASE" -n "$NS" -o json \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["images"]["registry"])')
MC_DIGEST=$(awk '$1 == "mc:" && match($0,/sha256:[0-9a-f]+/) {print substr($0,RSTART,RLENGTH); exit}' release.lock)
MC_IMAGE="${SITE_REGISTRY}/minio/mc@${MC_DIGEST}"
test -n "$SITE_REGISTRY" && test -n "$MC_DIGEST"
kubectl delete pod log-storage-audit -n "$NS" --ignore-not-found
kubectl apply -f - /dev/null
echo '===== recent log objects ====='
mc find logs/nanocloud-logs --name '*.json.gz' --newer-than 1d
echo '===== actual lifecycle ====='
mc ilm export logs/nanocloud-logs
env:
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: nanocloud-credentials
key: minio-root-user
- name: S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: nanocloud-credentials
key: minio-root-password
EOF
kubectl wait -n "$NS" --for=jsonpath='{.status.phase}'=Succeeded \
pod/log-storage-audit --timeout=180s
kubectl logs log-storage-audit -n "$NS"
kubectl delete pod log-storage-audit -n "$NS"
验收结果必须同时满足:DaemonSet 为 3/3、三个节点均有成功上传记录、MinIO 中存在 nanocloud-logs/YYYYMMDD//.json.gz,且实际 ILM 包含 nanocloud-log-retention、Enabled 和 Expiration.Days=7。仅看到 ConfigMap 中写了 7 天,不代表生命周期已在对象存储侧生效。
若 DaemonSet 在某个节点长期停留于 ImagePullBackOff,先检查该节点的 registry mirror、包内 images/pushed.manifest、release.lock 中的 Fluent Bit digest,以及渲染后的镜像是否以现场 registry 为前缀。不要通过改回 Docker Hub 地址绕过离线链路。
# 查看采集器错误、重试和上传失败
kubectl -n "$NS" logs daemonset/"${RELEASE}-log-agent" --since=30m | grep -Ei "error|retry|failed|buffer" | tail -100
# 查看 DaemonSet 是否有节点未就绪
kubectl -n "$NS" get daemonset "${RELEASE}-log-agent" -o wide
kubectl -n "$NS" get pods -l app.kubernetes.io/name=log-agent -o wide
# 查看节点本地缓冲目录(按实际节点进入)
sudo du -sh /var/lib/nanocloud/log-agent/buffer 2>/dev/null || true
sudo ls -lh /var/lib/nanocloud/log-agent/ 2>/dev/null || true
# 外部 S3 场景:确认 endpoint 和凭证 Secret 已注入,不要回退公网
kubectl -n "$NS" get secret -o name | grep -E "log|s3|object"
-
日志采集 DaemonSet 在全部节点 Ready,镜像来自现场 registry 且 digest 与 release.lock 一致
-
MinIO 中已出现
nanocloud-logs/YYYYMMDD//对象,生命周期规则为 7 天
10. AI 管线
Chart 默认 services.pipeline.enabled=true,会部署 AI 管线并开启文档约定的 MinIO 制品链路。不需要该能力时,
在 Site Contract 显式设为 false 并重跑 install.sh:
services:
pipeline:
enabled: true
datahubApiKey: "" # 空则首装 lookupOrRand 写入 nanocloud-credentials
vlm:
arkApiKey: "" # caption / instruction 需要
dashscopeApiKey: "" # 按需
# Caption、Instruction 和制品物化链路默认开启。
# Smart QC、Tagging、Importer、通知及策略类业务默认关闭。
| 项 | 说明 |
|---|---|
| 镜像 | 包内需含 data-hub / flowforge / artifact-writer |
| 建库 | data_pipeline 由平台迁移 Job db-migrate-data-pipeline(weight -10)entrypoint 幂等创建;prefect 由 hook Job pg-ensure(weight -6)幂等创建,晚开也安全(2026-07-29 起替代 data-pipeline-ensure) |
| 表结构 | data_pipeline schema 唯一通道 = 平台 goose 链(db-migrate-data-pipeline,goose validate && up);原 data-hub -mode migrate 通道已退役(migration-rebaseline-plan.md) |
| 工作负载 | data-hub、prefect-server、FlowForge resident/Caption/Instruction worker、artifact-writer |
| 核心调度 | Dispatcher、Reconciler 和 Instruction Artifact Sync 默认开启;Smart QC、Tagging 等业务仍默认关闭 |
| 入口 | Data Hub 不暴露 HTTP 路由;Prefect UI 由 gateway.expose.prefect 控制,且未接入 NanoCloud RBAC |
| FlowForge | worker 使用集群内 http://prefect-server:4200/api,不连接云端 Prefect |
| 凭证 | kubectl get secret nanocloud-credentials -n nanocloud -o jsonpath='{.data.datahub-api-key}' | base64 -d; echo |
启用后核验:
kubectl get job -n nanocloud -l 'app.kubernetes.io/component in (db-migrate-data-pipeline,pg-ensure)'
kubectl get deploy -n nanocloud data-hub prefect-server flowforge-qc-worker artifact-writer
kubectl exec -n nanocloud postgres-0 -- sh -c \
'PGPASSWORD=$POSTGRES_PASSWORD psql -U "$POSTGRES_USER" -d postgres -tAc \
"SELECT datname FROM pg_database WHERE datname IN ('\''data_pipeline'\'','\''prefect'\'') ORDER BY datname"'
11. 备份开通(生产必做)
生产 profile 契约要求 backup.enabled=true + 非空 backup.target.endpoint(install 预检)。
目标必须离机:与集群同机/同盘的备份不构成灾难恢复能力。验证环境可临时指向内嵌 MinIO,上线前必须改离机端点。
11.1 A 轨架构速览
每日 02:00 CronJob -site-export(concurrencyPolicy=Forbid,失败重试 3 次):
mysqldump/pg_dump(C1 库清单)→ 版本账本导出 → tar + zstd → age 公钥加密
→ mc 推送离机目标(S3 契约,只写凭据)→ 保留策略(日 7 + 周 4 + 月 12)
- C1 库清单:MySQL
db_x2robot_cloud,db_x2robot_v2,db_x2robot_analytics;PGxrai-core,db_label_service,airflow,data_pipeline,prefect;ClickHousex2robot_analysis。 - payload:
dbs/(dump)+ledger/(账本快照)+keys/(记名不记值密钥清单)。 - age 私钥永不进集群(日常路径);仅恢复演练 Job 按需挂载。
11.2 开通步骤
# ① 在集群外的受控运维机生成 age 密钥对;私钥权限仅限当前用户
umask 077
age-keygen -o backup-age-private-key.txt
age-keygen -y backup-age-private-key.txt > backup-age-public-key.txt
# 公钥必须为单行 age1...;私钥以 AGE-SECRET-KEY- 开头
# 只允许把公钥写入集群,backup-age-private-key.txt 必须离线保管,禁止上传服务器/集群
cat backup-age-public-key.txt
NS=nanocloud
# ② 创建或更新 age 公钥 Secret(键名必须为 publicKey)
kubectl -n "$NS" create secret generic backup-age-public-key \
--from-file=publicKey=backup-age-public-key.txt \
--dry-run=client -o yaml | kubectl apply -f -
# 验证公钥格式;输出应以 age1 开头
kubectl -n "$NS" get secret backup-age-public-key \
-o jsonpath='{.data.publicKey}' | base64 -d
echo
# ③ 创建或更新备份目标凭据 Secret(外部 S3/OSS/MinIO)
# AK/SK 由目标存储管理员提供,建议仅授予目标 bucket 的最小读写权限
kubectl -n "$NS" create secret generic backup-target-credentials \
--from-literal=accessKey=[REDACTED]' \
--from-literal=secretKey=[REDACTED]' \
--dry-run=client -o yaml | kubectl apply -f -
# 内部 minio 获取
kubectl -n "$NS" get secret nanocloud-credentials \
-o jsonpath='{.data.minio-root-user}' | base64 -d
# 内部 minio 获取
kubectl -n "$NS" get secret nanocloud-credentials \
-o jsonpath='{.data.minio-root-password}' | base64 -d
# 只检查键名和字节数,不输出凭据明文
kubectl -n "$NS" describe secret backup-target-credentials
Site Contract 追加后重跑 install.sh:
backup:
enabled: true
target:
endpoint: https://s3. # 离机!
bucket: nanocloud-backup
prefix: site-export
credentialsSecret: backup-target-credentials
# retention: { daily: 7, weekly: 4, monthly: 12 }
# convenienceCopy: { enabled: true, keep: 7 } # 可选:平台 MinIO 便利副本(非 DR)
内部存储:使用内嵌 MinIO
仅为跑通备份 CronJob 和恢复流程时,可将目标临时设为同 namespace 的 http://minio:9000。这份副本与业务处于同一集群、同一故障域,不属于离机备份,也不能作为生产灾难恢复依据。
backup:
enabled: true
target:
endpoint: http://minio:9000
bucket: nanocloud-backup
prefix: site-export
credentialsSecret: backup-target-credentials
backup-target-credentials 和 backup-age-public-key 不会由 Chart 自动创建。走内部存储可将现有平台 MinIO 凭据转换为备份目标要求的键名;命令不会输出凭据明文:
NS=nanocloud
# 从平台 Secret 读取内部 MinIO 管理凭据;变量只存在于当前 shell
MINIO_USER=$(kubectl -n "$NS" get secret nanocloud-credentials \
-o jsonpath='{.data.minio-root-user}' | base64 -d)
MINIO_PASSWORD=$(kubectl -n "$NS" get secret nanocloud-credentials \
-o jsonpath='{.data.minio-root-password}' | base64 -d)
# 转换成备份 Job 要求的 accessKey / secretKey 键名;重复执行会更新 Secret
kubectl -n "$NS" create secret generic backup-target-credentials \
--from-literal=accessKey=[REDACTED] \
--from-literal=secretKey=[REDACTED] \
--dry-run=client -o yaml | kubectl apply -f -
unset MINIO_USER MINIO_PASSWORD
# 只验证 Secret 存在、键名正确,不输出凭据明文
kubectl -n "$NS" describe secret backup-target-credentials
# 内部 MinIO 只替代备份目标,不替代加密步骤;仍须按上文创建 backup-age-public-key
kubectl -n "$NS" get secret backup-age-public-key
离线演练:不修改包内 Chart,使用包内 backup-tools 生成 age 密钥
本流程用于干净环境验收。只新增站点 overlay 与 Kubernetes Secret,不修改交付包内 Chart、release.lock 或镜像清单。backup-tools 必须由当前交付包的 release.lock 锁定,并从现场 registry 以 digest 拉取;禁止临时访问公网镜像。
先在已完成离线安装的首节点执行以下命令。PKG 指向当前版本解包根目录;PROFILE 和 SITE_VALUES 必须与首次安装保持一致。
PKG=/opt/nanocloud/nanocloud--amd64
NS=nanocloud
RELEASE=nanocloud
PROFILE=k3s-ha
SITE_VALUES=/tmp/site-k3s-ha.yaml
export PATH="$PKG/bin:$PATH"
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
cd "$PKG"
test -s release.lock
test -s "$SITE_VALUES"
kubectl get nodes
# 从已部署 Helm values 读取动态现场 registry;不得写死节点 IP
SITE_REGISTRY=$(helm get values "$RELEASE" -n "$NS" -o json \
| python3 -c 'import json,sys; print(json.load(sys.stdin)["images"]["registry"])')
BACKUP_DIGEST=$(awk '$1 == "backup:" && match($0,/sha256:[0-9a-f]+/) {print substr($0,RSTART,RLENGTH); exit}' release.lock)
test -n "$SITE_REGISTRY"
test -n "$BACKUP_DIGEST"
BACKUP_IMAGE="${SITE_REGISTRY}/nanocloud/backup-tools@${BACKUP_DIGEST}"
printf 'backup image: %s\n' "$BACKUP_IMAGE"
密钥生成 Pod 的日志包含 age 私钥,因此必须直接写入权限为 0600 的受控文件,不在终端回显。Pod 日志落盘后立即删除;集群只保存公钥 Secret。
KEY_DIR=/root/nanocloud-preserve
KEY_FILE="$KEY_DIR/backup-age-private-key.txt"
install -d -m 700 "$KEY_DIR"
umask 077
kubectl delete pod age-keygen -n "$NS" --ignore-not-found
kubectl run age-keygen -n "$NS" \
--image="$BACKUP_IMAGE" --restart=Never --command -- age-keygen
kubectl wait -n "$NS" --for=jsonpath='{.status.phase}'=Succeeded \
pod/age-keygen --timeout=120s
kubectl logs age-keygen -n "$NS" > "$KEY_FILE"
chmod 600 "$KEY_FILE"
kubectl delete pod age-keygen -n "$NS"
# 只解析公钥;不得 cat 私钥文件
AGE_PUBLIC_KEY=$(sed -n 's/^# public key: //p' "$KEY_FILE" | head -1)
case "$AGE_PUBLIC_KEY" in age1*) ;; *) echo 'age 公钥解析失败' >&2; exit 1;; esac
kubectl create secret generic backup-age-public-key -n "$NS" \
--from-literal=publicKey="$AGE_PUBLIC_KEY" \
--dry-run=client -o yaml | kubectl apply -f -
unset AGE_PUBLIC_KEY
# 只检查 Secret 键和长度,不输出公钥或私钥
kubectl describe secret backup-age-public-key -n "$NS"
随后按上方“内部 MinIO:复用平台凭据创建目标 Secret”创建 backup-target-credentials,并在包外新建站点 overlay。内部 MinIO 仅用于证明导出、加密、上传和回读校验链路,不构成离机容灾。
backup:
enabled: true
target:
endpoint: http://minio:9000
bucket: nanocloud-backup
prefix: site-export
credentialsSecret: backup-target-credentials
retention:
enabled: true
daily: 7
weekly: 0
monthly: 0
BACKUP_VALUES=/tmp/backup-test-values.yaml
test -s "$BACKUP_VALUES"
cd "$PKG"
sudo env PATH="$PATH" KUBECONFIG="$KUBECONFIG" \
bash scripts/install.sh --profile "$PROFILE" --airgap \
--values "$SITE_VALUES" --values "$BACKUP_VALUES"
kubectl get cronjob "${RELEASE}-site-export" -n "$NS"
kubectl get cronjob "${RELEASE}-site-export" -n "$NS" \
-o jsonpath='{.spec.jobTemplate.spec.template.spec.containers[?(@.name=="site-export")].image}'; echo
最后一条命令输出必须是现场 registry 前缀并带 @sha256:。重跑安装器会按受支持入口重新校验离线物料和当前 release;请在维护窗口执行并保留完整终端记录。
上线前必须把 backup.target.endpoint、目标凭据和 bucket 切换到独立于 NanoCloud 集群的 S3/OSS/MinIO,并重新执行一次手动备份与恢复验证。
11.3 手动触发与验证
NS=nanocloud
RELEASE=nanocloud
CRONJOB="${RELEASE}-site-export"
JOB="${CRONJOB}-manual-$(date +%Y%m%d%H%M%S)"
kubectl get cronjob "$CRONJOB" -n "$NS"
kubectl create job -n "$NS" --from="cronjob/$CRONJOB" "$JOB"
if ! kubectl wait -n "$NS" --for=condition=complete "job/$JOB" --timeout=30m; then
kubectl describe job "$JOB" -n "$NS"
kubectl logs "job/$JOB" -n "$NS" --all-containers --prefix=true --tail=300
exit 1
fi
# 完整保留验收日志;末行应包含“A 轨备份成功”和 backupId
kubectl logs "job/$JOB" -n "$NS" -c site-export --timestamps
# 应同时看到 data 与 annotation 两处 backup.last,location 指向 site-export/
kubectl get configmap "${RELEASE}-release" -n "$NS" \
-o jsonpath='{.data.backup\.last}'; echo
kubectl get configmap "${RELEASE}-release" -n "$NS" \
-o go-template='{{ index .metadata.annotations "nanocloud.io/backup-last" }}'; echo
# 完成取证后再清理临时 Job
kubectl delete job "$JOB" -n "$NS"
成功日志必须覆盖:MySQL 3 库、PostgreSQL 5 库、启用时的 ClickHouse 库、账本与记名不记值的密钥清单、tar.zst 压缩、age 加密、上传后 SHA-256 回读一致,以及保留轮换结果 日 7 / 周 0 / 月 0。若日志只显示 Job Complete,但缺少回读校验或 backup.last,不得判定备份闭环通过。
排障速查:Job 起不来且报 backup-age-public-key 缺失 → 公钥 Secret 未建或键名错;推送 403 → 目标凭据/权限不符;dump 连不上库 → nanocloud-credentials 口令与 infra values 不一致。
11.4 B 轨(对象复制 + 月度演练,按需)
默认关。生产开备份时可显式打开:
- 源桶版本化:
objectTrack.enabled=true时minio-init对业务桶mc version enable+ ILMnoncurrent-expire-days = graceDays(默认 7)。 - 对象复制:
mode: mirror(mc mirror --overwrite,不开--remove)或replication(mc replicate add+ 水位探测);副本 Secretbackup-object-replica-credentials;禁止把 replica endpoint 指回平台 MinIO 自身。 - 月度演练:
drill.enabled=true需 age 私钥 Secret(backup-age-private-key,键privateKey)——与日常"私钥不进集群"路径不同,现场拒绝则不要开。演练深度:抽一份 A 轨 → 解密 → checksum → 副本抽样 → 写drill.last;不做隔离库全量导入。 drill.requireForUpgrade: true且严格模式时,空/fail/≥45 天演练记录会让升级门 FAIL。
12. 升级与回退
12.1 升级模型(先迁移后滚动)
升级 = 用新交付包在同一 Site Contract 上重跑同一条 install.sh 命令。helm hook 保证顺序:
pre-upgrade -20 backup-gate 备份门(G1 快照桩 / G2 每日备份新鲜度 / G3 账本可读 / G4 演练年龄 / G5 复制滞后)
pre-upgrade -10 db-migrate-* 平台迁移链 goose validate && up(仅 platform-chain 库)
主资源滚动更新(Deployment / StatefulSet)
关键性质:
- 先迁移后滚动:schema 先于代码升级;迁移只增不改(破坏性变更拆 expand/contract 两个 Release)。
- 备份门:备份过期时门即时补跑一次完整 A 轨(产物前缀
backups/,与每日备份的site-export/不同,都正常);补跑失败才拦升级,且主资源零改动。备份未开通(backup.enabled=false)时门直接跳过——生产升级前务必先开通每日备份,否则失去回滚锚点。 - 升级窗:新包
release.lock的minUpgradeFrom与集群账本比较,`from --namespace $NS --airgap --values /tmp/site.yaml --dry-run # 与正式安装同输入渲染比对
### 12.3 执行与验证
cd /opt/nanocloud/nanocloud--amd64
export PATH="$PWD/bin:$PATH"
sudo env PATH="$PATH" bash scripts/install.sh
--profile --airgap --values /tmp/site.yaml
观察(另开终端):`kubectl get jobs -n $NS -w`(先 backup-gate 后 db-migrate);
`kubectl logs -n $NS -l job-name=-db-migrate- -f`。
升级后验证:goose 链头 = 新包链头;账本 `release` / `lastUpgradeAt` 回写;全 Pod Running;
镜像 digest 与新包 `release.lock` 一致且仍以动态站点 registry 为前缀;业务冒烟同 §9。
### 12.4 失败恢复与回退
| 场景 | 处置 |
|-|-|
| `Prior release in state 'pending-*'` | 上次操作中断:`--recover-failed` 优先;无 good revision 再 `--force-reinstall` |
| 备份门失败 | 先手动触发一次备份(§11.3),成功后再升级 |
| 迁移 hook 失败 | release 标 failed、主资源零改动;看 hook Pod 日志定位 SQL,修复后重跑 install.sh |
| 升级后业务异常、schema 向后兼容 | 安装器可回滚到上一 Helm revision;数据库迁移不回退,完成后按 §9 复验 |
| 已前进 schema 不兼容或数据损坏 | **不做 goose down,也不自动恢复数据库**;审批后用 `scripts/restore.sh` 指定 backupId 恢复,再用旧交付包重装 |
---
## 13. 卸载与清场
export PATH="$PWD/bin:$PATH"
bash scripts/uninstall.sh --platform --namespace nanocloud # 卸平台(保留业务 PVC) bash scripts/uninstall.sh --platform --purge --namespace nanocloud # 并删该 ns 全部 PVC(交互确认) bash scripts/uninstall.sh --foundation # 仅打印拆 k3s 指引(不自动执行)
| 对象 | 位置 | 说明 |
|-|-|-|
| Helm release / 业务 PVC | ns `nanocloud` | `--purge` 只删**该 ns** 的 PVC |
| 版本账本 CM `nanocloud-release` | ns `nanocloud` | 带 keep policy,helm uninstall **不删**;重装受阻时手删或 `--force-reinstall` |
| Registry PVC | ns `nanocloud-registry`,名 `registry-data` | **不在**`--purge` 范围;彻底清场另行删除 |
| k3s | 本机 | 确认后手工 `sudo bash /usr/local/bin/k3s-uninstall.sh`(agent:`k3s-agent-uninstall.sh`) |
彻底清场示例(**不可逆,生产慎用**):
sudo bash scripts/uninstall.sh --platform --purge --namespace nanocloud kubectl delete ns nanocloud-registry --ignore-not-found kubectl delete configmap nanocloud-release -n nanocloud --ignore-not-found sudo bash /usr/local/bin/k3s-uninstall.sh
**三节点彻底清场顺序:**先卸业务和 Longhorn,确认 CSI 挂载已释放;再先卸两个 agent,最后卸首节点 server。反向操作可能让 Longhorn globalmount 或 kubelet mount 卡住 K3s 卸载。
1. 首节点执行平台 `--purge`,删除站点 Registry,并卸载 Longhorn。
2. 在 `172.16.10.242`、`172.16.10.243` 依次运行 `/usr/local/bin/k3s-agent-uninstall.sh`。
3. 在 `172.16.10.241` 运行 `/usr/local/bin/k3s-uninstall.sh`。
4. 逐节点确认没有 kubelet、Longhorn 或 K3s 残留挂载,再清理明确的旧数据目录。
每台节点验收;systemd 返回 inactive/unknown,findmnt 无输出
systemctl is-active k3s k3s-agent || true findmnt -rn -o TARGET | grep -E '/var/lib/(kubelet|longhorn|rancher/k3s)' || true
仅在确认是测试环境且数据已批准删除后执行
rm -rf /var/lib/longhorn /var/lib/rancher/k3s /var/lib/kubelet
再次确认目录与挂载均不存在
test ! -e /var/lib/longhorn test ! -e /var/lib/rancher/k3s findmnt -rn -o TARGET | grep -E '/var/lib/(kubelet|longhorn|rancher/k3s)' && exit 1 || true
---
## 14. 故障排查
### 14.1 通用诊断入口
helm status nanocloud -n nanocloud kubectl get jobs -n nanocloud --sort-by=.metadata.creationTimestamp kubectl get pods -n nanocloud --field-selector=status.phase!=Running,status.phase!=Succeeded kubectl logs -n nanocloud -l job-name= --tail=200
`helm STATUS=failed` 时**不要只看 DESCRIPTION 提到的最后一个 Job**——列全部 hook/Job。成功的 hook Job 可能已被 `hook-delete-policy` 删除,属正常。常见 hook:`jwt-keygen`、`db-migrate-*`、`analysis-grants`、`minio-init`、`backup-gate`(升级时)。
### 14.2 镜像拉取 / registry
| 现象 | 处置 |
|-|-|
| registry Pod ImagePullBackOff | `sudo bash scripts/seed-registry-image.sh --images-dir images`(import 后 `ctr tag` 短名);确认 socket `/run/k3s/containerd/containerd.sock` |
| push 失败 connection refused | registry 未 Ready / 防火墙:`kubectl -n nanocloud-registry rollout status deploy/registry`;放行 **30500** |
| 业务 Pod ImagePullBackOff | mirror 未生效 / 未 push / 前缀错:重跑 `configure-registry-mirror.sh --registry :30500`;查 `images/pushed.manifest` |
| 双前缀 not found(`…/…/…`) | values 镜像引用重复写了 registry 前缀;由 `images.registry` 拼接,引用保持短名 |
| IfNotPresent 仍拉不到,ctr 能看到 | 镜像导进了系统 containerd 而非 k3s 的;用包内脚本(自动探测 k3s socket)重导 |
| tag 能拉、`@sha256:` 失败 | digest 问题,见 §14.4 |
| jwt-keygen DeadlineExceeded | 多为 toolbox digest 拉取失败(`@sha256:` 引用,tag 预拉无效);先修 digest(§14.4) |
| `release.lock` 空 digest | 非客户包 / lab 逃逸;拒绝安装,向发版方索取完整 digest 包 |
### 14.3 数据库 / hook
| 错误 / 现象 | 根因与处置 |
|-|-|
| `ERROR 1049` Unknown database | 库尚未被 migrate 创建:查 db-migrate hook 日志,确认三库注册且 Job 成功 |
| `ERROR 1044` Access denied | 业务账号缺授权:查 `analysis-grants` Job(须在 migrate 之后成功);verify-schema 会查 grants |
| `pending-install` + 无 Job + 只有 cloud 库 | **Helm `--wait` 死锁**(Chart /dev/null
cat /etc/rancher/k3s/registries.yaml
# 3) 对比 lock digest 与 registry 实际 digest
REG=:30500
LOCK_DIGEST=$(grep -E '^[[:space:]]+toolbox:' release.lock | grep -oE 'sha256:[a-f0-9]+')
TAG_DIGEST=$(PATH=$PWD/bin:$PATH crane digest --insecure ${REG}/nanocloud/toolbox:1.0.0)
curl -sS -o /dev/null -w '%{http_code}\n' -I "http://${REG}/v2/nanocloud/toolbox/manifests/${LOCK_DIGEST}"
修复(推荐 A):
# A) 从包重 push,使 registry 同时有 tag 与 lock digest
export PATH="$PWD/bin:$PATH"
bash scripts/push-images.sh --registry "$REG" --images-dir images
# B) registry 无法重 push 时 — helm 覆盖为 registry 实际 digest
helm upgrade nanocloud ./chart -n nanocloud --reuse-values \
--set "images.digests.toolbox=${TAG_DIGEST}"
kubectl delete job -n nanocloud -l app.kubernetes.io/component=jwt-keygen --ignore-not-found
# 长期:重跑 configure-registry-mirror.sh(写 registries + config + restart)
sudo bash scripts/configure-registry-mirror.sh --registry ${REG}
lab 还可用 install.sh --allow-digest-override --digest-override digests-override.yaml 在 lock 注入后覆盖。
14.5 Longhorn(k3s-ha)
-
CSI / engine ImagePullBackOff:包内
addons.images.list缺 CSI sidecar / engine 族。有网跳板补齐后重 push(v1.7.0 完整集见 §2.3 清单):REG=:30500 for img in longhornio/csi-attacher:v4.6.1 longhornio/csi-provisioner:v4.0.1 longhornio/csi-resizer:v1.11.1 longhornio/csi-snapshotter:v7.0.2 longhornio/csi-node-driver-registrar:v2.11.1 longhornio/livenessprobe:v2.13.1 longhornio/longhorn-engine:v1.7.0 longhornio/longhorn-instance-manager:v1.7.0; do ./bin/crane copy "docker.io/${img}" "${REG}/${img}" --insecure done sudo bash foundation/base-install.sh --profile k3s-ha --longhorn-only -
registry PVC Pending:
local-path磁盘不足 / provisioner 异常(registry 不迁 Longhorn):kubectl -n nanocloud-registry get pvc,events;kubectl get sc local-path。
14.6 磁盘 / PVC 过小(lab 小盘)
0.4.33 的 k3s-ha 默认业务 PVC 请求合计约 115Gi。Longhorn 默认每卷 2 副本,仅副本数据即需要约 230Gi,还未计入 storage-minimal-available-percentage=25、Registry local-path、系统组件、快照和增长空间。本次三台约 99Gi 系统盘在默认值下出现 insufficient storage / ReplicaSchedulingFailure,不能通过只缩小 RustFS 一个卷解决。
# 仅用于本次约 100Gi/节点的 0.4.33 测试环境;生产不得直接复制
persistence:
mysql: { size: 10Gi }
postgres: { size: 5Gi }
redis: { size: 2Gi }
rustfs: { size: 10Gi }
emqx: { size: 2Gi }
kafka: { size: 2Gi }
clickhouse: { size: 10Gi }
airflow: { size: 5Gi }
上述测试 overlay 的逻辑请求总量为 46Gi,按 2 副本约占 92Gi,最终 8 个 Longhorn volume 均为 attached/healthy。生产容量必须按“PVC 请求总量 × 副本数 + 最小保留空间 + 快照 + 预期增长”规划,优先增加独立数据盘;不得照搬测试容量,也不得通过放宽 Longhorn over-provisioning 或 minimal-available 阈值掩盖物理容量不足。Registry PVC 仍使用 local-path,不计入业务 Longhorn PVC,但必须单独预留系统盘空间。
14.7 节点 Muttipathd 条件异常
# 每个节点都需要执行 最好提前和存储管理员确认
cp -a /etc/multipath.conf \
/etc/multipath.conf.bak.$(date +%Y%m%d%H%M%S)
cat >/etc/multipath.conf /dev/null || true
systemctl reload multipathd || systemctl restart multipathd
# 验证配置
grep -n -A10 -B2 blacklist /etc/multipath.conf
systemctl is-active multipathd
multipath -ll
# 更新配置后可继续执行安装脚本
14.8 其他高频项
| 现象 | 处置 |
|---|---|
Prior release in state 'pending-*' | --recover-failed 或 --force-reinstall |
| helm 已卸但重装被账本拦(minUpgradeFrom 空) | kubectl delete cm nanocloud-release -n nanocloud;或 --force-reinstall(无 release 时自动清 orphan 账本) |
部署后镜像全变 :latest、组件消失 | 包内残留 chart/values.local.yaml 被自动叠加:删除/改名后重装 |
helm: command not found(sudo 下) | sudo env PATH="$PWD/bin:$PATH" bash scripts/install.sh …,或 ./bin/helm |
| NodePort 冲突 | 改 overlay 中 Traefik / MQTT nodePort,或查占用 |
GatewayClass "traefik" exists and cannot be imported | 集群级单例被占:overlay 改 gateway.className: traefik- 独占 |
robot-service Ready 失败、:8000 refused | 进程等 MQTT 连上才听 HTTP;勿关 MQTT auth,核对 nanocloud-credentials 注入 |
CRD apply / 渲染报 YAML 错,目录有 ._* | macOS AppleDouble:find /opt/nanocloud -name '._*' -delete;打包侧 COPYFILE_DISABLE=1 |
前端 nginx.conf mount「not a directory」 | frontend-nginx ConfigMap 未生成(旧 chart 模板缺陷);升级包并确认 CM 存在 |
**日志告警忽略边界。**短时的单次上传失败可以观察重试是否恢复;但如果持续忽略 MinIO/S3 不可达、磁盘缓冲增长或 DaemonSet 非 Ready,超过 256Mi 本地缓冲上限后,尚未上传的日志可能丢失,且 Pod 删除后无法追溯。处理顺序:先确认 log-agent Pod、registry mirror 和 endpoint/Secret,再检查 MinIO/S3 连通性与生命周期配置;恢复后用 9.6 节的上传成功日志、对象路径和生命周期查询命令完成闭环验收。
附录:命令速查与版本钉扎
15.1 命令速查
# —— 构建机 ——
bash k8s/scripts/package.sh --arch amd64
bash k8s/scripts/package.sh --arch amd64 --from-local
# —— 目标机:预检 / 安装 ——
sudo bash foundation/os-preflight.sh --profile standalone
export PATH="$PWD/bin:$PATH"
sudo env PATH="$PATH" bash scripts/install.sh --profile standalone --airgap --values /tmp/site.yaml
sudo env PATH="$PATH" bash scripts/install.sh --profile standalone --airgap --values /tmp/site.yaml --dry-run
sudo env PATH="$PATH" bash scripts/install.sh --profile standalone --airgap --values /tmp/site.yaml --force-reinstall
# —— 状态 / 测试 ——
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
bash scripts/status.sh -n nanocloud
helm status nanocloud -n nanocloud
kubectl get pods,svc,gateway,httproute -n nanocloud
kubectl get pods,pvc -n nanocloud-registry
# —— 备份 ——
kubectl create job -n nanocloud --from=cronjob/-site-export manual-backup-$(date +%Y%m%d%H%M)
# —— 卸载 ——
bash scripts/uninstall.sh --platform --namespace nanocloud
bash scripts/uninstall.sh --platform --purge --namespace nanocloud
bash scripts/uninstall.sh --foundation
# —— 拆步(一般不必)——
sudo bash foundation/base-install.sh --profile standalone --airgap
sudo bash scripts/seed-registry-image.sh --images-dir images
sudo bash scripts/push-images.sh --registry ":30500" --images-dir images
sudo bash scripts/configure-registry-mirror.sh --registry ":30500"
# 获取密码:
# 默认用户名:admin 密码获取如下
kubectl get secret nanocloud-credentials \
-n nanocloud \
-o jsonpath='{.data.user-service-password}' | base64 -d
echo
15.2 组件版本钉扎(foundation f2026.07)
| 组件 | 版本 | 组件 | 版本 |
|---|---|---|---|
| k3s | v1.30.2+k3s1 | cert-manager | v1.15.3 |
| Longhorn | v1.7.0 | registry | 2.8.3 |
| kube-vip | v0.8.2 | helm(包内工具) | v3.16.4 |
以交付包 release.lock 的 foundation.components 段为最终依据。
实测补充:nano.x2robot.com、备份日志与 HTTPS
本节记录 2026-08-10 在 39.108.97.7 三节点 K3s 环境的实测结果,并给出当前 Chart 启用 HTTPS 的安全路径。运行版本为 NanoCloud 0.4.18,入口为 Chart 自管 Traefik + Gateway API,Service 类型为 LoadBalancer(K3s ServiceLB)。
16.1 当前状态与域名结果
| 检查项 | 实测结果 |
|---|---|
| 业务域名 | network.publicAddress=nano.x2robot.com,协议暂为 http |
| HTTPRoute | platform、admin、api、oss、minio-console、airflow、prefect 共 7 条,全部 Accepted=True、ResolvedRefs=True |
| DNS | 7 个 FQDN 的 A 记录均解析到 39.108.97.7 |
| 备份 | 手工触发 CronJob 成功;3 个 MySQL 库、5 个 PostgreSQL 库、45 张 ClickHouse 表、版本账本和 2 份 C0 密钥进入 age 加密载荷,上传后 checksum 一致 |
| 日志 | nanocloud-log-agent 为 3/3;日志对象持续写入 nanocloud-logs,ILM 为 7 天 |
| HTTPS | 当前尚未启用:tls-nanocloud Secret 不存在,Gateway 只有 web:HTTP:8000 listener |
域名按服务名前缀统一派生:
platform.nano.x2robot.com
admin.nano.x2robot.com
api.nano.x2robot.com
oss.nano.x2robot.com
minio-console.nano.x2robot.com
airflow.nano.x2robot.com
prefect.nano.x2robot.com
入口验收命令如下。API 根路径返回 404、MinIO 匿名根路径返回 403 都属于后端语义,不代表路由失败。
kubectl get httproute -n nanocloud \
-o custom-columns=NAME:.metadata.name,HOSTNAMES:.spec.hostnames[*]
for host in platform admin minio-console airflow prefect; do
curl -sS -o /dev/null -w "$host=%{http_code}\n" \
-H "Host: ${host}.nano.x2robot.com" http://39.108.97.7/
done
for host in api oss; do
curl -sS -o /dev/null -w "$host=%{http_code}\n" \
-H "Host: ${host}.nano.x2robot.com" http://39.108.97.7/
done
16.2 备份与日志复核命令
# 定时策略与最近执行
kubectl get cronjob nanocloud-site-export -n nanocloud -o wide
kubectl get cm nanocloud-release -n nanocloud \
-o jsonpath='{.data.backup\.last}{"\n"}'
# 用 CronJob 原模板做一次现场验收
job="nanocloud-site-export-manual-$(date +%H%M%S)"
kubectl create job -n nanocloud \
--from=cronjob/nanocloud-site-export "$job"
kubectl wait -n nanocloud --for=condition=complete \
"job/$job" --timeout=20m
kubectl logs -n nanocloud "job/$job" --tail=100
# 日志 Agent 与最近上传结果
kubectl get ds nanocloud-log-agent -n nanocloud
for pod in $(kubectl get pod -n nanocloud \
-l app.kubernetes.io/name=log-agent -o name); do
kubectl logs -n nanocloud "$pod" --since=10m | \
grep -E 'Successfully uploaded|error|failed|retry' | tail -n 30
done
# 在内嵌 MinIO 中检查对象和 7 天 ILM;命令不输出访问密钥
kubectl exec -n nanocloud minio-0 -- sh -c '
mc alias set audit http://127.0.0.1:9000 \
"$MINIO_ROOT_USER" "$MINIO_ROOT_PASSWORD" >/dev/null
mc ls --recursive audit/nanocloud-backup | tail -n 20
mc ls --recursive audit/nanocloud-logs | tail -n 20
mc ilm rule ls audit/nanocloud-logs
'
实测注意:修改域名触发 Helm 滚动更新 MinIO 时,Fluent Bit 曾在约 30 秒内出现
PutObject request failed;MinIO Ready 后三节点均自动重试并继续输出Successfully uploaded object,对象数继续增长。验收不能只看 DaemonSet 3/3,必须同时检查 Agent 日志与目标桶的新对象时间。
16.3 HTTPS 前置条件与 Chart 行为
启用 HTTPS 前必须同时满足 DNS、网络、证书和 Helm 配置四个条件。80/443 应从客户端网络到达 Traefik;证书必须覆盖上面的 7 个 FQDN,可使用一张 *.nano.x2robot.com 通配符证书,或使用包含 7 个名称的多 SAN 证书。完整证书链和私钥必须匹配,私钥不得写入代码仓或飞书文档。
# DNS 与公网入口
for host in platform admin api oss minio-console airflow prefect; do
fqdn="${host}.nano.x2robot.com"
printf '%-42s ' "$fqdn"
dig +short A "$fqdn"
done
# 端口连通性
nc -vz 39.108.97.7 80
nc -vz 39.108.97.7 443
# 证书有效期、签发者与 SAN
openssl x509 -in fullchain.pem -noout \
-subject -issuer -dates -ext subjectAltName
# 证书与私钥公钥指纹必须相同
openssl x509 -in fullchain.pem -pubkey -noout | \
openssl pkey -pubin -outform der | openssl sha256
openssl pkey -in privkey.pem -pubout -outform der | openssl sha256
当前 Chart 的关键值如下:network.publicAddress 派生主机名,network.domains.protocol=https 更新应用侧公开 URL,tls.secretName 使 Gateway 生成 websecure:HTTPS:8443 listener,gateway.redirectHttpToHttps=true 才会把 80 永久跳转到 443。Traefik Service 对外仍暴露 80/443,容器和 Gateway 内部端口为 8000/8443。
16.4 推荐路径:客户证书或站点 CA(适合离线交付)
完全离线或不允许 ACME 出网的站点,推荐先由客户 PKI 签发证书,再创建 Kubernetes TLS Secret。先创建 Secret,再切换 Helm,避免 Gateway 引用不存在的证书。
# 1. 创建或更新 TLS Secret;fullchain.pem 应包含完整链
kubectl -n nanocloud create secret tls tls-nanocloud \
--cert=fullchain.pem --key=privkey.pem \
--dry-run=client -o yaml | kubectl apply -f -
kubectl -n nanocloud get secret tls-nanocloud \
-o jsonpath='{.type}{"\n"}'
# 2. 在 gitignored 的 Site Contract 中加入以下内容
umask 077
vi /root/nanocloud-preserve/site-k3s-ha.yaml
network:
publicAddress: nano.x2robot.com
domains:
protocol: https
tls:
secretName: tls-nanocloud
certManager:
enabled: false
create: false
gateway:
redirectHttpToHttps: true
已有环境可在维护窗口执行 Helm 升级。--reuse-values 保留 release.lock 注入的站点 Registry 与 digest;不要只用一组零散 --set 替代现场 Site Contract。
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
export PKG=/opt/nanocloud/nanocloud-0.4.18-amd64
"$PKG/bin/helm" upgrade nanocloud "$PKG/chart" \
-n nanocloud --reuse-values \
-f /root/nanocloud-preserve/site-k3s-ha.yaml \
--wait --timeout 30m
kubectl -n nanocloud get gateway nanocloud \
-o jsonpath='{range .spec.listeners[*]}{.name}{":"}{.protocol}{":"}{.port}{"\n"}{end}'
16.5 cert-manager 自动签发的限制
Chart 可以通过 tls.certManager.enabled=true 创建多 SAN Certificate,并引用指定 Issuer。但 0.4.18 的 K3s 离线底座仅安装了 cert-manager v1.15.3,当前 controller 没有 --enable-gateway-api;同时 Chart Traefik 明确设置 --providers.kubernetesingress=false,Gateway 的 HTTP listener 端口为 8000,而 cert-manager v1.15 HTTP-01 Gateway solver 要求启用 GatewayAPI feature gate 并匹配 80 端口 listener。因此当前 K3s profile 不应直接使用默认 ClusterIssuer/letsencrypt 假设。
如需自动签发,优先使用客户 DNS 提供商支持的 ACME DNS-01 ClusterIssuer;它不依赖 HTTP listener,但需要 DNS API 凭据和访问 ACME 服务的出网能力。Issuer Ready 后再配置:
network:
publicAddress: nano.x2robot.com
domains:
protocol: https
tls:
secretName: tls-nanocloud
certManager:
enabled: true
issuerRef:
kind: ClusterIssuer
name: letsencrypt-dns01
group: cert-manager.io
create: false
gateway:
redirectHttpToHttps: false
# 第一阶段:签发期间保留 HTTP,不启用强制跳转
kubectl wait --for=condition=Ready \
clusterissuer/letsencrypt-dns01 --timeout=120s
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
export PKG=/opt/nanocloud/nanocloud-0.4.18-amd64
"$PKG/bin/helm" upgrade nanocloud "$PKG/chart" \
-n nanocloud --reuse-values -f /tmp/https-dns01.yaml \
--wait --timeout 30m
kubectl -n nanocloud wait --for=condition=Ready \
certificate/tls-nanocloud --timeout=10m
kubectl -n nanocloud describe certificate tls-nanocloud
# 第二阶段:证书 Ready 后再开启 80 -> 443 跳转
"$PKG/bin/helm" upgrade nanocloud "$PKG/chart" \
-n nanocloud --reuse-values \
--set gateway.redirectHttpToHttps=true \
--wait --timeout 30m
如坚持使用 HTTP-01,必须先在底座中持久化 cert-manager v1.15 的 --enable-gateway-api,并调整 Gateway/Service 端口契约后完成独立演练;不要用一次性的 Deployment patch 代替交付包修复。cert-manager v1.15 的 Gateway API solver 要求见 官方 HTTP-01 文档。
16.6 HTTPS 验收与回退
# 资源状态
kubectl -n nanocloud get gateway,httproute,certificate
kubectl -n nanocloud get secret tls-nanocloud \
-o jsonpath='{.type}{"\n"}'
# HTTP 应跳转到 HTTPS
curl -sS -D - -o /dev/null \
http://platform.nano.x2robot.com/
# HTTPS 验收不能使用 -k
for host in platform admin api oss minio-console airflow prefect; do
curl -sS -o /dev/null -w "$host=%{http_code}\n" \
"https://${host}.nano.x2robot.com/"
done
# 校验证书链、有效期与 SAN
echo | openssl s_client \
-connect platform.nano.x2robot.com:443 \
-servername platform.nano.x2robot.com 2>/dev/null | \
openssl x509 -noout -subject -issuer -dates -ext subjectAltName
# 查看 Helm 历史;失败时回退到切换 HTTPS 前的 revision
"$PKG/bin/helm" history nanocloud -n nanocloud
"$PKG/bin/helm" rollback nanocloud \
-n nanocloud --wait --timeout 30m
回退 Helm 后还应把 Site Contract 的 network.domains.protocol 恢复为 http,并确认 Gateway 只剩 web listener。TLS Secret 可暂时保留以便重新启用;确认不再使用且已有可恢复副本后,才允许删除。
16.7 MinIO Console 401、hostAliases 修复与升级窗口
2026-08-10 在 39.108.97.7 的 HTTP 环境中,访问 POST /api/v1/login 返回 401 Unauthorized。该现象不是账号密码错误:使用 nanocloud-credentials 中同一组 root 凭据直接访问 MinIO API 成功,但 Console 登录仍返回 invalid Login。
**根因。**内嵌 MinIO 同时存在内部地址和公开地址。业务服务使用 infra.minio.endpoint=minio:9000;Console 使用 MINIO_SERVER_URL=http://oss.nano.x2robot.com,以保证浏览器重定向和公开对象地址保持为外部可达域名。问题发生时,MinIO Pod 把该公开域名解析到公网 IP 39.108.97.7,Pod 内回环访问公网 80 端口被拒绝:
Get "http://oss.nano.x2robot.com/.../?location=":
dial tcp 39.108.97.7:80: connect: connection refused
因此不能简单把 MINIO_SERVER_URL 改成 http://minio:9000。正确模型是保留公开 Host,同时让 MinIO Pod 内部把公开域名解析到 Traefik Service ClusterIP:
外部客户端:oss.nano.x2robot.com -> 公网入口 -> Traefik -> minio:9000
MinIO Pod: oss.nano.x2robot.com -> Traefik ClusterIP -> minio:9000
47 环境对照。47.107.155.43 使用 HTTPS,Chart 已自动把 oss.nanocloud-cluster.x2robot.cn 和 minio-console.nanocloud-cluster.x2robot.cn 映射到动态查询得到的 Traefik ClusterIP,所以 Console 登录返回 204。39 环境使用 HTTP,而旧模板只有 protocol=https 才执行同一自动注入逻辑,导致 HTTP 路径遗漏。
Chart 修复。chart/templates/infra/minio.yaml 已移除仅 HTTPS 生效的条件。只要启用 Chart Traefik,HTTP/HTTPS 都通过 Helm lookup 查询 service/traefik 的 ClusterIP,并自动注入公开 OSS 域名;不得写死节点 IP或 10.43.x.x。显式 infra.minio.hostAliases 仍优先,配置空数组可关闭自动注入。
hostAliases:
- ip: ""
hostnames:
- "oss.nano.x2robot.com"
- "minio-console.nano.x2robot.com"
39 环境使用修复后的 Chart 执行 --reuse-values 升级,server-side dry-run 与运行态均解析为当前 Traefik ClusterIP。验收命令如下,命令不会输出 Secret 值:
# 验证动态映射
kubectl get pod minio-0 -n nanocloud \
-o jsonpath='{.spec.hostAliases}{"\n"}'
# 验证 Console 首页和路由
curl -sS -o /dev/null -w '%{http_code}\n' \
http://minio-console.nano.x2robot.com/
kubectl get httproute nanocloud-minio-console -n nanocloud
# 在 MinIO Pod 内验证公开 OSS 域名确实可以完成 API 认证
# 账号密码从 Secret 读取到当前 shell,禁止 echo 或粘贴到工单
u=$(kubectl get secret nanocloud-credentials -n nanocloud \
-o jsonpath='{.data.minio-root-user}' | base64 -d)
p=$(kubectl get secret nanocloud-credentials -n nanocloud \
-o jsonpath='{.data.minio-root-password}' | base64 -d)
kubectl exec -n nanocloud minio-0 -- \
mc alias set publiccheck http://oss.nano.x2robot.com "$u" "$p"
kubectl exec -n nanocloud minio-0 -- mc admin info publiccheck >/dev/null
unset u p
| 验收项 | 39 环境结果 |
|---|---|
| Helm | revision 4,deployed |
| Console 首页 | 连续返回 200 |
| Console 登录 | 当前 root 凭据返回 204 |
| 公开 OSS 域名回连 | MinIO Pod 内 mc admin info 成功 |
| 集群状态 | 三节点 Ready,无异常 Pod |
| 日志回传 | MinIO 恢复后,三节点 Fluent Bit 持续输出 Successfully uploaded object |
**升级窗口说明:**MinIO 为单副本 StatefulSet,并使用 Longhorn RWO 卷。修改 PodSpec 会重建 MinIO;旧卷解除、新节点重新挂载以及 Endpoint 恢复期间,Console 曾出现约 15 秒
503 Service Unavailable,日志 Agent 同期短暂出现 S3 上传失败,随后自动重试恢复。现场变更应安排维护窗口,且验收必须同时检查 Console、MinIO Endpoint 和日志对象持续增长。
**首装边界。**Helm 首次安装第一次渲染时,service/traefik 可能尚不存在,lookup 无法获得 ClusterIP;本次闭环验证的是“已有 Traefik Service 的升级路径”。正式宣告冷安装闭环前,还需让安装器在 Traefik Service 创建后执行一次幂等 Helm 收敛,或提供无需二次渲染的内部解析方案。不得通过写死 ClusterIP 规避该问题。
17. 2026-08-12:K3s HA、目标网段、HTTPS 与 RustFS RC 实测
测试环境为 root@39.108.97.7(n1:172.16.10.241,n2:172.16.10.242,n3:172.16.10.243),K3s v1.30.2+k3s1。变更前已创建 etcd 快照并导出 Kubernetes/Helm 资源。三节点阶段完成了 HA、目标网段、HTTPS 与 RustFS RC 的专项验收;随后按验证目标收敛为单控制面,并恢复与原三节点环境一致的完整业务及 Infra 组件,唯一对象存储变更为 rustfs/rustfs:rc。最终完整栈结果见 17.5。
17.1 关键决策
- 用户输入的 Pod CIDR 与 Service CIDR 都是
10.10.6.0/24,Kubernetes 不允许重叠;最终采用 Pod10.10.6.0/24、Service10.10.7.0/24。 - Pod
/24按每节点/26划分,max-pods=60;该配置最多支持 4 个节点。 - 控制面使用 3 个 embedded-etcd server;旧
joinNodes继续作为 agent 兼容别名。 - 内嵌对象存储切换为
rustfs/rustfs:rc,但保留minioService、Secret key、PVCdata-minio-0、端口和初始化脚本契约。
17.2 实测结果
| 检查项 | 结果 |
|---|---|
| 控制面 | n1/n2/n3 均为 control-plane,etcd,master,三个本地 API 的 etcd ready 均通过 |
| Pod CIDR | n1=10.10.6.0/26,n2=10.10.6.64/26,n3=10.10.6.128/26;实际 Pod 获得 10.10.6.x |
| Service CIDR | kubernetes=10.10.7.1、kube-dns=10.10.7.10,RustFS 与 HTTPS 验收 Service 也获得 10.10.7.x |
| HA 故障注入 | 停止 n1 后 2 秒内 n2 API Ready,etcd ok,并成功写入 ConfigMap;恢复 n1 后 3 个 etcd server 全部 Ready |
| RustFS RC | minio-0 Ready;原 init-buckets.sh 成功完成建桶、版本化、noncurrent 7 天 ILM、应用用户和 readwrite policy |
| S3 应用账号 | 以旧 Secret key 对应的应用 AK/SK 完成 PUT、GET、stat,内容一致并返回有效 VersionID |
| HTTPS | HTTP 返回 308,HTTPS health 返回 200;证书 SAN 覆盖 nano.x2robot.com、*.nano.x2robot.com 与 39.108.97.7 |
17.3 兼容与限制
- 本地同卷演练已验证“旧 MinIO 写对象 → 同一卷启动 RustFS →
mc cat内容一致”;原 mc 初始化逻辑也完整通过。 - RustFS 以 UID/GID 10001 非 root 运行;Chart 通过
fsGroup=10001与OnRootMismatch兼容原 PVC 权限。 - MinIO 未加密的
xl.meta数据属于本次兼容范围;MinIO SSE 加密对象不在兼容承诺内,存在 SSE 时必须先做独立迁移演练。 rctag 只用于选择版本;正式离线包必须按架构锁定 platform manifest digest,不能直接拿多架构 index digest 替代。- 本轮公网 IP 上可用的 VIP 候选地址已被占用,因此控制面故障测试使用 n2/n3 的直接 API 地址完成;生产现场仍应分配未占用 VIP,并把它加入所有 server 的 TLS SAN。
17.4 代码与回归
实现分支:codex/k3s-ha-rustfs-rc。本地发布门禁结果为 87 passed / 0 failed;standalone、k3s-ha、k8s-bare、ACK、EKS、TKE 六个 profile 均可渲染,k3s-ha Helm lint 通过。仓库运行手册已补充 HA/CIDR、离线自签 HTTPS 与 RustFS/MinIO 兼容迁移说明。
17.5 单节点完整业务栈最终验收(2026-08-12)
最终环境保留 n1 单控制面,n2、n3 的 K3s 均为 disabled/inactive。组件集合与原三节点部署保持一致,存储类因单节点验证改用 local-path;RustFS 继续复用原数据目录并绑定 20Gi Retain PV。
| 检查项 | 最终结果 |
|---|---|
| 集群与网段 | 仅 nanocloud-n1 Ready;Pod CIDR=10.10.6.0/24,Service CIDR=10.10.7.0/24,Kubernetes Service IP=10.10.7.1 |
| Helm | nanocloud-0.4.22,revision 5,状态 deployed |
| 业务与 Gateway | 23 个 Deployment 全部 1/1:核心四服务、分析中心、Acquisition、Airflow 三组件、Data Hub、FlowForge 四类 Worker、Artifact Writer、Prefect、Connect、APISIX、Traefik 与两个前端均 Ready |
| Infra 与日志 | 7 个 StatefulSet 全部 1/1:MySQL、PostgreSQL、Redis、RustFS、EMQX、Kafka、ClickHouse;日志 DaemonSet 1/1 |
| 镜像来源 | 完整 Helm 渲染共 28 个镜像,27 个来自深圳 ACR;唯一例外严格为 rustfs/rustfs:rc。运行时资源复核无 Docker Hub 残留 |
| RustFS S3 | 使用原业务 AK/SK 完成 PUT、GET、stat、delete;六个业务桶可见,x2robot-cloud 版本化启用 |
| CDC | MySQL source、PostgreSQL source、NDC ClickHouse sink、ALS ClickHouse sink 四个 Connector 及全部 task 均为 RUNNING |
| Gateway HTTP | Gateway PROGRAMMED=True;platform、admin、airflow、prefect 返回 200,OSS/Console 匿名访问返回 403;公网 39.108.97.7:80 按 Host 路由返回 200 |
| 存储 | 7 个 PVC 全部 Bound;RustFS 的 data-minio-0 保留原名称和兼容契约 |
**HTTPS 边界。**同一测试日已在三节点专项阶段验证 HTTP 308 跳转与 HTTPS health 200。最终完整栈为了与原三节点站点参数保持一致,恢复为 HTTP;因此本次完整业务恢复不重复启用 TLS,但 HTTPS Chart 配置与专项验收结论仍有效。
**修复记录。**正式离线包中的旧 Data Hub 镜像缺少当前 Chart 的 caption_no_ai_direct_label 参数,旧 Connect 镜像缺少 OffsetVersion SMT。已从当前源码构建 amd64 兼容镜像并推送 ACR,定时任务随后成功,四个 CDC Connector 注册和就绪检查全部通过。
现场切换前备份保存在 /var/backups/nanocloud-rustfs-full-switch-20260812-170448;部署值和 Chart 位于 /opt/nanocloud-rustfs-validation-20260812。
18. 2026-08-12:最终交付契约(默认三节点与 RustFS/rc 离线)
本节记录当前分支的最终交付口径,与第 17 章的历史现场实测区分。本节变更仅在本地仓库完成,未重建、升级或修改 39.108.97.7,该环境继续保留给现有测试人员使用。
18.1 三节点默认与 HA 开关
k3s-ha profile 继续部署三台机器,但默认 controlPlaneHA=false:1 个 control-plane/embedded-etcd server 加 2 个 worker agent。Site Contract 必须恰好给出两个 agentNodes,安装器不会把缺失节点列表降级为单节点。
需要开启控制面 HA 时,显式设置 controlPlaneHA=true,把同两个 IP 移入 serverNodes 并清空 agentNodes。首节点始终通过 --cluster-init 创建 embedded-etcd,因此可以后续把已有 agent drain 并转换为 server,无需重建首节点。未开开关却配置 server、默认 worker 数量不是 2、HA server 数量不是 2 或 HA 同时配置 agent,都会在 SSH 与集群变更前失败。
18.2 默认 CIDR
部署脚本默认不传 --cluster-cidr 和 --service-cidr,由 K3s 使用内建默认 CIDR。10.10.6.0/24 与 10.10.7.0/24 只作为客户 Site Contract 示例,不是产品默认配置。
18.3 RustFS / rc 资源与离线镜像
| 项目 | 最终契约 |
|---|---|
| 服务端镜像 | rustfs/rustfs:rc |
| 管理工具镜像 | rustfs/rc:v0.1.31 |
| Kubernetes 资源 | StatefulSet/Service/PVC/Secret/初始化 Job 全部使用 rustfs 命名,不保留旧 MinIO 对象名 |
| 离线包 | 两个镜像都必须按目标架构进入镜像归档、images/SHA256SUMS 与 release.lock |
| 现场拉取 | 安装器先推送到自建 Registry,再把所有运行引用渲染为 /rustfs/...@sha256: |
离线门禁会检查两项固定原始引用、归档 checksum、锁定 digest、Helm 全量渲染的现场 Registry 前缀,以及 release.lock 中每个 digest 在现场 Registry 的可访问性。运行中不得直接从 Docker Hub 或其他公网 Registry 拉取 RustFS/rc。
18.4 本地回归结果
发布包静态回归为 88 passed / 0 failed;standalone、k3s-ha、k8s-bare、ACK、EKS、TKE 六个 profile 全部渲染通过,Helm lint 通过。拓扑负例已覆盖:未显式开启 HA 的 serverNodes、默认仅一个 agent、HA 同时带 agent 均会被拒绝。
带 Docker 的补充自检完成 87 项后,最后一项因构建机专用阿里云 VPC ACR 授权端点连接超时失败;失败镜像为 image-sz-registry-vpc.cn-shenzhen.cr.aliyuncs.com/nanocloud/docker:27-cli,不涉及 RustFS/rc 或现场自建 Registry 的代码契约。
19. 2026-08-17:0.4.33 单节点与三节点离线部署演练
本节记录在专属测试环境 39.108.97.7 完成的 0.4.33 实际部署。演练依次完成旧环境彻底清场、单节点 standalone 安装与验收、再次清场,以及默认 1 控制面 + 2 工作节点的 k3s-ha 安装与验收。结论来自安装日志、Kubernetes、Helm、Longhorn、现场 Registry 和入口请求。
19.1 版本包与 OSS 物料
| 项目 | 实测结果 |
|---|---|
| 版本 | 0.4.33 |
| OSS 交付包 | oss://zbl-nanocloud/releases/0.4.33/nanocloud-0.4.33-amd64.tar.gz |
| 包大小 | 5,877,236,830 bytes |
| 包 SHA256 | 253da0b9b0456a95f4ff9971768148e813ba3014160320c1f3a7c67f0ff7e230,目标机复核通过 |
| 现场镜像目录 | /srv/nanocloud/mysql-images |
| 现场镜像 OSS | oss://zbl-nanocloud/releases/0.4.33/manual-images/ |
19.2 四个 MySQL 相关现场镜像
四个归档不进入主离线包,部署前下载到安装器指定目录。四个 tar 均通过 SHA256SUMS 和 crane validate --tarball;OSS 对象带 x-oss-meta-sha256 元数据,上传后已逐个回读核对。
| 文件 | 大小 | SHA256 |
|---|---|---|
nanocloud_db-migrator_1.2.0.tar | 81,202,688 | 91bf6a178384b4651c0ebc1910a7331de3d593e8c6112f4418ef609d3c86ac0e |
nanocloud_backup-tools_1.0.0.tar | 57,745,408 | 01299bbdb4b4d942dc423d8083e6714eea8ef00c5395c4c8ebf73854f5f5827d |
mysql_8.0.tar | 233,600,512 | 3cda3048ee8600101936696681f4b398d0d5106ac8329a4393f7c838109ac806 |
container-registry.oracle.com_mysql_community-router_8.4.6.tar | 93,329,408 | 0fa8833ee7ffcf101240272e85cfd96a60de7dbfdb888b219be0b958039f4717 |
安装时统一传入 --manual-images-dir /srv/nanocloud/mysql-images。安装器将常规归档和四个现场归档一起推送到 172.16.10.241:30500,再以现场 Registry 返回的 digest 生成最终 Helm 覆盖。
19.3 清场与单节点部署
清场先卸两个 agent,再卸首节点 server;同时删除旧业务、Registry、Longhorn 数据、K3s/Kubelet 数据目录和残留挂载。最终三台机器的 K3s 服务均为 inactive,旧目录及挂载不存在。
bash scripts/install.sh \
--profile standalone \
--airgap \
--values /root/nanocloud-preserve/site-standalone-0.4.33.yaml \
--manual-images-dir /srv/nanocloud/mysql-images
单节点安装一次完成:站点 Registry 共推送 48 个镜像,默认 Registry endpoint 已禁用,全部工作负载 Ready,版本账本为 0.4.33,平台 HTTP 请求返回 200。单节点验收完成后再次执行完整清场,为多节点首装提供干净环境。
19.4 三节点部署与容量修正
bash scripts/install.sh \
--profile k3s-ha \
--airgap \
--values /root/nanocloud-preserve/site-k3s-ha-0.4.33.yaml \
--manual-images-dir /srv/nanocloud/mysql-images
节点为 172.16.10.241、172.16.10.242、172.16.10.243。本次 controlPlaneHA=false:n1 是唯一 control-plane/embedded-etcd,n2、n3 是 worker agent;k3s-ha 在这里表示多节点工作负载和 Longhorn 副本,不表示控制面 HA。
首次按默认 PVC 安装时,RustFS 100Gi 因 insufficient storage 失败;只把 RustFS 缩到 20Gi 后仍失败。原因是 0.4.33 默认业务 PVC 请求总量约 115Gi,2 副本需要约 230Gi,而三台约 99Gi 系统盘扣除 Longhorn 保留空间后的可调度容量不足。
persistence:
mysql: { size: 10Gi }
postgres: { size: 5Gi }
redis: { size: 2Gi }
rustfs: { size: 10Gi }
emqx: { size: 2Gi }
kafka: { size: 2Gi }
clickhouse: { size: 10Gi }
airflow: { size: 5Gi }
最终测试 overlay 的逻辑请求总量为 46Gi,2 副本约占 92Gi。该配置仅用于小盘实验,不是新的生产默认值;生产必须通过增加独立数据盘和容量预算解决。
19.5 最终验收
| 检查项 | 终态 |
|---|---|
| 节点 | 3/3 Ready;n1 control-plane/etcd,n2、n3 worker |
| 工作负载 | 全部 Deployment、StatefulSet 达到期望副本;失败 Pod 为 0 |
| Longhorn | 8 个 volume 均 attached/healthy,每卷 2 副本 |
| 镜像来源 | nanocloud、cert-manager、longhorn-system 的受管镜像均使用 172.16.10.241:30500 前缀 |
| 节点 mirror | 三台节点均配置现场 Registry mirror,并设置 disable-default-registry-endpoint: true |
| Helm / 账本 | Revision 1、deployed;版本账本为 0.4.33 |
| 入口 | https://platform.39.108.97.7 返回 200;HTTP 返回 301;使用现场自签名证书 |
| 磁盘占用 | 最终约 n1 64%、n2 27%、n3 30% |
19.6 离线边界与证据
交付包和四个现场镜像均在安装开始前准备完成,安装全程使用 --airgap、现场 Registry、节点 mirror 和受管镜像前缀硬门禁。本次没有通过防火墙或路由策略做公网出口隔离测试,因此结论是“离线物料路径与离线安装链通过”,不能表述为“严格无公网出口已证明”。如项目要求严格断网证据,需在安装窗口额外执行出口阻断和访问审计。
本次测试 Site Contract 关闭备份,因此不能把工作负载 Ready 视为备份闭环通过。单节点日志为 /root/nanocloud-0.4.33-stage/install-standalone.log,多节点最终日志为 /root/nanocloud-0.4.33-stage/install-k3s-ha-final-capacity.log;Site Contract 分别保存在 /root/nanocloud-preserve/site-standalone-0.4.33.yaml 和 /root/nanocloud-preserve/site-k3s-ha-0.4.33.yaml。
19.7 演练结论
0.4.33 已在同一专属环境完成“彻底清场 → 单节点离线安装与验收 → 再次清场 → 三节点离线安装 → Longhorn 容量修正 → 工作负载、镜像门禁、存储副本和入口验收”的闭环。阻塞点不是镜像或安装器流程,而是默认 PVC 与小盘环境的物理容量不匹配;正式交付必须在首装前完成容量计算。
20. 2026-08-15:Longhorn SSD/HDD 分层存储交付契约
本节记录当前本地代码已经实现并通过静态回归的分层存储方案。K8s 轨当前对象存储组件是 RustFS,不是 MinIO;因此现场所称“MinIO 使用机械盘”在当前交付中对应“RustFS 数据卷使用机械盘”。本节变更尚未在 39.108.97.7 执行存储迁移,该环境继续使用既有未分层 Longhorn 磁盘和 PVC。
20.1 存储分配模型
| 对象 | StorageClass | 磁盘池 |
|---|---|---|
| MySQL、PostgreSQL、ClickHouse、Redis、EMQX、Kafka | longhorn-ssd | Longhorn 标签为 ssd 的路径 |
| RustFS 数据卷 | longhorn-hdd | Longhorn 标签为 hdd 的机械盘 RAID/LVM 挂载路径 |
nanocloud-registry | local-path | 保持部署引导盘,不迁入 Longhorn |
两个自定义 StorageClass 都使用 2 副本、Retain、Immediate 和 dataLocality=best-effort。启用分层后,longhorn-ssd 是集群默认类,RustFS 必须显式指定 longhorn-hdd。未启用分层的现有 Site Contract 继续使用原 longhorn,兼容行为不变。
20.2 自动化边界
| 安装器自动完成 | 现场必须提前完成 |
|---|---|
| 读取 Site Contract 中的 SSD/HDD 路径 | 确认物理盘、RAID 卡、逻辑盘和 Linux 设备的实际对应关系 |
为每个 Ready 节点注册名为 ssd、hdd 的 Longhorn 磁盘并添加同名标签 | 机械盘完成 RAID/LVM、ext4 或 XFS 格式化,并持久挂载到统一路径 |
等待两个 Longhorn 磁盘均达到 Ready=True、Schedulable=True | 把挂载写入 /etc/fstab,逐节点重启或等价验证后确认仍然存在 |
创建 longhorn-ssd、longhorn-hdd 并设置默认类 | 确认 SSD 与 HDD 路径实际位于不同块设备/逻辑卷,并完成容量与性能验收 |
**安装器不会自动识别磁盘介质,也不会分区、格式化或挂载磁盘。**如果 /mnt/longhorn-hdd 没有真正挂载,Longhorn 可能在系统 SSD 上创建同名目录并显示 Ready。因此逐节点 findmnt 是现场硬门禁,不能只看 Longhorn 状态。
服务器配置截图只能证明采购规格中包含 SSD、SATA HDD 和 RAID 卡,不能证明现场 Linux 设备名、RAID 级别、逻辑卷、文件系统和挂载点已经配置完成,因此安装器不能基于截图或设备名称安全推断磁盘类型。
20.3 Site Contract
foundation:
longhorn:
tiering:
enabled: true
ssdPath: /var/lib/longhorn
hddPath: /mnt/longhorn-hdd
platform:
storageClass: longhorn-ssd
persistence:
rustfs:
storageClass: longhorn-hdd
size: 2Ti # 示例,按现场容量预算填写
安装前 preflight 会检查:profile 必须为 k3s-ha;两个路径必须是不同的绝对路径且不能包含空格;SKIP_LONGHORN=1 不能与分层模式并用;平台默认类必须是 longhorn-ssd;RustFS 必须显式使用 longhorn-hdd。同名环境变量 LONGHORN_TIERING_ENABLED、LONGHORN_SSD_PATH、LONGHORN_HDD_PATH 可覆盖安装期路径。
20.4 宿主机安装前验收
以下命令必须在每个会运行 Longhorn 的节点上执行:
findmnt -T /var/lib/longhorn -no TARGET,SOURCE,FSTYPE
findmnt -T /mnt/longhorn-hdd -no TARGET,SOURCE,FSTYPE
df -hT /var/lib/longhorn /mnt/longhorn-hdd
findmnt -T /var/lib/longhorn -no SOURCE
findmnt -T /mnt/longhorn-hdd -no SOURCE
两条 SOURCE 必须不同,文件系统必须为 ext4 或 XFS。HDD 路径应是 RAID/LVM 提供的受管理文件系统;Longhorn 不会把一个卷自动分片到 12 块裸 HDD。当前 2 副本契约至少需要 2 个 Ready 节点,生产建议 3 个节点;SSD 池和 HDD 池要分别按 PVC request × 2 副本 + 保留空间 + 快照 + 增长率计算容量,不能互相借用可调度容量。
20.5 安装后验收
kubectl get storageclass longhorn-ssd longhorn-hdd -o yaml
kubectl -n longhorn-system get nodes.longhorn.io \
-o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{range $n,$d := .spec.disks}{" "}{$n}{" path="}{$d.path}{" tags="}{$d.tags}{"\n"}{end}{end}'
kubectl -n nanocloud get pvc \
-o custom-columns=NAME:.metadata.name,CLASS:.spec.storageClassName,STATUS:.status.phase
预期 longhorn-ssd 的 diskSelector=ssd 且为默认类,longhorn-hdd 的 diskSelector=hdd;data-rustfs-0 使用 longhorn-hdd,其余 NanoCloud 嵌入式基础组件 PVC 使用 longhorn-ssd。所有 PVC 应为 Bound,对应 Longhorn volume 应为 healthy、2 副本。
20.6 已有环境迁移边界
PVC 的 storageClassName 不能按本方案原地修改。Longhorn 默认磁盘注解也只对尚无既有磁盘的 Longhorn Node 生效;安装器发现已有磁盘布局不完全等于配置的 ssd/hdd 路径和标签时会拒绝自动迁移。
已有环境必须单独执行数据迁移评审:先完成可恢复备份并停止写入,再创建目标 StorageClass/PVC、恢复数据和逐组件验收。不能把修改 values 后执行 Helm upgrade 当作数据迁移方案。当前 39.108.97.7 正属于已有未分层环境,所以本次没有对其执行存储切换。
20.7 当前代码与回归事实
本地代码已新增两类 StorageClass、RustFS 独立 StorageClass 覆盖、Site Contract schema、安装前一致性检查、Longhorn 节点磁盘标签配置、已有布局拒绝逻辑和离线回归用例。实际 Helm 渲染结果为:MySQL、PostgreSQL、ClickHouse、Redis、EMQX、Kafka 使用 longhorn-ssd,RustFS 使用 longhorn-hdd。
当前离线交付静态回归结果为 103 passed / 0 failed;Bash 语法、JSON Schema、Helm lint/template、无 PyYAML 的离线解析回退均通过。以上是本地代码验证结果,不等同于目标服务器真实 HDD 挂载、故障重建和负载性能验收。
Longhorn 能力依据:Storage Tags、Multiple Disks、StorageClass Parameters。
21. zbl 站点:0.4.33 三节点离线部署与运维基线(2026-08-18)
**当前结论:**NanoCloud 0.4.33 已在 10.10.3.11 / 10.10.3.12 / 10.10.3.13 完成三节点离线部署,Helm revision 16 为 deployed。所有 Deployment/StatefulSet Ready,RustFS 3/3,离线镜像前缀不变量通过。
本节记录 zbl.sysxlzx.xyz 当前站点。前文 39.108.97.7、172.16.10.x 等内容属于历史现场,不应与本节参数混用。
21.1 现场拓扑与生产配置
| 角色 | 主机名 | 内网 IP |
|---|---|---|
| K3s server / etcd、registry、平台入口 | nanocloud-node1 | 10.10.3.11 |
| K3s agent | nanocloud-node2 | 10.10.3.12 |
| K3s agent | nanocloud-node3 | 10.10.3.13 |
- 系统:Ubuntu 24.04.3 LTS、x86_64;每节点约 112 vCPU、62GiB 内存、930GiB NVMe 系统盘、88TB XFS 数据盘。
- 域名:
zbl.sysxlzx.xyz;DNS 上游为10.10.3.1。 - 时区:主机、Pod、日志、所有 CronJob 统一为
Asia/Shanghai。 - 默认登录用户为
user,使用 SSH 公钥;特权操作走sudo或su -。 - 正式站点文件:
/srv/nanocloud/site.yaml;离线包:/home/user/nanocloud-0.4.33-amd64;四个 MySQL 现场镜像:/srv/nanocloud/mysql-images。
cd /home/user/nanocloud-0.4.33-amd64
sudo env SKIP_K3S_JOIN=1 bash scripts/install.sh \
--profile k3s-ha \
--values /srv/nanocloud/site.yaml \
--skip-k3s \
--airgap \
--manual-images-dir /srv/nanocloud/mysql-images
现场升级必须继续使用以上完整离线入口;不得省略 --airgap 或人工镜像目录,也不得把生产拓扑配置放在 /tmp。
21.2 存储池设计
| 层级 | StorageClass / 路径 | 用途 |
|---|---|---|
| SSD | longhorn-ssd(默认) | MySQL、PostgreSQL、Redis、Kafka、EMQX、ClickHouse 等 infra |
| HDD | longhorn-hdd-single;/data/nanocloud/longhorn-hdd | 仅 RustFS |
| 本地盘 | local-path | K3s 底座和现场 registry,不作为业务默认持久化类 |
RustFS Pool 0 为三节点各 80Ti,原始容量 240Ti。三节点纠删码按约 2 个数据分片 + 1 个校验分片估算,业务可用容量约 160Ti,尚未扣除文件系统和运行水位预留。“80Ti 校验”是对象数据的等宽校验分片容量预算,不是单个独立校验文件。
RustFS PVC 使用 longhorn-hdd-single,由 RustFS 跨节点纠删码承担冗余,避免 Longhorn 多副本与应用层纠删码双重放大。
当前 StorageClass 列表中 local-path、longhorn、longhorn-hdd、longhorn-ssd、longhorn-static 及 RustFS 专用 longhorn-hdd-single 各有用途,不应仅因数量多而删除。
后续加盘与均衡
- 三节点先准备同规格磁盘、挂载点和 Longhorn HDD disk tag,确认容量可调度。
- 只在
/srv/nanocloud/site.yaml的infra.rustfs.additionalPools末尾追加 Pool;禁止删除、重排或编辑既有 Pool。 - 备份站点文件和
nanocloud-rustfs-pool-contract后,在维护窗口执行完整离线安装命令。 - 等待全部 RustFS StatefulSet、Pod、PVC 收敛,再做只读 Pool 检查。
- 显式启动 RustFS rebalance 并持续观察。加盘不会自动创建 Pool,也不能假设历史对象会自动均衡。
追加 Pool 会改变全部 RustFS Pod 的端点列表并触发滚动更新;旧 PVC 不迁移,但仍属于有影响升级,必须安排维护窗口并保留离机备份。
21.3 本环境备份
当前备份目标是同一 RustFS 集群中的 nanocloud-backup 桶,只证明导出、加密、上传和回读链路,不构成离机灾备。生产投运前必须切换到独立故障域的 S3/OSS/MinIO,并完成恢复演练。
- CronJob:
nanocloud-site-export,每日 02:00,timeZone=Asia/Shanghai,禁止并发。 - age 私钥仅保存在 node1 的
/srv/nanocloud/backup-keys/age-private.key,root-only;集群内只保存公钥 Secret。 - 手动验收 Job:
nanocloud-site-export-manual-20260818-1439,状态 Complete。 - backupId:
site-export-2026-08-18T1440+08:00。 - 验收覆盖 MySQL 3 库、PostgreSQL 5 库、ClickHouse 45 表、版本账本、记名不记值的密钥清单;age 加密后上传,远端回读 SHA-256 一致。
kubectl get cronjob nanocloud-site-export -n nanocloud -o wide
kubectl get configmap nanocloud-release -n nanocloud \
-o jsonpath='{.data.backup\.last}'; echo
kubectl get job nanocloud-site-export-manual-20260818-1439 -n nanocloud
禁止在终端、日志或文档中输出备份 AK/SK 和 age 私钥;Secret 只检查名称、键名、长度和引用关系。
21.4 临时监控服务
nanocloud-health-monitor 仅用于本次离线部署验收和正式监控上线前的过渡期人工巡检,不是生产监控服务。它复用离线包内 toolbox 镜像,不访问公网;K3s metrics-server 继续提供 kubectl top 的瞬时 CPU/内存。
- ClusterIP:
nanocloud-health-monitor:8080;端点:/healthz、/metrics。 - 指标:节点总数/Ready、异常 Pod、不可用 Deployment、未就绪 StatefulSet、未绑定 PVC、失败 Job、RustFS 期望/Ready、副本备份开关及最近成功时间。
- RBAC 仅允许资源 get/list 和读取
nanocloud-release账本;容器非 root,无宿主机挂载。
kubectl rollout status deployment/nanocloud-health-monitor -n nanocloud
kubectl exec -n nanocloud deployment/nanocloud-health-monitor -- \
wget -qO- http://127.0.0.1:8080/metrics
kubectl top nodes
kubectl top pods -n nanocloud --sort-by=memory
临时监控验收:节点 3/3 Ready、RustFS 3/3、不可用 Deployment/StatefulSet=0、未绑定 PVC=0、失败 Job=0,最近成功备份时间指标为 1787035223。该组件不保存历史曲线、不提供 Dashboard 或告警通知,不能作为生产监控验收依据;正式监控需将 Prometheus、Alertmanager、Grafana、日志组件及依赖镜像和 digest 纳入离线包后部署。
21.5 部署问题与解决方案复盘
| 问题 | 根因 | 解决方案 |
|---|---|---|
| DNS/NTP 初始不可用 | bond4 没有上游 DNS,timesyncd 无法解析 NTP | 三节点配置 10.10.3.1,恢复 DNS 与时间同步 |
| 浏览器提示证书不安全 | 私网入口使用内部 CA,终端未信任;公网 ACME 验证条件不成立 | 统一域名入口并向受管终端分发站点 CA;若需公网可信证书,先满足 ACME 可验证条件 |
| 升级拉取公网 PostgreSQL | 未使用完整离线入口,release.lock/registry 覆盖缺失 | 回滚后仅使用 install.sh --airgap |
| agent 拉取本地 registry 失败 | 三节点 mirror 配置不一致 | 三节点统一 registry mirror 并重启验收 |
| 四个 MySQL 镜像 digest 不一致 | 现场镜像不属于原 release.lock 基线 | 固定目录并由 --manual-images-dir 后置注入实际 digest |
| RustFS 单副本 SC 被预检拒绝 | 初版分层门禁只接受 longhorn-hdd | 将 longhorn-hdd-single 纳入契约并校验三节点 disk tag |
| StorageClass 更新失败 | Kubernetes StorageClass 字段不可变 | 对齐声明契约,不直接修改不可变对象 |
| 旧 Longhorn engine image 指向公网 | 历史零引用对象残留 | 核对引用数后清理旧对象,保持现场 registry 闭环 |
| RustFS 扩容缺少拓扑保护 | 原 Chart 只有单 Pool | 实现 append-only Pool 与集群内 Pool Contract,Pool 0 原位兼容 |
| 监控容器首次重启 | toolbox 的 BusyBox 未编译 httpd applet | 改用镜像已包含的 Python HTTP server,重新执行完整离线安装并验证 revision 16 |
投产前剩余风险
- 当前 K3s 只有 1 个 server/etcd,三节点是 1 控制面 + 2 agent,并非控制面高可用。
- 备份仍与业务 RustFS 同故障域,必须迁移到离机目标并完成恢复演练。
- 内部 CA 需要向浏览器终端分发信任。
- 当前只有临时健康检查;正式监控所需的历史存储、Grafana 和告警通知渠道尚未建设。