30、在 Kubernetes 中运行有状态的 Cassandra 应用

在 Kubernetes 中运行有状态的 Cassandra 应用

在现代的分布式系统中,Kubernetes 已经成为了容器编排的事实标准,而 Cassandra 作为一款强大的分布式数据库,在处理大规模数据时表现出色。将 Cassandra 与 Kubernetes 集成,可以充分发挥两者的优势,实现高效、可靠的数据存储和管理。本文将详细介绍如何在 Kubernetes 中运行有状态的 Cassandra 应用,包括环境配置、容器启动脚本、与 Kubernetes 的集成以及不同的部署方式。

1. 配置 Cassandra 容器

首先,我们需要配置 Cassandra 容器,确保其能够在 Kubernetes 环境中正常运行。以下是一些关键步骤:

  1. 登录并设置权限 :登录到 Cassandra 容器,并设置 ready-probe.sh 脚本的权限。
login cassandra \
    && chown cassandra: /ready-probe.sh \
  1. 定义数据卷 :为 Cassandra 数据定义一个卷,确保数据的持久化存储。
VOLUME ["/$CASSANDRA_DATA"]
  1. 暴露端口 :暴露 Cassandra 节点之间通信以及外部访问所需的重要端口。
# 7000: 节点间通信
# 7001: TLS 节点间通信
# 7199: JMX
# 9042: CQL
# 9160: thrift 服务
EXPOSE 7000 7001 7199 9042 9160
  1. 启动脚本 :使用 dumb-init 作为容器的初始化系统,最终运行 run.sh 脚本。
CMD ["/sbin/dumb-init", "/bin/bash", "/run.sh"]
2. 解析 run.sh 脚本

run.sh 脚本负责设置 Cassandra 的运行环境和配置。以下是脚本的主要步骤:

  1. 设置本地变量 :为 Cassandra 配置文件设置本地变量。
set -e
CASSANDRA_CONF_DIR=/etc/cassandra
CASSANDRA_CFG=$CASSANDRA_CONF_DIR/cassandra.yaml
  1. 设置主机名 :如果未指定 CASSANDRA_SEEDS ,则设置主机名。
if [ -z "$CASSANDRA_SEEDS" ]; then
  HOSTNAME=$(hostname -f)
fi
  1. 设置环境变量 :设置一系列环境变量,并为其提供默认值。
CASSANDRA_RPC_ADDRESS="${CASSANDRA_RPC_ADDRESS:-0.0.0.0}"
CASSANDRA_NUM_TOKENS="${CASSANDRA_NUM_TOKENS:-32}"
CASSANDRA_CLUSTER_NAME="${CASSANDRA_CLUSTER_NAME:='Test Cluster'}"
# 其他环境变量设置...
  1. 打印变量信息 :打印一些重要的变量信息,方便调试。
echo Starting Cassandra on ${CASSANDRA_LISTEN_ADDRESS}
echo CASSANDRA_CONF_DIR ${CASSANDRA_CONF_DIR}
  1. 配置 Snitch :根据数据中心和机架信息,配置 Snitch 以优化网络拓扑。
if [[ $CASSANDRA_DC && $CASSANDRA_RACK ]]; then
  echo "dc=$CASSANDRA_DC" > $CASSANDRA_CONF_DIR/cassandra-rackdc.properties
  echo "rack=$CASSANDRA_RACK" >> $CASSANDRA_CONF_DIR/cassandra-rackdc.properties
  CASSANDRA_ENDPOINT_SNITCH="GossipingPropertyFileSnitch"
fi
  1. 内存管理 :控制 Cassandra 的最大堆大小,避免内存交换。
if [ -n "$CASSANDRA_MAX_HEAP" ]; then
  sed -ri "s/^(#)?-Xmx[0-9]+.*/-Xmx$CASSANDRA_MAX_HEAP/" "$CASSANDRA_CONF_DIR/jvm.options"
  sed -ri "s/^(#)?-Xms[0-9]+.*/-Xms$CASSANDRA_MAX_HEAP/" "$CASSANDRA_CONF_DIR/jvm.options"
fi
  1. 更新配置文件 :根据环境变量更新 Cassandra 的配置文件。
for yaml in \
  broadcast_address \
  broadcast_rpc_address \
  # 其他配置项...
  ; do
  var="CASSANDRA_${yaml^^}"
  val="${!var}"
  if [ "$val" ]; then
    sed -ri 's/^(# )?('"$yaml"':).*/\2 '"$val"'/' "$CASSANDRA_CFG"
  fi
done
echo "auto_bootstrap: ${CASSANDRA_AUTO_BOOTSTRAP}" >> $CASSANDRA_CFG
  1. 设置种子节点 :根据部署方案设置种子节点或种子提供者。
if [[ $CASSANDRA_SEEDS == 'false' ]]; then
  sed -ri 's/- seeds:.*/- seeds: "'"$POD_IP"'"/' $CASSANDRA_CFG
else
  sed -ri 's/- seeds:.*/- seeds: "'"$CASSANDRA_SEEDS"'"/' $CASSANDRA_CFG
fi
sed -ri 's/- class_name: SEED_PROVIDER/- class_name: '"$CASSANDRA_SEED_PROVIDER"'/' $CASSANDRA_CFG
  1. 远程管理和 JMX 监控 :设置远程管理和 JMX 监控选项。
if [[ $CASSANDRA_GC_STDOUT == 'true' ]]; then
  sed -ri 's/ -Xloggc:\/var\/log\/cassandra\/gc\.log//' $CASSANDRA_CONF_DIR/cassandra-env.sh
fi
echo "JVM_OPTS=\"\$JVM_OPTS -Djava.rmi.server.hostname=$POD_IP\"" >> $CASSANDRA_CONF_DIR/cassandra-env.sh
# 其他 JMX 相关设置...
  1. 启动 Cassandra :设置类路径并以 Cassandra 用户身份启动 Cassandra。
export CLASSPATH=/kubernetes-cassandra.jar
su cassandra -c "$CASSANDRA_HOME/bin/cassandra -f"
3. 连接 Kubernetes 和 Cassandra

将 Kubernetes 和 Cassandra 集成需要一些额外的工作,主要涉及种子提供者和 Snitch 的配置。

  1. Cassandra 配置解析 :Cassandra 的配置文件 cassandra.yaml 中有两个重要的设置:种子提供者和 Snitch。
    • 种子提供者 :负责发布集群中节点的 IP 地址列表,节点启动时会连接到这些种子节点以发现整个集群。默认的种子提供者是一个静态的 IP 地址列表。
      ```yaml
      seed_provider:
      • class_name: SEED_PROVIDER
        parameters:
        • seeds: “127.0.0.1”
          ```
    • Snitch :教 Cassandra 了解网络拓扑,以便高效路由请求,并将副本分布在不同的数据中心和机架上,避免相关故障。默认的 Snitch 是 SimpleSnitch
      yaml endpoint_snitch: SimpleSnitch
  2. 自定义种子提供者 :在 Kubernetes 中运行 Cassandra 节点时,Kubernetes 可能会移动节点,包括种子节点。因此,需要一个自定义的种子提供者与 Kubernetes API 服务器交互。以下是一个简单的 Java 类示例:
public class KubernetesSeedProvider implements SeedProvider {
    /**
     * 调用 Kubernetes API 收集种子提供者列表
     * @return 种子提供者列表
     */
    public List<InetAddress> getSeeds() {
        String host = getEnvOrDefault("KUBERNETES_PORT_443_TCP_ADDR", "kubernetes.default.svc.cluster.local");
        String port = getEnvOrDefault("KUBERNETES_PORT_443_TCP_PORT", "443");
        String serviceName = getEnvOrDefault("CASSANDRA_SERVICE", "cassandra");
        String podNamespace = getEnvOrDefault("POD_NAMESPACE", "default");
        String path = String.format("/api/v1/namespaces/%s/endpoints/", podNamespace);
        String seedSizeVar = getEnvOrDefault("CASSANDRA_SERVICE_NUM_SEEDS", "8");
        Integer seedSize = Integer.valueOf(seedSizeVar);
        String accountToken = getEnvOrDefault("K8S_ACCOUNT_TOKEN", "/var/run/secrets/kubernetes.io/serviceaccount/token");
        List<InetAddress> seeds = new ArrayList<InetAddress>();
        try {
            String token = getServiceAccountToken(accountToken);
            SSLContext ctx = SSLContext.getInstance("SSL");
            ctx.init(null, trustAll, new SecureRandom());
            String PROTO = "https://";
            URL url = new URL(PROTO + host + ":" + port + path + serviceName);
            logger.info("Getting endpoints from " + url);
            HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
            conn.setSSLSocketFactory(ctx.getSocketFactory());
            conn.addRequestProperty("Authorization", "Bearer " + token);
            ObjectMapper mapper = new ObjectMapper();
            Endpoints endpoints = mapper.readValue(conn.getInputStream(), Endpoints.class);
            // 其他处理...
        } catch (Exception e) {
            // 异常处理...
        }
        return Collections.unmodifiableList(seeds);
    }
}
  1. 创建 Cassandra 无头服务 :无头服务允许 Kubernetes 集群中的客户端通过标准的 Kubernetes 服务连接到 Cassandra 集群,而无需跟踪节点的网络标识。以下是配置文件示例:
apiVersion: v1
kind: Service
metadata:
  labels:
    app: cassandra
  name: cassandra
spec:
  clusterIP: None
  ports:
    - port: 9042
  selector:
    app: Cassandra
4. 使用 StatefulSet 创建 Cassandra 集群

StatefulSet 是 Kubernetes 中用于管理有状态应用的资源,它可以确保每个 Pod 都有一个稳定的网络标识和持久化存储。以下是一个创建三节点 Cassandra 集群的 StatefulSet 配置文件示例:

apiVersion: "apps/v1"
kind: StatefulSet
metadata:
  name: cassandra
spec:
  serviceName: cassandra
  replicas: 3
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      containers:
        - name: cassandra
          image: gcr.io/google-samples/cassandra:v12
          imagePullPolicy: Always
          ports:
            - containerPort: 7000
              name: intra-node
            - containerPort: 7001
              name: tls-intra-node
            - containerPort: 7199
              name: jmx
            - containerPort: 9042
              name: cql
          resources:
            limits:
              cpu: "500m"
              memory: 1Gi
            requests:
              cpu: "500m"
              memory: 1Gi
          securityContext:
            capabilities:
              add:
                - IPC_LOCK
          env:
            - name: MAX_HEAP_SIZE
              value: 512M
            - name: CASSANDRA_SEEDS
              value: "cassandra-0.cassandra.default.svc.cluster.local"
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
          readinessProbe:
            exec:
              command:
                - /bin/bash
                - -c
                - /ready-probe.sh
            initialDelaySeconds: 15
            timeoutSeconds: 5
          volumeMounts:
            - name: cassandra-data
              mountPath: /cassandra_data
  volumeClaimTemplates:
    - metadata:
        name: cassandra-data
        annotations:
          volume.beta.kubernetes.io/storage-class: fast
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 1Gi
5. 使用复制控制器部署 Cassandra

除了 StatefulSet,我们还可以使用复制控制器来部署 Cassandra。复制控制器更符合 Cassandra 的语义,因为它直接使用节点上的存储,而不是依赖于持久化存储声明。以下是复制控制器的配置文件示例:

apiVersion: v1
kind: ReplicationController
metadata:
  name: cassandra
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: Cassandra
    spec:
      containers:
        - command:
            - /run.sh
          image: gcr.io/google-samples/cassandra:v11
          name: cassandra
          resources:
            limits:
              cpu: 0.5
          env:
            - name: MAX_HEAP_SIZE
              value: 512M
            - name: HEAP_NEWSIZE
              value: 100M
            - name: CASSANDRA_SEED_PROVIDER
              value: "io.k8s.cassandra.KubernetesSeedProvider"
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
            - name: POD_IP
              valueFrom:
                fieldRef:
                  fieldPath: status.podIP
          ports:
            - containerPort: 7000
              name: intra-node
            - containerPort: 7001
              name: tls-intra-node
            - containerPort: 7199
              name: jmx
            - containerPort: 9042
              name: cql
          volumeMounts:
            - mountPath: /cassandra_data
              name: data
      volumes:
        - name: data
          emptyDir: {}
总结

通过以上步骤,我们可以在 Kubernetes 中成功运行有状态的 Cassandra 应用。无论是使用 StatefulSet 还是复制控制器,都可以根据实际需求选择合适的部署方式。同时,通过自定义种子提供者和 Snitch,我们可以确保 Cassandra 集群在 Kubernetes 环境中高效、稳定地运行。

下面是一个简单的 mermaid 流程图,展示了在 Kubernetes 中部署 Cassandra 的主要步骤:

graph LR
    A[配置 Cassandra 容器] --> B[解析 run.sh 脚本]
    B --> C[连接 Kubernetes 和 Cassandra]
    C --> D{选择部署方式}
    D --> |StatefulSet| E[使用 StatefulSet 创建集群]
    D --> |复制控制器| F[使用复制控制器部署]

表格总结不同部署方式的特点:
| 部署方式 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| StatefulSet | 提供稳定的网络标识和持久化存储 | 配置相对复杂,依赖于持久化存储声明 | 需要严格的状态管理和持久化存储的场景 |
| 复制控制器 | 更符合 Cassandra 语义,直接使用节点存储 | 节点故障时数据恢复可能较复杂 | 对存储要求不高,更注重节点本地存储的场景 |

在 Kubernetes 中运行有状态的 Cassandra 应用

6. 不同部署方式的深入分析
6.1 StatefulSet 部署方式
  • 稳定性优势 :StatefulSet 为每个 Pod 提供了稳定的网络标识和持久化存储。在 Cassandra 集群中,这意味着每个节点都有一个固定的名称和存储卷,即使 Pod 被重新调度,也能挂载回原来的存储卷,保证数据的一致性。例如,在 cassandra-0.cassandra.default.svc.cluster.local 这样的 DNS 名称中, cassandra-0 是固定的,方便节点之间的通信和数据恢复。
  • 配置复杂度 :StatefulSet 的配置相对复杂,需要定义服务名称、副本数量、Pod 模板和卷声明模板等。例如,在配置文件中需要明确指定 serviceName replicas template volumeClaimTemplates 等字段,对于初学者来说可能有一定的难度。
  • 适用场景 :适用于对数据一致性和稳定性要求较高的场景,如金融交易系统、医疗数据存储等。这些场景需要确保数据不会丢失,并且在节点故障时能够快速恢复。
6.2 复制控制器部署方式
  • 符合 Cassandra 语义 :复制控制器直接使用节点上的存储,更符合 Cassandra 设计初衷。Cassandra 本身就是为了在节点本地存储数据而设计的,通过复制控制器部署可以充分利用节点的本地存储,减少对网络存储的依赖。
  • 数据恢复挑战 :当节点发生故障时,由于使用的是 emptyDir 存储,数据恢复可能会比较复杂。因为 emptyDir 是临时存储,当 Pod 被删除或重新调度时,数据会丢失。需要依赖 Cassandra 的复制机制来恢复数据。
  • 适用场景 :适用于对存储要求不高,更注重节点本地存储性能的场景,如实时数据分析、日志存储等。这些场景对数据的实时性要求较高,而对数据的持久化要求相对较低。
7. 性能优化建议
7.1 内存管理
  • 设置最大堆大小 :通过设置 CASSANDRA_MAX_HEAP 环境变量,可以控制 Cassandra 的最大堆大小。例如,在 run.sh 脚本中使用 sed 命令修改 jvm.options 文件:
if [ -n "$CASSANDRA_MAX_HEAP" ]; then
  sed -ri "s/^(#)?-Xmx[0-9]+.*/-Xmx$CASSANDRA_MAX_HEAP/" "$CASSANDRA_CONF_DIR/jvm.options"
  sed -ri "s/^(#)?-Xms[0-9]+.*/-Xms$CASSANDRA_MAX_HEAP/" "$CASSANDRA_CONF_DIR/jvm.options"
fi

这样可以避免 Cassandra 因为内存不足而进行频繁的交换,提高性能。
- 调整新生代大小 :可以通过设置 HEAP_NEWSIZE 环境变量来调整新生代的大小,优化垃圾回收性能。

7.2 磁盘优化
  • 使用 SSD 存储 :建议使用 SSD 驱动器作为 Cassandra 的存储介质,特别是对于日志文件。SSD 的读写速度比传统硬盘快很多,可以显著提高 Cassandra 的性能。
  • 预留足够的磁盘空间 :Cassandra 在数据处理过程中会进行大量的数据洗牌、压缩和重新平衡操作,因此需要预留足够的磁盘空间。一般建议至少预留 50% 的磁盘空间,以确保数据处理的顺利进行。
7.3 网络优化
  • 合理配置种子节点 :种子节点是 Cassandra 集群中非常重要的节点,它们负责向新节点提供集群信息。合理配置种子节点的数量和位置,可以减少节点之间的通信延迟,提高集群的性能。
  • 使用高效的 Snitch :根据实际的网络拓扑和数据中心布局,选择合适的 Snitch 可以优化 Cassandra 的请求路由和副本分布,减少跨数据中心的通信。
8. 监控和故障处理
8.1 监控指标
  • JMX 监控 :Cassandra 支持 Java Management Extensions (JMX) 标准,可以通过 JMX 监控 Cassandra 的各种指标,如内存使用情况、磁盘 I/O、请求响应时间等。可以使用工具如 Cassandra OpsCenter 来监控这些指标。
  • 日志监控 :通过监控 Cassandra 的日志文件,可以及时发现异常情况,如节点故障、数据不一致等。可以使用日志收集工具如 Fluentd 或 Logstash 来收集和分析日志。
8.2 故障处理
  • 节点故障恢复 :当节点发生故障时,Cassandra 可以通过复制机制自动恢复数据。但是,在某些情况下,可能需要手动干预,如重新启动节点、调整配置等。
  • 集群扩容和缩容 :在集群扩容或缩容时,需要注意数据的重新平衡和节点的加入或退出顺序。可以使用 Cassandra 的工具如 nodetool 来进行这些操作。
9. 总结和展望

通过本文的介绍,我们了解了如何在 Kubernetes 中运行有状态的 Cassandra 应用,包括容器配置、脚本解析、与 Kubernetes 的集成以及不同的部署方式。同时,我们也探讨了性能优化、监控和故障处理等方面的内容。

在未来,随着 Kubernetes 和 Cassandra 的不断发展,我们可以期待更多的优化和改进。例如,Kubernetes 可能会提供更强大的状态管理功能,而 Cassandra 可能会进一步优化其分布式架构,提高性能和可靠性。

下面是一个 mermaid 流程图,展示了 Cassandra 集群的监控和故障处理流程:

graph LR
    A[监控指标] --> B{是否异常}
    B --> |是| C[故障处理]
    B --> |否| D[继续监控]
    C --> E{故障类型}
    E --> |节点故障| F[节点故障恢复]
    E --> |集群扩容/缩容| G[集群调整操作]
    F --> D
    G --> D

表格总结性能优化的关键要点:
| 优化方面 | 具体措施 |
| ---- | ---- |
| 内存管理 | 设置最大堆大小和新生代大小 |
| 磁盘优化 | 使用 SSD 存储,预留足够磁盘空间 |
| 网络优化 | 合理配置种子节点,使用高效 Snitch |

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值