首页 / 资讯中心 / 文章详情

在 Kubernetes 里跑对象存储的三个方案:Helm、Operator、以及什么时候别用 K8s

在 Kubernetes 里跑对象存储的三个方案:Helm、Operator、以及什么时候别用 K8s ★ FEATURED ARTICLE
把对象存储搬进 K8s 的动机通常是顺便反正集群已经在跑再加一套存储也不差一个 StatefulSet。但存储和 Web 应用在 K8s 里的相处方式完全不同Web 应用挂了重启没事存储的 StatefulSet 挂了要考虑 PVC 会不会丢、分布式集群能不能凑够法定人数、重启循环会不会把可用性拖穿。这三件事想不清楚就上后面每一步都在还债。RustFS 在 K8s 上官方支持两条路径Helm Chart 和 Operator加上别放 K8s 里这个选项构成三个方向。这篇按决策顺序讲先看 Helm 的默认值里藏着什么再看 Operator 适合谁最后说清楚什么情况下放弃 K8s 部署。Helm Chart 的默认值你得逐个看RustFS 官方 Helm Chart 的仓库在 github.com/rustfs/rustfs 的helm/rustfs目录或者从charts.rustfs.com拉helm repoaddrustfs https://charts.rustfs.com helm repo updateexportRUSTFS_CHARTrustfs/rustfs helm upgrade--installrustfs$RUSTFS_CHART\--namespacerustfs --create-namespace\-fdistributed-values.yaml helm upgrade--installrustfs$RUSTFS_CHART\--namespacerustfs --create-namespace\-fstandalone-values.yamlHelm 里还有一类配置值得看一眼PDBPod Disruption Budget默认关闭开pdb.createtrue后默认maxUnavailable1。这个设置的含义是节点滚动维护时最多允许一个 Pod 不可用对存储这种有法定人数要求的组件来说这条约束决定了维护期间集群是否还能写入。4 副本的集群 1 个 Pod 不可用通常还能写2 个同时不可用就得看纠删配置扛不扛得住。做节点维护窗口规划时把这条算进去。但要准确理解这条约束的作用范围。PDB 是一个调度约束不是可用性保证它只在自愿驱逐路径上生效节点drain、升级节点内核这类操作才受它约束。kubectl delete pod、节点直接宕掉、OOM 杀进程这些情况根本不走驱逐路径PDB 拦不住。所以 PDB 能防御的是维护时别一次抽走太多防御不了故障本身。要让维护期间集群仍然可写还得看纠删配置能容忍几块盘。官方文档里有一句关于默认值的话值得抄在 values.yaml 旁边默认 PVC 大小 256Mi仅用于基础评估生产要显式设置存储容量。这意味着拿默认 values 直接 install 出来的集群PVC 只有 256 MiB跑真业务立刻写满。默认的关键值项默认值说明mode.distributed.enabledtrue默认走分布式replicaCount4分布式下是 Pod 数量drivesPerNode按 replicaCount 推断4 → 每节点 4 块盘其他 → 每节点 1 块storageclass.namelocal-path集群里必须有对应 StorageClassstorageclass.dataStorageSize256Mi生产必须改service.endpoint.port9000S3 APIservice.console.port9001Console一个容易踩的点是drivesPerNode的推断规则replicaCount 为 4 时默认每节点 4 块盘共 16 块其他 replicaCount 默认每节点 1 块。想要 8 节点 × 2 盘的布局必须显式设drivesPerNode: 2否则会得到 8 节点 × 1 盘可用容量差一倍。Chart 里只有 OTel 导出的开关没有 Prometheus、Grafana、Jaeger 等附属组件这意味着可观测栈要自己搭。这和 Docker compose 那套compose 文件里带 grafana/prometheus/otel/jaeger 四个服务不一样迁移时要重新规划监控。Operator 是什么、什么时候用RustFS 官方有一个独立的 Operator 仓库 github.com/rustfs/operator用 Rust 写的当前版本 v0.1.0 pre-release。官方文档对它的描述是applies the Kubernetes Operator pattern to RustFS clusters装上去会创建两个 CRDTenantrustfs.com/v1alpha1和PolicyBindingsts.rustfs.com/v1alpha1。一个 Tenant 的 yaml 长这样apiVersion:rustfs.com/v1alpha1kind:Tenantmetadata:name:tenant-anamespace:storage-aspec:image:rustfs/rustfs:1.0.0credsSecret:name:rustfs-tenant-credspools:-name:pool-0servers:1persistence:volumesPerServer:1volumeClaimTemplate:storageClassName:standardaccessModes:-ReadWriteOnceresources:requests:storage:10Gi部署方式之间还有一个容易混用的细节Helm Chart 里 Console 端口是 9001Operator 带出来的 Console 是 9090。写访问文档或脚本时如果两套环境并存别把端口抄串了。Operator 适合的场景是多租户每个 Tenant 是一个独立的 RustFS 集群有独立的凭据和存储池适合平台团队给不同业务线各发一套存储。它还带了 Grafana 仪表盘 ConfigMap通过 labelgrafana_dashboard: 1暴露、Console端口 9090、tenantMonitor默认 300 秒间隔。用之前先补一样上面这段 yaml 里没写的东西Pod 的 CPU 与内存requests/limits。示例 Tenant 只给了 PVC 的 storagePod 侧没有任何资源声明这种 Pod 在 Kubernetes 里被归为 BestEffort QoS节点资源紧张时是第一批被驱逐的对象存储进程被驱逐走的代价比 Web 应用高得多。生产环境必须显式配置同时给 requests 和 limits让 Pod 落在 Burst 或 Guarantee QoS 上。另一个 CRDPolicyBinding在sts.rustfs.com组下从命名看是给 STS 相关的策略绑定用的官方文档目前对它的描述篇幅不多用之前最好先读一遍仓库里的 CRD 定义和示例别照着Tenant的用法套。Operator 安装本身是走 Helmgitclone https://github.com/rustfs/operator.gitcdoperator helm upgrade--installrustfs-operator deploy/rustfs-operator/\--namespacerustfs-system --create-namespaceOperator 的 Chart 默认带 dashboard、metrics端口 8080、tenantMonitor、ConsoleserviceMonitor.enabled和prometheusRule.enabled默认关着接 Prometheus Operator 时再打开。扩 pool 的动作是在 Tenant yaml 里追加一个 pool 条目Operator 会为每个 pool 创建独立的 StatefulSet官方文档里明确一句Existing pools are immutable已有 pool 的 servers 和 volumesPerServer 不能改。代价是版本和成熟度Operator 当前是 pre-release文档里明确写了 under active development生产用要自己评估。如果只是想给一套业务跑一个 RustFS 集群Helm Chart 更直接需要管理多个 Tenant 时 Operator 才划算。StatefulSet 的 volumeClaimTemplates 不能改这条约束是 K8s 层面的不是 RustFS 特有的但对存储部署影响很大官方文档专门用一句话强调了它Kubernetes does not allow updates to StatefulSet volumeClaimTemplates. Changing drivesPerNode later requires StatefulSet recreation or a new installation.想改 drivesPerNode 就得删 StatefulSet 重建--cascadeorphan保留 Pod 和 PVC或者干脆重装。这个约束决定了容量规划必须在装机前做完跑起来之后发现盘不够走的是加 pool多池扩容而不是改现有 pool。另外注意扩容会把数据搬动IO 与延迟的代价见下一节。官方给出的扩容路径就是多池加 rebalance新 pool 加进来之后数据在池之间重新分布旧 pool 的 PVC 不用动。所以第一次部署时把单池规模定得保守一点、留出加池的余地比一开始就把盘塞满要稳。rebalance 这件事要单独排窗口。把一卷数据从旧池挪到新池代价是持续的磁盘读写和网络传输集群读写延迟在这段时间里会明显上抬IO 打满时还会牵连同一节点上的其他 Pod。官方文档给的节奏是先确认 Tenant Ready、再应用完整 manifest、再等 Ready扩容期间反复观察get tenant -w与get pods,pvc -l rustfs.poolpool-1中间不停下来做下一次拓扑变更。生产上把它排在业务低峰窗口扩容窗口内别同时安排其他节点维护。官方文档还给了两个运行时细节值得知道anti-affinity 默认开启topologyKey 是kubernetes.io/hostnamePod 会被打散到不同节点要按可用区打散得开topologySpreadConstraints启动时 Pod 崩溃循环是正常的官方原话pods restart until every pod of every pool is resolvableserver 拒绝在有不可达 peer 的情况下启动所以预期会看到几次 crash loop这是无害的第二条如果不提前知道第一次装的时候很容易以为装坏了。把 Helm 和 Operator 两条路径放在一起看分工其实比较清楚Helm 管的是一套 RustFS 集群的部署Operator 管的是多个 Tenant 的生命周期。如果只有一套集群Helm 加手写 yaml 足够如果集群数量会增长、或者要把存储当作平台能力提供给多个团队Operator 的 CRD 模型把重复劳动抽象掉了。从 Operator 现在的版本号看它更像在能力建设的早期功能覆盖还没到可以完全替代 Helm 的程度实际部署里两条路径并存也正常。数据怎么持久化PVC 别删Helm Chart 的持久化走 PVC分布式模式下每个 Pod 挂drivesPerNode个数据 PVC 加一个日志 PVC。官方升级文档里有一句加粗的话Do not delete PersistentVolumeClaims (PVCs) during an upgrade or rollback。PVC 是数据的家删了就真没了。多 pool 部署时Chart 会为每个 pool 生成一个独立的 StatefulSet命名形如fullname-poolN所有 pool 共享同一个 headless service、主 service、配置和凭据。这意味着扩 pool 时的动作是改 values 里的pools.listChart 会渲染出新的 StatefulSet原有 pool 不受影响。RUSTFS_VOLUMES表达式由 Chart 自动生成集群 DNS 域名默认cluster.local改了 K8s 的 DNS 域要跟着改 Chart 里的clusterDomain。还有一个和 StorageClass 绑定的陷阱Chart 默认的local-path把卷落在具体某个节点的本地目录上Pod 漂到别的节点就读不到原来的数据。节点故障后重建 Pod 时要先确认调度回原节点或者从一开始就用能跟着 Pod 迁移的 StorageClass。测试环境无所谓生产环境这个点会直接决定一次节点故障是不是变成数据丢失。local-path这个类 StorageClass 还有一层限制值得知道它一般不提供拓扑约束topology能力调度器无法感知某个卷到底在哪个节点上。生产要用本地盘更稳妥的做法是手工创建local类型的 PV 并在 Pod 上写nodeAffinity钉住节点而不是依赖简易 provisioner 自动创建的本地卷。这两条加起来决定了本地盘方案是可控但麻烦不是简单直接。卸载时 Helm 不会删除 StatefulSet 创建的 PVC。这一点是 K8s 里 StatefulSet 的既有行为StatefulSet 管理出来的 PVC 有独立生命周期helm uninstall 只删 Helm 自己管理的资源不会去动它。换句话说卸载之后想恢复数据下面这条命令千万别跑kubectl-nrustfs delete pvc-lapp.kubernetes.io/namerustfs升级时的滚动状态检查kubectl-nrustfs rollout status statefulset/rustfs--timeout10m kubectl-nrustfs rollout status deployment/rustfs--timeout10m滚动升级是另一个容易踩法定人数的动作开了 PDB 也不等于安全。升级过程中 Pod 是逐个被驱逐再拉起的同时不可用的数量一旦超过纠删码能容忍的坏盘数写请求就会失败。所以开升级窗口前先看一眼自己的纠删配置几块盘、几个奇偶校验对应能容忍几个 Pod 同时不在把同时下线的数量卡在容忍线以内再按 pool 逐个滚动。单节点模式没有纠删这层Pod 不可写就是整体不可写窗口要挑在业务完全空的时候。三条路线并列看是这样什么情况下别用 K8s 跑三个信号出现任何一个就该考虑把存储放在 K8s 外面第一本地盘资源是瓶颈。 K8s 的 StorageClass 通常走网络存储Ceph、云盘而对象存储恰恰是吃本地盘吞吐的网络存储一层转发会显著拖累 S3 请求延迟。如果集群里没有 local PV 或高性能本地盘 StorageClass跑出来的对象存储性能会远低于裸机部署。更麻烦的是冗余会叠加对象存储自己用纠删码做了一份冗余底层的网络存储通常又做一份副本或纠删等于同一份数据付两次保护成本可用容量被压缩到裸机部署的一半甚至更低。第二节点调度可能拆散纠删集。 对象存储的可靠性建立在同一个纠删集的盘分散在不同故障域上K8s 调度器的 anti-affinity 默认只按 hostname 打散不感知纠删集结构节点维护或自动伸缩时可能把同一个纠删集的多个 Pod 调到同一台机器可靠性会退化。这层保护要靠 topologySpreadConstraints 加亲和规则自己搭。第三运维能力不在一个团队手里。 K8s 上跑存储意味着存储团队要懂 K8s或者 K8s 团队要懂存储。两边都懂的组合不多见出问题时容易互相推诿。直接在裸机或虚机上部署边界更清楚。三条之外补一个决策参考如果 K8s 集群本身就是临时性的比如CI 环境的测试集群存储放进去跟着一起创建销毁反而是合理的这种场景下数据本来就不需要长期保留K8s 的生命周期管理正好覆盖。真正麻烦的是数据要长期保留、但集群形态会变的场景那是把两个生命周期绑在一起出问题的概率最大。RustFS 在 Apache 2.0 许可下开源Helm Chart 与 Operator 的官方文档分别在 cloud-native/helm-chart 与 cloud-native/operator。三条路径没有绝对对错关键是想清楚自己要解决的问题是什么再把对应的默认值逐条过一遍——Helm 的 256Mi 默认值只够评估operator 的版本状态也值得在选型阶段确认清楚。
阅读完成 · 觉得有帮助?
咨询建站