建设由 Ansible 与 Flux 管理的正式 OpenSandbox 集群 #83

Open
opened 2026-09-17 10:27:54 +00:00 by panxiao81 · 4 comments
Owner

背景

issue #47 的 backend 评估已经验证:privileged PVE LXC 中可以运行 k3s、Kata
Containers 与 Cloud Hypervisor;Kata 4.1.0 的 clh-runtime-rs 实际报告
memory.shared=true,guest 写入 1 GiB 时只增加一次 shared memory charge,没有重现
早期 private memfd 的 anon + shmem 双重记账。

下一步需要把 PoC 收敛成独立的正式 sandbox 集群,供 OpenSandbox、CI 和后续 AI
Agent workload 使用。正式环境不得依赖手工登录配置;所有持久状态必须能由 Ansible
与 Flux 从仓库重建,并能够检测实际节点状态与声明状态的偏差。

所有权边界

Ansible

  • 在 pve1/pve2 创建和维护 privileged LXC;不再由 Terraform 与 Ansible 共同管理同一
    LXC。
  • 声明 VMID、放置节点、hostname、MAC/IP、pve-rg-hdd rootfs、CPU、内存、swap、
    /dev/kvm、vhost、TUN、kmsg、AppArmor、capability、cgroup 与 mount 配置。
  • 管理 LXC 内 OS 基线、内部 CA、网络和软件包。
  • 管理 K3s 外部 PostgreSQL。数据库不能部署在由它支撑的 K3s 集群内部。
  • 通过固定版本的官方 k3s-io/k3s-ansible collection 安装和升级 K3s;配置文件权限
    使用 0600。
  • bootstrap 固定版本的 Flux controllers 和 sandbox root Kustomization。
  • 管理位于 Kubernetes 之外的 PostgreSQL exporter 等 systemd 服务。
  • 提供幂等 site.yml 与只读 verify.yml;普通重复执行不得产生变化。

Flux

  • 管理 Kata Containers、kata-clh-runtime-rs RuntimeClass。
  • 管理 SPIRE Agent、SPIFFE CSI Driver、ServiceAccount、RBAC 与 workload identity
    声明。
  • 管理 OpenSandbox operator/server、ci-pod 与 ci-vm Pools、API Service/Gateway。
  • 管理 vmagent、kube-state-metrics、集群内 exporters、ServiceScrape/PodScrape 与告警规则。
  • 通过独立 Kustomization 和 dependsOn 明确顺序;Kata 不能作为 OpenSandbox chart 的
    隐式依赖。

目录目标

infrastructure/sandbox-cluster/
  README.md
  ansible/
    requirements.yml
    inventory/
    site.yml
    verify.yml
    roles/

clusters/sandbox/
  flux-system/
  infrastructure/kata/
  infrastructure/spire-agent/
  infrastructure/monitoring/
  infrastructure/opensandbox-operator/
  workloads/opensandbox/

现有 infrastructure/kata-lxc-lab 只保留为历史 PoC 和已验证陷阱记录,不作为正式
环境的部署入口。

集群与数据库

  • 两个 LXC 分别位于 pve1、pve2;两个节点都作为 K3s server 和 workload node。
  • 使用外部 PostgreSQL,避免两节点 embedded etcd 拓扑。
  • 首期至少实现 primary + synchronous standby、复制状态监控和受控 promotion/failback
    runbook;不能仅安装 streaming replication 就宣称自动 HA。
  • K3s datastore credential 不提交 Git,由 Ansible 从 Bao 获取并写入 root-only 配置。
  • 节点地址必须由现有 DHCP/DNS IaC 固定,inventory 不接受运行后人工抄写的动态地址。
  • K3s API 稳定入口、PostgreSQL 写端点和 failover 策略需要在实现前明确记录。

SPIFFE/SPIRE

  • 复用现有 SPIRE Server 和 trust domain,不在 sandbox 集群建立第二套信任根。
  • homelab Flux 先为 sandbox 集群配置独立 PSAT cluster identity 和 server 侧授权;SPIRE
    Server 必须提供 sandbox 节点可访问的地址。
  • sandbox Flux 部署 Agent DaemonSet 与 CSI Driver。
  • ci-pod 和 ci-vm workload 必须取得自己的 SPIFFE identity,不得共享 OpenSandbox
    Server 或外层 runner 的身份。
  • identity 使用 repo/task 等稳定且具有权限意义的字段,不把一次性 job ID 放入
    SPIFFE ID。
  • OpenSandbox Pod 模板必须提供 Workload API;普通 Pod 和 Kata guest 都要验收。

监控

  • 复用 homelab 的中央 VictoriaMetrics/Grafana;sandbox 集群运行独立 vmagent并进行
    remote write。
  • 采集 K3s control plane、kubelet/cAdvisor、kube-state-metrics、node-exporter、
    PostgreSQL/复制延迟、SPIRE Agent/SVID、Kata、OpenSandbox API/Pool 与 LXC
    memory/swap/PSI/OOM。
  • 为 PostgreSQL 无可写 primary、复制中断、节点 NotReady、SPIRE agent 断连、SVID
    签发失败、sandbox 创建失败/超时、Pool 耗尽、memory pressure/OOM 建立告警。
  • 保留自动化回归检查,确认 Cloud Hypervisor
    vm.info.config.memory.shared=true。

部署顺序

  1. Ansible 创建两个 LXC并完成 OS、PostgreSQL、K3s。
  2. homelab Flux 配置 sandbox cluster 的 SPIRE Server attestation。
  3. Ansible bootstrap sandbox Flux。
  4. sandbox Flux 部署监控组件。
  5. sandbox Flux 部署 SPIRE Agent + CSI Driver并验收身份。
  6. sandbox Flux 部署 Kata并验证 KVM、guest kernel 和 memory.shared=true。
  7. sandbox Flux 部署 OpenSandbox 单一 API与两个 Pool。
  8. 完成 runc、Kata、监控和 SPIFFE 四条端到端 smoke test。
  9. 最后接入 CI scheduler/controller,不与集群基础设施同时切换。

验收标准

  • 从全新 LXC 开始,只运行 Ansible site.yml 和等待 Flux reconciliation 即可恢复环境。
  • 连续第二次 Ansible运行无非预期 change;Flux 全部 Ready且无持续 drift。
  • 实际 LXC 配置、存储、设备、K3s版本和节点标签与 inventory 一致。
  • PostgreSQL primary/standby、复制和受控切换验证通过。
  • 两个 K3s node Ready;任一节点维护时已有服务行为符合设计。
  • SPIRE Agent Ready;普通 Pod和 Kata guest 获得预期 SVID,错误身份无法取得授权。
  • vmagent 已将上述基础设施、身份与 sandbox 指标写入中央 VictoriaMetrics,关键告警可
    通过测试触发。
  • OpenSandbox 使用一套 Lifecycle API,通过 extensions.poolRef 选择 ci-pod 或
    ci-vm;Pool 可配置零预热并按需创建。
  • runc sandbox与 Kata CLH sandbox均能创建、执行和销毁;删除后无残留 VMM、Pod、
    credential 或临时数据。

运维原则

  • 手工 SSH 只允许用于诊断,不得成为部署或修复步骤;诊断得到的修复必须回写 IaC。
  • 不把 token、数据库密码或长期 workload credential 写入仓库、镜像或 cloud-init。
  • CHANGELOG.md 保持冻结;持久状态和 runbook 写入对应 README。

关联:#47、#34。

## 背景 issue #47 的 backend 评估已经验证:privileged PVE LXC 中可以运行 k3s、Kata Containers 与 Cloud Hypervisor;Kata 4.1.0 的 `clh-runtime-rs` 实际报告 `memory.shared=true`,guest 写入 1 GiB 时只增加一次 shared memory charge,没有重现 早期 private memfd 的 `anon + shmem` 双重记账。 下一步需要把 PoC 收敛成独立的正式 sandbox 集群,供 OpenSandbox、CI 和后续 AI Agent workload 使用。正式环境不得依赖手工登录配置;所有持久状态必须能由 Ansible 与 Flux 从仓库重建,并能够检测实际节点状态与声明状态的偏差。 ## 所有权边界 ### Ansible - 在 pve1/pve2 创建和维护 privileged LXC;不再由 Terraform 与 Ansible 共同管理同一 LXC。 - 声明 VMID、放置节点、hostname、MAC/IP、`pve-rg-hdd` rootfs、CPU、内存、swap、 `/dev/kvm`、vhost、TUN、kmsg、AppArmor、capability、cgroup 与 mount 配置。 - 管理 LXC 内 OS 基线、内部 CA、网络和软件包。 - 管理 K3s 外部 PostgreSQL。数据库不能部署在由它支撑的 K3s 集群内部。 - 通过固定版本的官方 `k3s-io/k3s-ansible` collection 安装和升级 K3s;配置文件权限 使用 `0600`。 - bootstrap 固定版本的 Flux controllers 和 sandbox root Kustomization。 - 管理位于 Kubernetes 之外的 PostgreSQL exporter 等 systemd 服务。 - 提供幂等 `site.yml` 与只读 `verify.yml`;普通重复执行不得产生变化。 ### Flux - 管理 Kata Containers、`kata-clh-runtime-rs` RuntimeClass。 - 管理 SPIRE Agent、SPIFFE CSI Driver、ServiceAccount、RBAC 与 workload identity 声明。 - 管理 OpenSandbox operator/server、`ci-pod` 与 `ci-vm` Pools、API Service/Gateway。 - 管理 vmagent、kube-state-metrics、集群内 exporters、ServiceScrape/PodScrape 与告警规则。 - 通过独立 Kustomization 和 `dependsOn` 明确顺序;Kata 不能作为 OpenSandbox chart 的 隐式依赖。 ## 目录目标 ```text infrastructure/sandbox-cluster/ README.md ansible/ requirements.yml inventory/ site.yml verify.yml roles/ clusters/sandbox/ flux-system/ infrastructure/kata/ infrastructure/spire-agent/ infrastructure/monitoring/ infrastructure/opensandbox-operator/ workloads/opensandbox/ ``` 现有 `infrastructure/kata-lxc-lab` 只保留为历史 PoC 和已验证陷阱记录,不作为正式 环境的部署入口。 ## 集群与数据库 - 两个 LXC 分别位于 pve1、pve2;两个节点都作为 K3s server 和 workload node。 - 使用外部 PostgreSQL,避免两节点 embedded etcd 拓扑。 - 首期至少实现 primary + synchronous standby、复制状态监控和受控 promotion/failback runbook;不能仅安装 streaming replication 就宣称自动 HA。 - K3s datastore credential 不提交 Git,由 Ansible 从 Bao 获取并写入 root-only 配置。 - 节点地址必须由现有 DHCP/DNS IaC 固定,inventory 不接受运行后人工抄写的动态地址。 - K3s API 稳定入口、PostgreSQL 写端点和 failover 策略需要在实现前明确记录。 ## SPIFFE/SPIRE - 复用现有 SPIRE Server 和 trust domain,不在 sandbox 集群建立第二套信任根。 - homelab Flux 先为 sandbox 集群配置独立 PSAT cluster identity 和 server 侧授权;SPIRE Server 必须提供 sandbox 节点可访问的地址。 - sandbox Flux 部署 Agent DaemonSet 与 CSI Driver。 - `ci-pod` 和 `ci-vm` workload 必须取得自己的 SPIFFE identity,不得共享 OpenSandbox Server 或外层 runner 的身份。 - identity 使用 repo/task 等稳定且具有权限意义的字段,不把一次性 job ID 放入 SPIFFE ID。 - OpenSandbox Pod 模板必须提供 Workload API;普通 Pod 和 Kata guest 都要验收。 ## 监控 - 复用 homelab 的中央 VictoriaMetrics/Grafana;sandbox 集群运行独立 vmagent并进行 remote write。 - 采集 K3s control plane、kubelet/cAdvisor、kube-state-metrics、node-exporter、 PostgreSQL/复制延迟、SPIRE Agent/SVID、Kata、OpenSandbox API/Pool 与 LXC memory/swap/PSI/OOM。 - 为 PostgreSQL 无可写 primary、复制中断、节点 NotReady、SPIRE agent 断连、SVID 签发失败、sandbox 创建失败/超时、Pool 耗尽、memory pressure/OOM 建立告警。 - 保留自动化回归检查,确认 Cloud Hypervisor `vm.info.config.memory.shared=true`。 ## 部署顺序 1. Ansible 创建两个 LXC并完成 OS、PostgreSQL、K3s。 2. homelab Flux 配置 sandbox cluster 的 SPIRE Server attestation。 3. Ansible bootstrap sandbox Flux。 4. sandbox Flux 部署监控组件。 5. sandbox Flux 部署 SPIRE Agent + CSI Driver并验收身份。 6. sandbox Flux 部署 Kata并验证 KVM、guest kernel 和 `memory.shared=true`。 7. sandbox Flux 部署 OpenSandbox 单一 API与两个 Pool。 8. 完成 runc、Kata、监控和 SPIFFE 四条端到端 smoke test。 9. 最后接入 CI scheduler/controller,不与集群基础设施同时切换。 ## 验收标准 - 从全新 LXC 开始,只运行 Ansible `site.yml` 和等待 Flux reconciliation 即可恢复环境。 - 连续第二次 Ansible运行无非预期 change;Flux 全部 Ready且无持续 drift。 - 实际 LXC 配置、存储、设备、K3s版本和节点标签与 inventory 一致。 - PostgreSQL primary/standby、复制和受控切换验证通过。 - 两个 K3s node Ready;任一节点维护时已有服务行为符合设计。 - SPIRE Agent Ready;普通 Pod和 Kata guest 获得预期 SVID,错误身份无法取得授权。 - vmagent 已将上述基础设施、身份与 sandbox 指标写入中央 VictoriaMetrics,关键告警可 通过测试触发。 - OpenSandbox 使用一套 Lifecycle API,通过 `extensions.poolRef` 选择 `ci-pod` 或 `ci-vm`;Pool 可配置零预热并按需创建。 - runc sandbox与 Kata CLH sandbox均能创建、执行和销毁;删除后无残留 VMM、Pod、 credential 或临时数据。 ## 运维原则 - 手工 SSH 只允许用于诊断,不得成为部署或修复步骤;诊断得到的修复必须回写 IaC。 - 不把 token、数据库密码或长期 workload credential 写入仓库、镜像或 cloud-init。 - `CHANGELOG.md` 保持冻结;持久状态和 runbook 写入对应 README。 关联:#47、#34。
Author
Owner

补充 2026-09-17 Kata block-backed CI storage PoC 结果:

  • Kata 4.1.0 clh-runtime-rs 设置 [runtime] emptydir_mode = "block-plain" 后,
    /var/lib/docker 是 guest /dev/vdb 上的 ext4,Docker 29.1.5 可使用原生
    overlay2,不再依赖 fuse-overlayfs。
  • Kata/Cloud Hypervisor 直接把 kubelet emptyDir 中的稀疏 disk.img 热插拔为 block
    device,host/LXC 没有使用 loop device。
  • 256 MiB + 2000 小文件的 BuildKit 冷构建:Kata 46 秒,同一 LXC/DRBD HDD 上的
    runc 对照 34 秒,Kata 约慢 35%。
  • overlay2 内 512 MiB fsync 写入:Kata 12 秒(40.5 MB/s),runc 10 秒
    (50.3 MB/s);一万个小文件两者均约 2 秒。
  • kind v0.27.0 / Kubernetes v1.32.2 可以运行。dockerd bootstrap 必须先在 Kata
    guest 内执行 mknod /dev/kmsg c 1 11,否则 kind node kubelet 因缺少
    /dev/kmsg 退出。修复后 kind 总创建时间 122 秒,等待阶段 23 秒 Ready。
  • emptyDir.sizeLimit: 8Gi 没有决定 block image 容量;12 GiB LXC rootfs 产生约
    12 GiB 稀疏 image。这是 Kata 已知限制,正式节点必须给 kubelet volume 目录使用
    独立且容量受控的文件系统。
  • 删除 Pod 后 disk.img、VMM 和 kind 容器全部回收,无 loop device 或 OOM 残留。

结论:Kata 不再因 virtio-fs/overlay2 问题直接淘汰。#83 的 ci-vm 应使用独立的
block-plain RuntimeClass,并把 overlay2、kind、backing-file 回收及性能阈值纳入
上线验收;不能使用默认 shared-fs RuntimeClass 承载镜像构建。

完整数据已写入 infrastructure/kata-lxc-lab/README.md。

补充 2026-09-17 Kata block-backed CI storage PoC 结果: - Kata 4.1.0 `clh-runtime-rs` 设置 `[runtime] emptydir_mode = "block-plain"` 后, `/var/lib/docker` 是 guest `/dev/vdb` 上的 ext4,Docker 29.1.5 可使用原生 `overlay2`,不再依赖 `fuse-overlayfs`。 - Kata/Cloud Hypervisor 直接把 kubelet emptyDir 中的稀疏 `disk.img` 热插拔为 block device,host/LXC 没有使用 loop device。 - 256 MiB + 2000 小文件的 BuildKit 冷构建:Kata 46 秒,同一 LXC/DRBD HDD 上的 runc 对照 34 秒,Kata 约慢 35%。 - overlay2 内 512 MiB fsync 写入:Kata 12 秒(40.5 MB/s),runc 10 秒 (50.3 MB/s);一万个小文件两者均约 2 秒。 - kind v0.27.0 / Kubernetes v1.32.2 可以运行。dockerd bootstrap 必须先在 Kata guest 内执行 `mknod /dev/kmsg c 1 11`,否则 kind node kubelet 因缺少 `/dev/kmsg` 退出。修复后 kind 总创建时间 122 秒,等待阶段 23 秒 Ready。 - `emptyDir.sizeLimit: 8Gi` 没有决定 block image 容量;12 GiB LXC rootfs 产生约 12 GiB 稀疏 image。这是 Kata 已知限制,正式节点必须给 kubelet volume 目录使用 独立且容量受控的文件系统。 - 删除 Pod 后 `disk.img`、VMM 和 kind 容器全部回收,无 loop device 或 OOM 残留。 结论:Kata 不再因 virtio-fs/overlay2 问题直接淘汰。#83 的 `ci-vm` 应使用独立的 `block-plain` RuntimeClass,并把 overlay2、kind、backing-file 回收及性能阈值纳入 上线验收;不能使用默认 `shared-fs` RuntimeClass 承载镜像构建。 完整数据已写入 `infrastructure/kata-lxc-lab/README.md`。
Author
Owner

设计修订(2026-09-17):正式 sandbox 集群取消独立 /var/lib/kubelet volume。PoC 确认 block-plain 的 disk.img 容量取决于承载目录文件系统、且 emptyDir.sizeLimit 不可靠,但单独 kubelet volume 会额外引入 DRBD volume、LXC mount 和清理生命周期,同时不能隔离 containerd 等其他节点数据。正式节点统一使用 pve-rg-hdd 上的 32 GiB rootfs;容量安全改由节点磁盘监控、Pod 删除后的 backing-file/VMM 回收检查及构建基准验收保证。该修订取代正文及前一条 PoC 评论中“正式节点必须使用独立 kubelet volume”的结论。当前两台 LXC 已完成迁移,后端实际容量、PVE 配置和容器内 ext4 均为 32 GiB,旧 volume 已删除。

设计修订(2026-09-17):正式 sandbox 集群取消独立 `/var/lib/kubelet` volume。PoC 确认 `block-plain` 的 `disk.img` 容量取决于承载目录文件系统、且 `emptyDir.sizeLimit` 不可靠,但单独 kubelet volume 会额外引入 DRBD volume、LXC mount 和清理生命周期,同时不能隔离 containerd 等其他节点数据。正式节点统一使用 `pve-rg-hdd` 上的 32 GiB rootfs;容量安全改由节点磁盘监控、Pod 删除后的 backing-file/VMM 回收检查及构建基准验收保证。该修订取代正文及前一条 PoC 评论中“正式节点必须使用独立 kubelet volume”的结论。当前两台 LXC 已完成迁移,后端实际容量、PVE 配置和容器内 ext4 均为 32 GiB,旧 volume 已删除。
Author
Owner

数据库入口决策(2026-09-17):首期不部署 PgBouncer、Patroni、repmgr 或额外 DCS。PostgreSQL 使用 primary + synchronous standby,不宣称自动 HA;由 Ansible runbook 执行隔离旧 primary、提升 standby、切换入口和 failback。K3s datastore 使用 VyOS 上的固定 TCP LB 入口,LB 仅指向当前已声明 primary,不依据 TCP/pgsql 存活检查自动把写流量切到 standby。优先复用 labnet gateway 10.60.0.1:5432,避免新增 VIP;最终配置以 VyOS 当前版本支持的原生语法验证结果为准。

数据库入口决策(2026-09-17):首期不部署 PgBouncer、Patroni、repmgr 或额外 DCS。PostgreSQL 使用 primary + synchronous standby,不宣称自动 HA;由 Ansible runbook 执行隔离旧 primary、提升 standby、切换入口和 failback。K3s datastore 使用 VyOS 上的固定 TCP LB 入口,LB 仅指向当前已声明 primary,不依据 TCP/pgsql 存活检查自动把写流量切到 standby。优先复用 labnet gateway 10.60.0.1:5432,避免新增 VIP;最终配置以 VyOS 当前版本支持的原生语法验证结果为准。
Author
Owner

2026-09-17 正式 Kata 阶段验收结果:

  • Flux/HelmRelease 已部署 Kata Containers 4.1.0,OCI artifact 固定为 sha256:33f102f6db70083de4fc8238af4439c4245a601bcb6a72e49bc80529098aefc0。
  • job 模式按 parallelism=1 串行完成 sandbox1/sandbox2 安装;两个节点 Job 分别约 2 分半,最终节点均 Ready,kata-clh-runtime-rs handler 已注册。
  • 两台节点 Kata guest 均启动成功,guest kernel 为 6.18.35。
  • block-plain 验证通过:emptyDir 在 guest 中为 /dev/vdb ext4;Cloud Hypervisor 将 /var/lib/kubelet/.../disk.img 作为 sparse Raw block disk 热插拔。
  • Cloud Hypervisor /api/v1/vm.info 确认 config.memory.shared=true、thp=true。
  • 删除 smoke Pod 后,disk.img 与对应 VMM/virtiofsd 均已回收;两个节点仍 Ready。
  • kata-monitor 两副本运行正常,中央 VictoriaMetrics 查询 up{cluster="sandbox",namespace="kata-system"} 得到两条 up=1。
  • monitor 已通过 Helm post-render 禁止自动挂载 ServiceAccount token。

发现一个独立架构缺口:普通 SPIFFE CSI volume 在 Kata guest 内只能看到 spire-agent.sock 路径,实际连接返回 connection refused,因为现有 CSI driver 是 host Unix socket 目录 bind mount,socket RPC 不能跨 VM/virtiofs 边界。两台节点结果一致。该问题已拆为 #94,不将 Kata guest SPIFFE 误记为通过。

另发现 root Kustomization timeout=3m 小于 Kata 首装约 6m,导致一次暂态 False;子 Kustomization/HelmRelease 实际成功,下一轮 root 已恢复 True。持久修复见 PR #93。

2026-09-17 正式 Kata 阶段验收结果: - Flux/HelmRelease 已部署 Kata Containers 4.1.0,OCI artifact 固定为 sha256:33f102f6db70083de4fc8238af4439c4245a601bcb6a72e49bc80529098aefc0。 - job 模式按 parallelism=1 串行完成 sandbox1/sandbox2 安装;两个节点 Job 分别约 2 分半,最终节点均 Ready,kata-clh-runtime-rs handler 已注册。 - 两台节点 Kata guest 均启动成功,guest kernel 为 6.18.35。 - block-plain 验证通过:emptyDir 在 guest 中为 /dev/vdb ext4;Cloud Hypervisor 将 /var/lib/kubelet/.../disk.img 作为 sparse Raw block disk 热插拔。 - Cloud Hypervisor /api/v1/vm.info 确认 config.memory.shared=true、thp=true。 - 删除 smoke Pod 后,disk.img 与对应 VMM/virtiofsd 均已回收;两个节点仍 Ready。 - kata-monitor 两副本运行正常,中央 VictoriaMetrics 查询 up{cluster="sandbox",namespace="kata-system"} 得到两条 up=1。 - monitor 已通过 Helm post-render 禁止自动挂载 ServiceAccount token。 发现一个独立架构缺口:普通 SPIFFE CSI volume 在 Kata guest 内只能看到 spire-agent.sock 路径,实际连接返回 connection refused,因为现有 CSI driver 是 host Unix socket 目录 bind mount,socket RPC 不能跨 VM/virtiofs 边界。两台节点结果一致。该问题已拆为 #94,不将 Kata guest SPIFFE 误记为通过。 另发现 root Kustomization timeout=3m 小于 Kata 首装约 6m,导致一次暂态 False;子 Kustomization/HelmRelease 实际成功,下一轮 root 已恢复 True。持久修复见 PR #93。
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: panxiao81/homelab-infra#83