跳到主要内容

部署与 Operator

DeviceChain 分两层部署,每层都由一个 dcctl 命令就位:

  • dcctl install 准备集群。它安装所有实例共享的前提组件,以及 Kubernetes Operator(基于 controller-runtime 构建)和资源定义。它不处理任何特定实例。
  • dcctl bootstrap 创建实例。它写入集群范围的 Instance 资源,声明即将构建的内容,再由 Helm Chart 渲染该实例的工作负载。

Operator 监视 Instance 资源。租户不在其职责内:租户是控制平面数据库记录(参见自定义资源)。

状态

已可用:Helm Chart 渲染各服务工作负载和配置,dcctl bootstrap 与 dcctl upgrade 驱动实例生命周期。Operator 观察 Instance 资源,目前不对其中内容执行任何操作。它的另一项工作针对使用 --backup-snapshot-class 安装的集群:每十分钟删除已超出恢复窗口的数据库卷快照基础备份,CloudNativePG 不会执行这项清理。实例状态聚合和按环境划分的 Kustomize 覆盖层已规划,但尚未开始。

状态聚合将加入 Operator 的监视循环。在实现之前,Instance 不报告状态,因此应读取工作负载本身或使用 dcctl 判断实例是否健康。

使用 Helm 部署​

deploy/helm/devicechain 中的 Chart 渲染:

  • 每个已启用功能域对应一个 Deployment 和 Service;
  • 各服务配置 ConfigMap;
  • 实例配置 Secret。它携带持久化凭据,因此使用 Secret,而不是 ConfigMap。

每个 Pod 都暴露 /healthz(存活检查)和 /readyz(就绪检查),因此未就绪的服务不会参与流量分配。

可以通过具名配置档或显式服务集合选择运行哪些服务:

配置档功能域
defaultuser-management、device-management、event-sources、event-management、device-state、dashboard-management、command-delivery、notification-management、event-processing——标准系统,也是未设置配置档时的选择
full此构建发布的全部功能:default 加 ai-inference、outbound-connectors、mcp、sparkplug-ingest 和 lwm2m-ingest;这些功能不包含在 default 中,因为每一项都需要明确作出决定(付费提供商密钥、出站访问接口、面向代理的 API,以及绑定独立入站端口的 Sparkplug B 或 LwM2M 设备传输)
telemetryuser-management、device-management、event-sources、event-management、device-state、dashboard-management
ingest-onlyuser-management、device-management、event-sources

秘密存储根密钥​

每种配置档都需要实例的秘密存储根密钥。所有配置档都运行的 user-management 会用它封装登录令牌签名密钥。存储集成凭据的功能域也用它封装凭据。缺少时,Chart 会使渲染失败,而不是让 user-management 陷入崩溃重启循环。

生成一个值(openssl rand -base64 32),妥善保存,并在每次安装和升级时传入同一个值。新密钥会使旧密钥保护的已有秘密不可读,并阻止所有人登录。dcctl bootstrap 会代你生成并托管保管此密钥;只有直接操作 Chart 时才需自行提供。

DC_ROOT_KEY="$(openssl rand -base64 32)" # generate ONCE, then keep it

helm install dc deploy/helm/devicechain \
--set instance.id=devicechain \
--set instance.config.infrastructure.secrets.rootKey="$DC_ROOT_KEY"

# Run a smaller set of services. Every profile needs the root key, the smallest
# ones included.
helm install dc deploy/helm/devicechain --set profile=telemetry \
--set instance.config.infrastructure.secrets.rootKey="$DC_ROOT_KEY"

安装已发布版本​

要安装正式发布的版本,将镜像标签固定为版本号。发布镜像在 ghcr.io/devicechain-io 公开提供,因此无需本地构建。用实际发布的标签替换 <version>;发布页面列出了它们。

helm install dc deploy/helm/devicechain \
--set instance.id=devicechain \
--set instance.config.infrastructure.secrets.rootKey="$DC_ROOT_KEY" \
--set image.tag=<version>

发布与升级介绍版本模型和升级流程。升级方法取决于实例的创建方式:

  • 通过引导创建的实例需要两条命令。dcctl install 升级属于集群的 Operator;dcctl upgrade 升级属于实例的消息代理、事件存储、配置文档及发布版本。Operator 不包含在 Chart 中,因此必须由 Chart 外的工具升级。
  • 只由 Chart 管理的实例使用 helm upgrade,并手动沿用配置值。

服务选择规则​

user-management 和 device-management 是必需核心。event-management、device-state 和 command-delivery 各自可选。如果选择中遗漏必需核心服务,或遗漏已启用服务的硬依赖,Chart 会使渲染失败,因此安装时就能发现无效拓扑,而不是等到 Pod 崩溃重启。应用时根据 Chart 的 values.schema.json 验证配置值。

自定义资源​

dcctl install 定义两种集群范围的自定义资源:

  • Instance(instances.core.devicechain.io,短名 dci):每次安装一个,声明实例身份和配置。
  • InstanceConfiguration(instanceconfigurations.core.devicechain.io,短名 dcic):可保存实例配置文档的资源。定义会被安装,但 dcctl bootstrap 不会创建该资源,Operator 也不监视它。每个 Pod 从挂载的实例配置 Secret 读取实例配置。

dcctl install 将两种定义和控制器一起安装。它们在各方面都是集群范围的:每个集群一份,由其上的所有实例共享,版本跟随集群,而不是某个实例。因此,准备集群的命令也是升级它们的命令。

dcctl bootstrap 和 dcctl upgrade 只读取定义。集群没有定义,或定义能够识别为来自不同发布版本时,它们会拒绝操作,并指出应运行的安装命令。手动安装的定义没有安装版本记录,因此允许通过,但会提示同一个命令,而不是拒绝。

租户不是自定义资源。它们是通过实例管理员 API 和 /admin 控制台创建的控制平面数据库记录,共享实例服务(参见多租户)。

kubectl get instances # platform

实例声明​

dcctl bootstrap 为即将构建的实例写入 Instance 对象。随后读回对象,并依据读回内容工作,而不是依据生成对象的参数。这让集群,而不是输入命令的机器,成为实例定义的记录来源:

  • 所属提供商和集群;
  • 运行的配置档和镜像版本;
  • 数据库是否从归档恢复;
  • 最后写入它的 dcctl 构建版本;
  • 上次运行试图执行的操作。

因此,另一台机器上的运维人员无需任何本地状态就能读取实例:

kubectl --context <kube-context> get instances
kubectl --context <kube-context> get instance <id> -o yaml

部分规范写入后不可更改。集群绑定最重要,因为改写它会使 dcctl destroy 指向另一个集群。两层都会拒绝这种编辑:

  • CRD 通过验证表达式携带规则,因此 API 服务器会拒绝用 kubectl 进行的手动编辑。
  • dcctl 写入前,会将同样的字段与集群内已有声明比较。

读取 PHASE 列​

kubectl get instances 打印 PHASE 列,其值来自声明的 core.devicechain.io/phase 注解。dcctl instances list 读取同一注解,并在 STATUS 列中用文字显示。该命令仅在引导实例的机器上工作,依据该机器的本地记录。

阶段表示意图,而不是健康状态

它记录上次 dcctl 运行试图执行的操作。写入它的流程没有检查任何 Pod,因此 dcctl instances list 从不报告 running。要知道工作负载是否运行,请直接查看:kubectl get pods -n dci-<id>。

PHASEdcctl instances list STATUS含义
Bootstrappingbootstrap started, not finisheddcctl bootstrap 已写入声明,但尚未写入最终阶段。另一个终端中正在正常执行的引导也显示此状态。
Upgradingupgrade started, not finished相同含义,对应 dcctl upgrade。
Readydeclared ready上次引导或升级已完成,不说明当前 Pod 状态。
Failedlast run failed上次引导或升级返回错误。
DestroyingPART-WAY DESTROYED — re-run `dcctl destroy` dcctl destroy 已开始但未完成。它在删除任何内容之前写入,因此销毁在之后任何时点被终止都会在此可见。
(空白)declared, phase not recorded声明由该注解出现之前的 dcctl 写入。

处理 Bootstrapping 或 Upgrading 之前,先检查运行是否仍在进行。正常运行结束时会将阶段重写为 Ready 或 Failed,包括用 Ctrl+C 中断的情况。只有没能写入结束状态的运行才留下这些值,例如机器断电或终端被终止。二者都不会阻止任何操作:dcctl bootstrap 和 dcctl upgrade 可以直接在其上运行。命令会采取行动的唯一阶段值是 Destroying(见下文)。

除了阶段之外,还有一个注解会影响行为。core.devicechain.io/bootstrap-unfinished: "true" 标记首次引导尚未成功结束的实例。只要该注解仍存在且阶段不是 Ready,即使配置文档存在,dcctl bootstrap 也会继续运行以完成实例。成功结束的引导或升级会在记录 Ready 的同一次写入中移除它。不要手动添加:对于正在运行且阶段不是 Ready 的实例,它会允许引导再次执行并覆盖该实例。

显示 declared, unknown phase "…" 的行,由比当前列出它的版本更新的 dcctl 写入。

dcctl instances list 在处理阶段之前还会检查几项情况。每种都有自己的状态文字,而不会显示为健康:

STATUS情况
PART-WAY DESTROYED来自本机的销毁标记,即使无法连接集群也会显示
cluster gone — stale local state集群已不存在
no record — destroy will guess the cluster实例在开始记录集群之前引导创建
cluster present, no declaration集群存在,但没有声明
declaration marked for deletion见下文
could not check: …探测失败或超时

kubectl delete instance 不会完成​

声明携带 finalizer。手动删除会保留对象并将其标记为删除,直到 dcctl 清除 finalizer:

kubectl delete instance prod # does not return; the object stays, now terminating

这是刻意的,保护两个不同方面:

  • 实例。 手动删除声明会使仍在运行的实例成为孤儿:命名空间、数据库、卷和工作负载都还在运行,但集群中没有记录说明它们属于什么。
  • 集群绑定。 不可变规则通过比较对象的新旧版本工作。重新创建的对象没有旧版本可比较,因此先删除再重新应用,会通过两个单独看都合法的步骤重新指向集群绑定。

dcctl destroy 自行清除 finalizer,作为其在集群中的最后一步:在命名空间消失之后、本地状态移除之前。一般无需手动操作。destroy 从不删除集群本身,因此由这一步移除声明。

销毁失败时会刻意留下声明,除非仅在移除本地状态时失败,而当时声明已经消失。保留的声明:

  • 仍记录实例所在集群,供重新运行使用;
  • 显示 Destroying 而不是 Ready,让下一个读取者知道拆除正在进行。
引导和升级会拒绝销毁了一半的实例

对这样的声明运行 dcctl bootstrap 或 dcctl upgrade 都会拒绝,而不会在半个旧实例上构建半个新实例。拒绝信息会要求用可恢复执行的 dcctl destroy <id> 完成拆除;或者,如果确定实例已无任何残留,可用不会销毁任何内容的 dcctl instances release <id> 删除声明。

移除声明而不销毁任何内容​

dcctl instances release <id> --kube-context <kube-context>

这会清除 finalizer 并删除声明。它不销毁任何内容。 命名空间、数据库、卷和工作负载之后仍在运行,而 dcctl 不再有记录说明它们属于什么。因此命令要求你明确确认这正是想要的结果。它之所以存在,是因为负责移除 finalizer 的工具消失时,否则集群会留下无人能够删除的对象。

命令会打印即将执行的操作,并要求再次输入实例名称;脚本使用时可用 --yes 跳过提示。任何未引导该实例的机器都需要 --kube-context,因为它告诉 dcctl 哪个集群保存声明。

要移除实例,请运行 dcctl destroy。

职责分离​

DeviceChain 刻意将每层交给一个工具:

层工具职责
基础设施OpenTofuNATS、TimescaleDB、命名空间、入口、TLS
工作负载Helm ChartDeployment、Service 和各功能域配置 ConfigMap
生命周期Operator监视 Instance 资源,目前不对其中内容采取行动(已规划状态聚合);按恢复窗口清理卷快照基础备份
实例身份与配置dcctl写入 Instance 声明及工作负载挂载的实例配置 Secret
业务配置实例管理员 API / /admin 控制台,以及每个租户自己的 API / 控制台租户及各租户自己的设置(例如品牌)

各层在不同时间运行:

  • OpenTofu 在安装集群时运行(dcctl install,配置所有实例共享的前提组件),也在引导实例时运行(配置该实例自己的消息代理和事件存储)。
  • dcctl install 还会应用 Operator 及其定义,使用嵌入 CLI 的清单,而不通过其他两层。
  • Chart 渲染工作负载。
  • Operator 监视 Instance 资源(目前如何处理它,参见本页顶部的状态说明)。

集群引导从不放在应用或 Operator 代码中,而是基础设施层的工作。OpenTofu 模块位于 deploy/opentofu。它们为数据库层配置保留保护,使数据库能在应用拆除后继续存在(参见发布与升级)。