1. 从K8ssandra到K8ssandra-Operator:一个时代的演进与迁移实战

如果你在过去几年里,一直在Kubernetes上寻找一个“开箱即用”的Apache Cassandra解决方案,那么K8ssandra这个名字你一定不陌生。它曾经是很多团队的首选,将Cassandra、Stargate、监控、备份等组件打包成一个Helm Chart,号称“生产就绪”,极大地简化了在K8s上部署和管理Cassandra的复杂度。然而,当你现在点开它的GitHub仓库,映入眼帘的首先是一个醒目的“ [DEPRECATED] ”标签。是的,这个项目已经正式被废弃,其使命由全新的 k8ssandra-operator 项目接棒。作为一个深度参与过多个基于K8ssandra项目的老兵,今天我想和你聊聊这背后的故事,更重要的是,手把手地带你走过从旧版K8ssandra平稳迁移到新Operator的完整路径,分享我踩过的坑和总结出的实战经验。

为什么一个看似成功的项目会被重构为一个Operator?核心原因在于 声明式API与自动化运维的深度需求 。原始的K8ssandra基于Helm,虽然部署简单,但在处理Cassandra这类有状态应用的复杂生命周期管理时(如节点替换、配置滚动更新、扩缩容),往往需要大量手动干预或编写复杂的脚本。Operator模式通过扩展Kubernetes API,用自定义资源(CRD)来描述应用期望状态,并由一个控制器(Controller)持续调和实际状态与期望状态,实现了真正的“Kubernetes原生”管理体验。k8ssandra-operator正是这一理念的产物,它让Cassandra集群的管理变得像管理一个Deployment一样直观和自动化。

2. 新旧架构深度对比:不仅仅是部署工具的变更

在决定迁移之前,我们必须彻底理解K8ssandra和k8ssandra-operator在架构和理念上的根本区别。这绝非简单的版本升级,而是一次架构范式的迁移。

2.1 K8ssandra (v1.x):基于Helm的“全家桶”式部署

旧版的K8ssandra本质上是一个复杂的 Helm Chart包 。它的核心是一个顶层的Chart,其 Chart.yaml 中定义了多个子Chart(Subchart)作为依赖项,包括:

  • cass-operator : 用于管理Cassandra StatefulSet。
  • stargate : 部署Stargate无状态API网关。
  • kube-prometheus-stack : 集成Prometheus和Grafana用于监控。
  • reaper-operator : 管理Cassandra的修复任务。
  • medusa-operator : 处理备份与恢复。

它的工作模式是“一次性部署与配置” 。你通过一个庞大的 values.yaml 文件配置所有组件,然后执行 helm install 。之后,如果你想修改Cassandra的资源配置、调整Stargate副本数,或者更新某个组件的配置,通常需要:

  1. 更新本地的 values.yaml
  2. 执行 helm upgrade
  3. 祈祷所有组件都能平滑升级,过程中可能需要手动介入处理Cassandra节点的滚动重启顺序。

这种模式的问题在于, 组件的耦合度较高,生命周期管理不够精细 。例如,升级监控栈(Prometheus)可能会意外触发Cassandra节点的重启,因为Helm将所有资源视为一个整体进行调和。

2.2 k8ssandra-operator:基于Operator的声明式管理

k8ssandra-operator则采用了完全不同的思路。它首先在Kubernetes集群中安装一个 Operator Pod (即控制器)。这个Operator会注册一系列自定义资源定义(CRD),其中最重要的两个是:

  • K8ssandraCluster : 用于定义整个集群的拓扑,包括Cassandra数据中心、节点数量、Stargate部署等。
  • CassandraDatacenter : 更底层的资源,通常由 K8ssandraCluster 自动创建和管理。

它的工作模式是“声明期望状态” 。你不再编写庞大的Helm values文件,而是编写一个YAML清单来描述你想要的集群样子:

apiVersion: k8ssandra.io/v1alpha1
kind: K8ssandraCluster
metadata:
  name: demo-cluster
spec:
  cassandra:
    serverVersion: "4.0.1"
    storageConfig:
      cassandraDataVolumeClaimSpec:
        storageClassName: fast-ssd
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 100Gi
    config:
      jvmOptions:
        heapSize: 2Gi
    datacenters:
      - metadata:
          name: dc1
        size: 3
        stargate:
          size: 2
          heapSize: 1Gi

写好这个YAML文件后,你只需执行 kubectl apply -f cluster.yaml 。Operator会持续监听这个资源对象,并自动驱动底层Kubernetes资源(StatefulSets, Services, Deployments等)达到你所声明的状态。如果你想扩容Stargate,只需将 spec.cassandra.datacenters[0].stargate.size 从2改为3,再次 kubectl apply ,Operator就会自动创建一个新的Stargate Pod,无需关心Helm的升级流程。

核心优势对比:

特性 K8ssandra (Helm) k8ssandra-operator
管理范式 命令式/配置驱动 声明式 /API驱动
扩缩容 需修改values并helm upgrade,过程复杂 修改CRD size字段,kubectl apply, 自动完成
配置更新 整体升级,风险较高 精细化滚动更新 ,Operator控制顺序
故障恢复 依赖K8s基础能力,复杂场景需手动 Operator内置更智能的故障检测与恢复逻辑
学习曲线 需熟悉Helm和每个子Chart配置 需理解CRD结构,更符合K8s原生思维
组件耦合 高,通过一个Chart管理 ,CRD可独立管理不同组件

注意 :迁移不仅仅是工具的更换,更是运维理念的升级。团队需要从“如何执行安装/升级命令”转向“如何定义和管理集群的期望状态”。

3. 迁移前关键准备:环境审视与数据安全策略

迁移不是一场说走就走的旅行,尤其是对于数据库这种有状态的核心服务。盲目操作是数据丢失和服务中断的直通车。在开始之前,请务必完成以下准备工作。

3.1 全面评估现有K8ssandra集群状态

首先,你需要对你的旧集群有一个完整的“体检报告”。

  1. 获取当前配置 :如果你还保留着当初部署时的 values.yaml ,那是最好的。如果没有,可以通过Helm命令获取:

    helm get values k8ssandra-release-name -n k8ssandra-namespace > old-values.yaml
    

    这份文件是你迁移时配置新集群的基准参考,特别是里面关于资源请求限制、存储类、Cassandra版本等关键配置。

  2. 记录集群拓扑 :明确你的集群规模。

    # 查看Cassandra节点(由cass-operator管理)
    kubectl get cassandradatacenter -n k8ssandra-namespace
    # 查看每个DC的节点数
    kubectl describe cassandradatacenter <dc-name> -n k8ssandra-namespace | grep -A2 -B2 "Server Nodes"
    # 查看Stargate实例数
    kubectl get deployment -l app=stargate -n k8ssandra-namespace
    
  3. 确认数据备份有效 :这是迁移的“生命线”。确保Medusa备份功能正常工作,并且最近的备份是可用的。列出备份进行验证:

    # 假设你使用k8ssandra的默认设置,通过port-forward访问Medusa服务
    kubectl port-forward svc/k8ssandra-release-name-medusa-service 8080:80 -n k8ssandra-namespace &
    # 然后访问 http://localhost:8080/api/v1/backups 查看备份列表
    

    实操心得 :在迁移前的24小时内, 务必手动触发一次全量备份 ,并确认备份文件已成功上传到你配置的对象存储(如S3、GCS)中。不要完全依赖自动备份周期。

3.2 搭建目标环境与安装k8ssandra-operator

迁移通常采用“侧挂”方式,即新旧集群并行运行一段时间。因此,你需要一个可以部署新Operator和集群的Kubernetes环境。

  1. 命名空间规划 :建议为新的Operator和集群创建独立的命名空间,例如 k8ssandra-operator-system (用于Operator本身)和 cassandra-new (用于新集群)。这有助于资源隔离和管理。

    kubectl create ns k8ssandra-operator-system
    kubectl create ns cassandra-new
    
  2. 安装k8ssandra-operator :官方推荐使用Helm安装Operator本身,这比直接应用YAML文件更方便后续升级。

    # 添加Helm仓库
    helm repo add k8ssandra https://helm.k8ssandra.io/stable
    helm repo update
    # 在独立的命名空间中安装Operator
    helm install k8ssandra-operator k8ssandra/k8ssandra-operator -n k8ssandra-operator-system --wait
    

    安装完成后,验证CRD和Operator Pod是否就绪:

    kubectl get crd | grep k8ssandra
    kubectl get pods -n k8ssandra-operator-system -l control-plane=k8ssandra-operator
    
  3. 准备新集群的存储 :仔细核对旧集群 values.yaml 中的存储配置( storageClass , size )。确保新集群要使用的命名空间(如 cassandra-new )有权限访问相同的或兼容的存储类(StorageClass)。 这是迁移后数据能否成功恢复的关键

重要提示 :如果旧集群使用了特定的 clusterName (在Cassandra内部用于节点发现),在规划新集群时, 绝对不能使用相同的 clusterName ,否则两个集群的节点会尝试互相加入,导致网络拓扑混乱。新集群应使用一个全新的、不同的名称。

4. 分步迁移实战:从旧集群到新集群的无缝切换

官方提供了迁移指南,但其中有很多细节需要结合实战经验来补充。下面是我总结的、经过验证的详细步骤。

4.1 第一步:利用Medusa将旧集群数据备份至共享存储

确保你的Medusa配置指向一个 新旧集群都能访问的共享存储位置 ,例如云厂商的对象存储(S3兼容)或一个共享的NFS路径。这是实现数据迁移的桥梁。

  1. 检查并确认Medusa配置 :在旧集群的 values.yaml 中,找到 medusa 相关的配置,特别是 storage 部分。确认 storage_provider (如 s3 )和 bucket_name prefix 等。
  2. 执行一次性备份 :虽然可能有定时备份,但为了获取一个明确的、用于迁移的基准点,手动触发一次备份更稳妥。
    # 查找一个Cassandra Pod
    CASS_POD=$(kubectl get pods -n k8ssandra-namespace -l app=cassandra -o jsonpath='{.items[0].metadata.name}')
    # 在Pod内执行Medusa备份命令
    kubectl exec -it -n k8ssandra-namespace $CASS_POD -c cassandra -- medusa backup --backup-name=migration_backup_$(date +%Y%m%d_%H%M%S)
    
    命令执行后,在Pod日志或Medusa API中确认备份成功完成。

4.2 第二步:在新集群中声明并部署一个“空”的K8ssandraCluster

根据旧集群的配置,编写你的 K8ssandraCluster CRD文件。核心要点是:

  • spec.cassandra.serverVersion 必须与旧集群的Cassandra主版本一致 (例如都是3.11.x或4.0.x),否则Medusa恢复可能失败。
  • spec.cassandra.datacenters[].size :可以先设置为和旧集群相同的节点数,也可以从较小的规模开始(如先恢复1个节点做验证)。
  • spec.cassandra.storageConfig :务必配置为与旧集群 相同或兼容的存储类 。PVC的大小至少要能容纳待恢复的数据。
  • 关键配置 :在 spec.cassandra 下配置 medusa 部分,指向 同一个备份存储位置

一个简化的新集群声明示例 ( new-cluster.yaml ):

apiVersion: k8ssandra.io/v1alpha1
kind: K8ssandraCluster
metadata:
  name: prod-cluster-new
  namespace: cassandra-new
spec:
  cassandra:
    serverVersion: "4.0.1" # 与旧集群保持一致
    storageConfig:
      cassandraDataVolumeClaimSpec:
        storageClassName: fast-ssd # 与旧集群相同或兼容
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 100Gi # 容量需足够
    config:
      jvmOptions:
        heapSize: 2Gi
    datacenters:
      - metadata:
          name: dc1
        size: 3 # 初始节点数
        stargate:
          size: 2
          heapSize: 1Gi
    # 配置Medusa,用于从旧集群备份恢复
    medusa:
      containerImage:
        repository: my-registry/medusa
        tag: latest
      storageProperties:
        storage_provider: s3_compatible
        region: us-east-1
        bucket_name: my-cassandra-backups
        prefix: k8ssandra-old-cluster/
        host: s3.amazonaws.com
        port: 443
        secure: "true"
      # 可选:配置Medusa客户端使用的Secret,包含访问密钥
      # medusaSecretRef:
      #   name: medusa-s3-credentials

应用这个配置,让Operator先创建出“空”的Cassandra节点和Stargate实例:

kubectl apply -f new-cluster.yaml -n cassandra-new

等待所有Pod进入 Running 状态,Cassandra节点完成启动(通过 nodetool status 检查)。

4.3 第三步:在新集群节点上执行Medusa数据恢复

这是迁移的核心步骤。我们需要在 新集群的每个Cassandra节点上 ,执行Medusa恢复命令,将旧集群的备份数据灌入。

  1. 获取备份名称 :通过Medusa API或命令行,列出备份,找到你为迁移创建的那个备份名称(例如 migration_backup_20231027_1430 )。
  2. 逐个节点恢复 绝对不能在所有节点上同时执行恢复 。Medusa恢复会替换当前节点的数据目录。标准做法是: a. 恢复第一个节点
    # 获取新集群第一个Cassandra Pod名
    NEW_CASS_POD=$(kubectl get pods -n cassandra-new -l k8ssandra.io/cluster=prod-cluster-new -o jsonpath='{.items[0].metadata.name}')
    # 执行恢复
    kubectl exec -it -n cassandra-new $NEW_CASS_POD -c cassandra -- medusa restore --backup-name=migration_backup_20231027_1430
    
    恢复完成后,该节点会自动重启Cassandra服务并加载恢复的数据。 b. 等待第一个节点重新加入集群 :使用 kubectl logs nodetool status 命令,确认第一个节点恢复完成并成功加入新集群(状态应为 UN - Up Normal)。 c. 依次恢复剩余节点 :对第二个、第三个节点重复上述恢复操作。每次恢复前,确保上一个节点已完全正常。

踩坑实录 :在恢复过程中,如果遇到 Failed to authenticate with existing credentials 之类的错误,很可能是因为备份中包含了 system_auth 等系统密钥空间,而新旧集群的密码或内部认证信息不同。解决方法是在恢复命令中添加 --bypass-auth 参数(如果安全策略允许),或者在恢复前确保新集群的 cassandra.yaml 中关于认证的配置与旧集群兼容。更稳妥的做法是在Medusa备份时使用 --no-backup-auth 选项排除系统认证表,迁移后再单独处理用户权限。

4.4 第四步:应用流量切换与旧集群退役

当新集群所有节点恢复完成,并且通过 nodetool status 确认所有节点状态为 UN ,集群环(ring)完整后,就可以切换流量了。

  1. 更新客户端配置 :将你的应用程序的Cassandra连接端点,从旧集群的Service(如 k8ssandra-release-name-dc1-service )切换到新集群的Service(如 prod-cluster-new-dc1-service )。对于Stargate,同理切换其Service地址。
  2. 灰度切换与验证 :如果可能,采用金丝雀发布的方式,先将一部分非关键应用的流量切到新集群,运行一段时间,验证数据读写、查询性能是否正常。使用监控工具(如Grafana)对比新旧集群的关键指标(读写延迟、错误率、吞吐量)。
  3. 旧集群资源清理 :确认新集群稳定运行至少一个业务周期(例如24小时)后,开始清理旧集群。 a. 卸载Helm Release helm uninstall k8ssandra-release-name -n k8ssandra-namespace b. 删除残留的PVC(谨慎!) :在确认备份绝对有效且新集群数据无误后,再删除旧集群的PersistentVolumeClaim以释放存储空间。 建议先备份再删除
    kubectl delete pvc -n k8ssandra-namespace -l app.kubernetes.io/managed-by=cass-operator
    

5. 迁移后常见问题排查与运维模式转变

迁移完成并非终点,基于Operator的新运维模式需要一些适应。以下是几个你大概率会遇到的问题和应对策略。

5.1 数据不一致或恢复失败

  • 问题现象 :恢复后 nodetool status 显示节点状态不为 UN (可能是 DN -Down Normal, UJ -Up Joining等),或应用读写报错。
  • 排查思路
    1. 检查版本一致性 :再次确认新旧Cassandra主版本号完全一致。即使是小版本差异(如4.0.1 vs 4.0.3),也可能导致SSTable格式不兼容。
    2. 检查备份完整性 :登录到存储备份的对象存储中,核对备份文件是否完整,特别是 meta.json manifest.json 文件是否存在。
    3. 查看Medusa日志 :详细查看恢复命令执行时的Pod日志 ( kubectl logs <pod-name> -c cassandra ),寻找具体的错误信息。
    4. 检查磁盘空间 :确保新集群PVC的存储空间大于备份数据的总量。
  • 解决技巧 :如果恢复中途失败,可以清理该节点的PVC并重新创建Pod,然后针对该节点单独重新执行恢复流程。Operator会重新调度一个新的Pod。

5.2 新集群性能或行为与预期不符

  • 问题现象 :迁移后,查询变慢,或某些特定查询报错。
  • 排查思路
    1. 对比配置 :仔细比对旧 values.yaml 和新 K8ssandraCluster YAML中的所有配置项,特别是 cassandra.yaml 的重写配置、JVM堆内存( heapSize )、线程池参数等。Operator的配置路径可能与Helm不同。
    2. 检查资源限制 :确保新集群Pod的CPU/内存 limits requests 设置不低于旧集群,特别是Stargate节点,其资源不足会直接影响API性能。
    3. 验证拓扑 :确认新集群的 datacenters rack 配置(如果有)与旧集群的逻辑拓扑一致,这会影响数据复制和查询路由。
  • 解决技巧 :充分利用Operator的优势,你可以动态调整配置。例如,调整JVM参数后,直接更新 K8ssandraCluster CR并 apply ,Operator会以安全的方式(如逐个节点)滚动重启Cassandra容器应用新配置,无需手动操作。

5.3 如何执行日常运维操作

这是从Helm到Operator思维转变的最大体现。

  • 扩缩容Cassandra节点 :直接修改 spec.cassandra.datacenters[0].size 字段,然后 kubectl apply 。Operator会自动创建或删除StatefulSet中的Pod,并处理Cassandra节点的加入(bootstrap)或离开(decommission)流程。
  • 升级Cassandra版本 :修改 spec.cassandra.serverVersion 字段。 务必查阅官方文档,确认目标版本与当前版本的升级路径是否被支持 。Operator会执行滚动升级。
  • 调整资源 :修改 spec.cassandra.resources spec.cassandra.datacenters[0].stargate.resources ,应用后Operator会滚动更新Pod。
  • 修改存储 警告:修改存储类或PVC大小通常不能在线进行 。这需要复杂的、有状态的数据迁移操作,目前Operator可能无法完全自动化处理,需要制定详细的手动迁移方案。

我个人在实际迁移和后续运维中的体会是 ,k8ssandra-operator确实将Cassandra在K8s上的管理体验提升了一个维度。它把我们从繁琐的 nodetool 命令和自定义运维脚本中解放出来,让我们能更专注于定义“我们想要什么样的数据库集群”。当然,这种转变要求运维团队对Kubernetes的CRD和Operator模式有更深的理解。最初的学习曲线是存在的,但一旦熟悉,其带来的运维效率提升和风险降低是显而易见的。最后一个小建议:在将新Operator用于核心生产环境之前,务必在测试环境中用接近生产的数据量和负载进行充分的演练,熟悉每一个配置变更的操作和回滚流程。

更多推荐