ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

RustFS Operator 0.0.6升级必读:多租户CRD、Pod安全的默认变更与踩坑点

RustFS Operator 0.0.6升级必读:多租户CRD、Pod安全的默认变更与踩坑点 一个 Operator 管着七八个命名空间每个团队一份 Tenant、一把密钥、一套独立的 S3 端点和控制台。这种格局下最危险的不是某个 Pod 起不来而是半年后没人说得清为什么这个租户的 Pod 跑在 root 下、那个租户的 SA token 还挂在容器里。人肉维护 yaml 的故事通常都收在同一个结局第一版还算整齐第三版开始有人直接kubectl edit就地改第四版没人敢动。这套 Operator 走到 0.0.62026-09 发布做的事情方向很明确把上一版还需要你在 values 里手搓的那层安全壳变成生成工作负载时的默认值。对要在同一套集群里给多个团队切对象存储底座的团队这次 Release 值得单独看后文用它举的例子也都来自官方文档。安全默认值从哪来K8s 里跑有状态服务Pod 以什么身份启动、有没有挂 API token通常是合规审计最先问的两个问题。0.0.6 在这两处直接换了默认生成的 RustFS Pod 按Restricted档出安全上下文也就是 Pod Security Standards 三档里最严的一档并显式拒绝矛盾或不安全的 root 工作负载配置。同时 Operator 移除了一批遗留的 Tenant workload Role 与 RoleBinding渲染出的是automountServiceAccountToken: false。标准 RustFS 工作负载不需要访问 K8s API默认不给 token 是对的。这两条各有代价得提前想清楚。用自定义镜像的租户要复核。0.0.6 的 release notes 在这一段的措辞是覆盖默认镜像的 Tenant 需要显式确认最终解析出来的运行时镜像理由是别让未经验证的镜像跑在RuntimeDefaultseccomp 下面点名的风险对象仍然是较早版本里 io_uring 行为不兼容的那批。服务端默认布局是 124 纠删码升级前先确认你那个 tag 在新默认下能不能起来。如果你往 Pod 里加了 sidecar或者写了个脚本要调 K8s API那这套默认对你不成立。得自备一个用户自己的 ServiceAccount、最小权限 RBAC以及显式投影的 ServiceAccount token。这是一次 StatefulSet Pod 模板变更会让 Tenant Pod 整体滚动。同一批里还有几处是拒绝而不是提供Console 会话密钥必须是密码学强度的弱 key 直接拒绝Tenant 引用的凭据 Secret 为空或非法在校验阶段就拦下Console 登录和 STS 请求都加了未认证访问的上限。这些都属于把事故挡在 apply 之前的那类改动。起不来的时候先看这三类默认值收紧之后最容易出问题的是起了半截。这三种失败形态表现完全不同先分清是哪一类再动手改。先看 Operator 到底渲染出了什么。Tenant 里写的是意图真正生效的是 StatefulSet中间隔着 Operator 一层kubectl-nstorage-a get tenant tenant-a-ojsonpath{.spec.containerSecurityContext}kubectl-nstorage-a get statefulset tenant-a-pool-0\-ojsonpath{.spec.template.spec.securityContext}kubectl-nstorage-a get statefulset tenant-a-pool-0\-ojsonpath{.spec.template.spec.containers[0].securityContext}kubectl-nstorage-a get statefulset tenant-a-pool-0\-ojsonpath{.spec.template.spec.automountServiceAccountToken}StatefulSet 名是{tenant}-{pool}这个组合能直接从kubectl -n storage-a get statefulset列出来核对别照抄名字。最后一条应该返回false这是 0.0.6 想要的形态返回true说明你的 Tenant 或命名空间里有东西把它打开过。第一类镜像以 root 运行。restricted 档要求runAsNonRoot容器镜像里进程是 uid 0 的时候kubelet 在启动阶段就拒绝起容器Pod 卡在ContainerCreating后转入CrashLoopBackOff。这时候看容器上一个状态最直接kubectl-nstorage-a describe pod tenant-a-pool-0-0|grep-A5Last Statekubectl-nstorage-a logs tenant-a-pool-0-0--previous如果日志是空的、退出码也不是应用自己的基本就是这一类别去翻应用日志。第二类自定义 securityContext 和默认值打架。这一类的报错来自 Operator 的 Tenant 校验不在 Pod 里。看 Operator 自己的日志kubectl-nrustfs-system logs deployment/rustfs-operator-f|grep-i-Esecurity|credential|rejectedOperator 的对账日志里会带出错的 Tenant 名和错误类型形如error_policy: Context { source: CredentialSecretNotFound { ... } }。这类问题在 apply 之前就被拦住Pod 根本不会创建所以排障对象是 Operator 而不是业务容器。第三类旧镜像的 io_uring 撞上 RuntimeDefault seccomp。这一类最费时间因为它不算起不来Pod 会跑到 Running容器也没退出但 IO 卡住或者超时应用侧表现为读写请求堆起来。要把 seccomp 的影响单独摘出来可以临时把该池的容器安全上下文改成不收敛跑一次对比pools:-name:pool-0# 只用于定位验证完删掉这段Operator 默认会补回来containerSecurityContext:seccompProfile:type:Unconfined如果换到Unconfined之后读写恢复正常问题就锁定在 seccomp 放行的 syscall 集合上。这一类不走彻底放开而是把 profile 指到一个你自己的文件上按实际用到的 syscall 放行containerSecurityContext:seccompProfile:type:LocalhostlocalhostProfile:profiles/allow-io-uring.jsontype必填localhostProfile的路径相对 kubelet 的 seccomp 根目录/var/lib/kubelet/seccomp/hash/。改完记得把这段删掉它是覆盖 Operator 默认值的留在 manifest 里就是给自己埋一个安全配置写死在档案里、没人敢动的坑。顺带说一个更容易漏的变体改securityContext会让 Pod 模板变化进而触发整池滚动大卷在重新挂载时会重新做卷属主调整首次挂载偏慢。这一点在官方关于 OpenShift SCC 变更的说明里也提到了。一份 Tenant 长什么样Operator 装好之后两个 CRD 是入口Tenantrustfs.com/v1alpha1描述一个完整的存储集群PolicyBindingsts.rustfs.com/v1alpha1负责把 K8s ServiceAccount 映射到 RustFS 策略。一个 Operator 可以跨命名空间管理多个租户每个租户有独立的存储、凭证、S3 和控制台服务。官方多租户页给的最小声明大概是这样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:10Giimage我改成了1.0.0。官方文档示例和 0.0.6 release notes 里的默认值都还写着rustfs/rustfs:1.0.0-beta.10那是服务端 1.0.0 发布之前的口径服务端已于 2026-09-16 GA新租户直接钉1.0.0别让 beta 镜像混进来。credsSecret只引用名字密钥本身用独立的 Secret 放凭证不进 manifest这条对 GitOps 是必需的。pools是容量和故障域的单位不是节点数。每个 pool 由一个独立的 StatefulSet 承载所有 pool 合成一个统一的集群。扩容的做法不是改现有 pool而是追加一个新条目pools:-name:pool-0servers:1persistence:volumesPerServer:1volumeClaimTemplate:storageClassName:standardresources:requests:storage:10Gi-name:pool-1servers:2persistence:volumesPerServer:2volumeClaimTemplate:storageClassName:standardresources:requests:storage:100Gi现有 pool 的servers和persistence.volumesPerServer都不可变。这是 StatefulSet 的约束不是 Operator 自己加的。selector 只在rustfs.tenant上加想扩容就加池不要试图把老池改大。装 Operator 这一步官方文档给的是从仓库装不是从 chart 仓库拉gitclone https://github.com/rustfs/operator.gitcdoperator helm upgrade--installrustfs-operator deploy/rustfs-operator/\--namespacerustfs-system\--create-namespace起完之后kubectl get crd tenants.rustfs.com能返回就说明 CRD 装上了。要访问控制台Operator 自己的 HTTP API 在 9090 端口拿一个短时 token 转发出去即可kubectl-nrustfs-system create token rustfs-operator-console--duration24h kubectl-nrustfs-system port-forward svc/rustfs-operator-console19090:9090租户侧的 S3 和控制台各自有服务端口是 9000 和 9001kubectl-nstorage-a port-forward svc/tenant-a-io9000:9000 kubectl-nstorage-a port-forward svc/tenant-a-console9001:9001资源按池填调度按池切前面那份最小 Tenant 里没有resources。容易看漏的地方是 CRD 里有两个同名的resources层级不同含义也完全不同pools[].persistence.volumeClaimTemplate.resources.requests.storage是 PVC 要多大而pools[].resources.requests和.limits才是容器要多少算力。写混了 Kubernetes 不会报错只是 PV 和 Pod 各按各的走。官方 examples 目录里其实给了算力基线只是不在多租户页在production-ha-tenant.yaml16 个 server 配 4 块卷requests 是cpu: 4/memory: 16Gilimits 是cpu: 8/memory: 32Gi。同一个spot-instance-tenant.yaml里需要保底的 on-demand 池4 个 server 配 4 块卷给的是 requestscpu: 8/memory: 32Gilimitscpu: 16/memory: 64Gi。两个池子规模不一样、给的数字也没差一个量级。官方没有给按卷数换算的公式这两个点位只能当锚点用。真正要填的数得来自你自己那套集群上的压测并发客户端数、单次请求的对象大小、以及 GC 与后台重建对 CPU 的占用都影响曲线。有状态服务一般把 requests 和 limits 拉平Guaranteed让它不参与节点上争抢代价是资源预留得足。内存尤其别省对象读写要靠页缓存兜给少了会往回掉到磁盘上。pools:-name:pool-0servers:4resources:requests:cpu:8memory:32Gilimits:cpu:8memory:32Gipersistence:volumesPerServer:4volumeClaimTemplate:accessModes:[ReadWriteOnce]storageClassName:standard-ssdresources:requests:storage:2Ti调度约束的字段都在池这一层池与池之间可以完全不一样nodeSelector、affinity里面是nodeAffinity、podAffinity、podAntiAffinity、tolerations、topologySpreadConstraints、priorityClassName。多租户要隔离光靠策略是不够的得让两个租户的 Pod 不落在同一批节点上。做法分两步给节点打标签和污点再让各自的池去认领。kubectl labelnodenode-1tenanttenant-a kubectl taintnodenode-1dedicatedtenant-a:NoSchedule-name:pool-0nodeSelector:tenant:tenant-atolerations:-key:dedicatedoperator:Equalvalue:tenant-aeffect:NoSchedulenodeSelector是硬性筛选tolerations解决允许调度到带污点的节点。这两条要成对写只写污点不写容忍Pod 一个也起不来。还有一条存储侧的纪律官方 examples 的 README 写得比正文更直白所有 pool 用同一个 StorageClass混着 NVMe、SSD、HDD 上去整体按最慢的那一档算把热数据放 NVMe、冷数据放 HDD当成分层用是明确列出的无效用法。有意思的是cluster-expansion-tenant.yaml自己就没遵守v1 池用standard-ssd、v2 池用fast-ssd。扩容加池时按 README 的口径来别照着那份示例抄。在列表层面官方 HA 示例用的是topologySpreadConstraints按topology.kubernetes.io/zone打散maxSkew: 1、whenUnsatisfiable: DoNotSchedule。这种写法对 StatefulSet 要小心编排器资源不够时直接不满足约束会让 Pod 排队不动宁可用ScheduleAnyway先跑起来。STS 那部分要单独配 TLS0.0.6 新增的 Kubernetes STS配合 PolicyBinding 让工作负载拿临时凭证。思路是调用方在集群里用 ServiceAccount 向 Operator 的 STS 换一套短时 S3 凭据策略映射写在 PolicyBinding 里。这样密钥不用长期躺在 Secret 里给每个消费方分发。问题出在传输层。release notes 里明确写了sts.tls.auto在 0.0.6 默认改成false。也就是说升级之后用 STS 的集群必须自备一个含tls.crt、tls.key、ca.crt的 TLS Secret想继续用 Operator 托管证书得显式配sts.tls.auto: true。不开 STS 的集群不受这个设置影响。这条是默认收紧但收紧得有道理托管证书意味着 Operator 要持有签发链路。另外 STS web identity 会话被限定在 12 小时以内这一版还修正了 STS 的 SigV4 查询编码。如果你的消费方拿凭证后跑长任务12 小时是刷新节点不是并发上限。同样的思路也体现在别处Console 认证改走一条显式受保护的 API 路由敏感错误信息和凭据在日志里做了脱敏TLS SAN 的生成有上界、HTTP 指标标签基数也做了约束。这些都不在功能列表里但决定了这套东西能不能进审计范围。PolicyBinding 的最小可用形态。PolicyBinding 是这套多租户里最值得单独看的一个东西但它就三个字段看 CRD 反而比看文档清楚。spec.application下有namespace和serviceaccount全小写不是serviceAccountspec.policies是策略名列表官方 CRD 上还挂了一条校验规则列表不能为空。apiVersion:sts.rustfs.com/v1alpha1kind:PolicyBindingmetadata:name:reports-readonlynamespace:storagespec:application:namespace:reportsserviceaccount:reports-apipolicies:-readonly这段的意思是允许reports命名空间里reports-api这个 ServiceAccount来换取绑定了readonly策略的临时凭证。被绑定的策略必须已经在 RustFS 里存在并且能解析成合法的策略文档。消费方那边要挂一个投影 tokenaudience 得和 STS 对得上volumes:-name:rustfs-sts-tokenprojected:sources:-serviceAccountToken:path:tokenaudience:sts.rustfs.comexpirationSeconds:3600拿到 token 之后换凭证是往 Operator 的 STS 端点发一个表单编码的请求路径里带上租户所在的命名空间和租户名TOKEN$(cat/var/run/secrets/rustfs-sts/token)curl-sS-XPOST--cacert/var/run/secrets/rustfs-sts-ca/ca.crt\https://rustfs-operator-sts.rustfs-system.svc:4223/sts/storage/rustfs-a\-HContent-Type: application/x-www-form-urlencoded\--data-urlencodeVersion2011-06-15\--data-urlencodeActionAssumeRoleWithWebIdentity\--data-urlencodeWebIdentityToken${TOKEN}\--data-urlencodeDurationSeconds3600返回里是 access key、secret key 和 session token 三个字段直接用 S3 客户端配上去就行。要注意两点。一是调用方不能靠自己在请求里塞Policy参数去扩权。官方的写法是在 Operator 能证明调用方传入的 Policy 只会收窄 PolicyBinding 已有的权限之前这类请求会被直接拒绝。所以权限的上界由 PolicyBinding 决定不在调用参数里。二是这套链路里长期密钥只有一个来源就是 Tenant 的credsSecret临时凭证是它签出来的。策略绑定改了先前签出去的凭证不会跟着失效撤权限得靠吊销那把 key 或者缩短DurationSeconds。Controller 侧的观察点在 PolicyBinding 自己的 statuscurrentState和usage.authorizations后者是累计授权次数拿来做这个绑定到底有没有人用的核对比较方便。上面这段示例抄自 Operator 官方 chart 文档里 STS 那一节写法从 STS 引入之后没变过。升级前先手动 apply 两份 CRD这是 0.0.6 最容易漏的一步而且漏了不会报错只会让你在下一个版本遇到审批卡顿。Helm 不会自动升级 chart 的crds/目录里已经装过的 CRD所以升级 Operator 之前要自己 applykubectl apply --server-side --force-conflicts\--field-managerrustfs-operator-crd-upgrade\-fhttps://raw.githubusercontent.com/rustfs/operator/0.0.6/deploy/rustfs-operator/crds/tenant-crd.yaml kubectl apply --server-side --force-conflicts\--field-managerrustfs-operator-crd-upgrade\-fhttps://raw.githubusercontent.com/rustfs/operator/0.0.6/deploy/rustfs-operator/crds/policybinding-crd.yaml漏了这一步的麻烦在于它不一定报错。官方给的理由是先 apply 集群级 CRDAPI server 才会接受新版本 Operator 引入的字段。没 apply 的时候常见的几种表现是新字段写进去了没有反应。CRD schema 还是旧的那份Operator 读到的对象里没有新字段改配置不生效Tenant 状态也不往新版本走但 apply 一路顺利。反过来在别的路径上炸。schema 收严之后第一次提交带新字段的对象会被 API server 拒掉报解码错误报错信息里点的是字段名而不是你忘了升级 CRD。apply 卡住或者报冲突。CRD 是 Helm 创建的对象升级要接管它所以得用专属的--field-manager加--force-conflicts。确认是否真的更新了直接比 schema 里有哪些顶层属性最快kubectl get crd policybindings.sts.rustfs.com\-ojsonpath{.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties}|tr,\napplication和policies都在说明这份 CRD 已经是新版本。还有三处行为变化会影响现有部署。Console 变成单副本加 Recreate。因为会话是进程本地的多副本之间不共享登录态所以这一版改成 1 副本 Recreate策略。升级时控制台会短暂中断、活跃会话失效用户要重新登录S3 数据面不受影响只是管理界面闪一下。这一闪是写进 release notes 的预期行为不是故障。Operator 镜像 tag 从 latest 改成不可变的版本号。0.0.6 把 Operator 二进制、Helm chart、chart 里的 appVersion 和容器镜像版本全部对齐到 0.0.6chart 用不可变的 tag 替代了可变的latest。装的时候不显式覆盖装到的就是rustfs/operator:0.0.6。回滚要有准备。release notes 直接列了回滚要考虑的事升级前备份 Tenant 资源也备份当前的 Helm values。这两份东西在回滚时是唯一能还原现场的依据。边界得提前认官方文档在 Operator 页的开头写着RustFS Operator 目前是正在积极开发的v0.1.0预发布软件升级和租户变更要在非生产集群里先验证。这是官方口径不是我加的免责声明。同一套文档在生产拓扑那节也给过一句更实用的话单服务器示例只用于评估生产 Tenant 需要分布式存储池布局、资源请求、调度约束和不可变镜像引用。这四项在 CRD 里都有落点缺的是示例写法前两节已经逐个补上。跨 pool 的纠删码是集群级的事。官方 examples 的 README 里有一句被反复引用的架构说明所有 pool 组成一个集群数据在所有卷上做纠删码。也就是说纠删码策略不是每个池一套的配置你加池不会改变已有数据的编码方式但会改掉两件事集群总容量以及故障域的分布。老池的servers和volumesPerServer都不可变扩容量只能加池老池仍然带着它原来那份数据在原地。加完池之后要盯的是 Tenant 状态里的availableReplicas和每个池的readyReplicas滚动没结束之前不要往业务侧宣布扩容完成。滚动期间数据面会不会抖。0.0.6 的 release notes 对控制台的口径是S3 数据面流量不受影响这句话本身没问题但同一个文档也写了已解析的 RustFS 镜像或生成的 Pod 模板发生变化时存量 Tenant 会滚动。Pod 模板一变就是整池滚动数据面在滚动窗口里是副本数下降的状态。较新版本的 Operator chart 文档把这层说得更具体单副本租户重启期间不可用多副本租户临时降容量。所以升级窗口怎么切取决于你自己的副本分布。升级前记下当前status.availableReplicas滚动过程中盯kubectl get tenant -n storage-a -w把维护窗口放在业务低峰别和后台重建、跨池迁移这些重 IO 的动作叠在一起。环境要求也就摆在前面Kubernetesv1.30或更高Helmv3.0或更高集群要有能动态供给 PVC 的 StorageClass。你的账号还需要能创建 CRD、集群级 RBAC、Deployment 和 Service。动手顺序在测试集群按官方命令装 0.0.6确认kubectl get crd tenants.rustfs.com返回正常。先 apply 上面那两份 CRD再helm upgrade顺序别反。apply 完 CRD 顺手核一下 schema别等配置写了没反应才发现旧版挂在那儿。写一份最小 Tenant把image显式钉到rustfs/rustfs:1.0.0跑单池验证确认 Pod 安全上下文落在 Restricted 档、SA token 没挂进去。用自定义镜像的先按上一节那三条把失败形态认一遍。用上面那份 port-forward 命令连 S3 端点配rc alias set跑一次读写再叠加 pool-1 看扩容过程顺手把resources和调度约束填好扩容之后再看一次status.availableReplicas有没有回满。要用 STS 的提前备好含tls.crt/tls.key/ca.crt的 Secret或者显式打开sts.tls.auto再照那份 PolicyBinding 走一遍换凭证确认返回的三个字段能用。Operator 仓库与 0.0.6 的 release notes 在github.com/rustfs/operator文档在docs.rustfs.com.cn的 Operator 分类下四页概述、安装、多租户、存储池扩容。真正能当模板抄的是仓库里的examples目录13 份 Tenant 样例从最小单池到多区域都有资源基线和调度写法的可信出处在那里不在正文里。升级前对着 release notes 核一遍 CRD 和 STS 的行为变化这两处是唯一会静默改变旧部署的地方。
返回列表