使用Docker和Kubernetes创建Cassandra集群

首先

随着Docker容器技术的普及,Apache Cassandra也已经在常见的Docker Hub仓库中注册为Cassandra仓库。你只需要简单地运行以下docker命令:
$ docker run -d cassandra
即可轻松地在不到一分钟的时间内启动一个Cassandra单节点。非常方便。

考虑到Cassandra是一个分布式数据库的最大特点,自然而然地希望尝试使用多个Pod进行集群配置作为下一步,对吗?
所以,在之前提到的Cassandra存储库的”Make a cluster”部分中,写道如下。

有两种集群场景:在同一台机器上的实例和在不同机器上的实例。

原文:なるほど、クラスタ構成に挑むには、①同じマシンに複数のCassandraを作るか、②異なるマシンにCassandraを作るかの2通りあるよ、ということですね。

中文翻译:嗯,想要挑战集群架构,有两种选择:一是在同一台机器上创建多个Cassandra数据库,二是在不同的机器上创建Cassandra数据库。

如果只是个人享受,那么选择方案①可能会更简单…但如果考虑到商业使用,为了更实际的结构,我们选择方案②。但从这个点开始,许多人可能不再觉得Cassandra的生活有趣了。

在本篇文章中,我们将介绍如何建立Cassandra集群,假设读者对Docker和Kubernetes有基本了解,但对Cassandra并不十分了解。

首先,使用Docker来创建Cassandra集群。

随着阅读的进行

对于不同的机器(即云上的两个虚拟机),您需要告诉Cassandra要向其他节点广播哪个IP地址(因为容器的地址位于Docker桥后面)。
假设第一台机器的IP地址为10.42.42.42,第二台机器的IP地址为10.43.43.43,请使用暴露的Gossip端口来启动第一台机器:
$ docker run –name some-cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.42.42.42 -p 7000:7000 cassandra:tag

只需要一个选项:
有两个虚拟机(以下简称VM1和VM2),这部分有点字不够多,但如果能揭示其中隐含的意思,它的意思就是

    • そもそもCassandraの仕組みとして、VM1とVM2にあるCassandraがお互いに通信しながら協調動作することで1つのクラスタとして動作するようになっている。

 

    • だからお互い相手のIPアドレスを知っている必要がある。ただしコンテナ内での内部IPアドレスではPod間の通信には使えないので、通信したい相手のコンテナが動作している仮想マシン自体のIPアドレスであることに注意する。例えば、VM1のIPアドレスを10.42.42.42、VM2は10.43.43.43とすると、VM1のCassandraは10.43.43.43に対して通信し、VM2のCassandraは10.42.42.42に対して通信する。

 

    • また、Cassandra同士の通信の他にも、一般的にVM1、VM2以外の仮想マシンで動作するクライアントからINSERTやSELECTなどの要求通信があり、このクライアントに対しても内部IPアドレスではなく、やはり仮想マシン自体のIPアドレスを伝える必要がある。

 

    • そうすると、例えばVM1のCassandraにしてみれば、コンテナ内で割り当てられた内部IPアドレスで実際は動作しつつも、以下のことをする状況になる。

 

    • ① VM2など外部のCassandraに対しては10.42.42.42に向けて通信してねと伝える(broadcastする)

 

    • ② クライアントに対しても10.42.42.42に向けて通信してねと伝える(broadcastする)

 

    • この状況を実現するためにCassandraの設定ファイル「cassandra.yaml」の出番となり、その中で上記①に相当する設定項目が「broadcast_address」、上記②に相当する設定項目が「broadcast_rpc_address」になります。そしてDocker Hubに登録されているCassandraリポジトリでは、特別にこの2つの項目を1つの環境変数で同時に設定できるようにしてあり、これが「CASSANDRA_BROADCAST_ADDRESS」という環境変数となる。(使用できる環境変数については「Configuring Cassandra」の項に記載があり、同様のことがさらっと触れられています)

 

    ちなみに、1台構成の時には不要だったCassandra同士の協調動作が必要になったことで、相手の死活情報を知るための通信(これが文中に出てくる「gossip」)やデータのやり取りに使われるポート番号をDockerに追加で伝える必要もある。このポート番号はcassandra.yamlファイル内でデフォルトで7000として指定されていることで、コンテナ内では7000番ポートで待ち受けており、今回は仮想マシン間でも同じ7000番ポートを使用することにする。

作为这些结果的一部分,添加了对环境变量“-e CASSANDRA_BROADCAST_ADDRESS=10.42.42.42”的指定和对端口“-p 7000:7000”的指定。

$ docker run --name some-cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.42.42.42 -p 7000:7000 cassandra:tag

这样一条命令就完成了。

第二个Cassandra节点的关键是”seed”。

如果能走到这一步,后面就简单了。
由于第一台Cassandra已经启动,继续阅读即可。

然后在第二台机器上启动一个Cassandra容器,将公开的gossip端口和种子指向第一台机器:
$ docker run –name some-cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.43.43.43 -p 7000:7000 -e CASSANDRA_SEEDS=10.42.42.42 cassandra:tag

在这里,新出现的“seed”是指在启动第二个Cassandra节点(VM2)并使其加入集群时,用于指示集群所在位置的目标。由于此次只有VM1的Cassandra节点存在,所以其IP地址为10.42.42.42。VM2的Cassandra节点从这里获取有关集群的信息,并在确认自己是集群中的第二个节点后进行启动处理。

Cassandra.yaml文件中指定了种子节点,而在Docker Hub的Cassandra存储库中,可以通过环境变量”CASSANDRA_SEEDS”来指定,因此我们将添加”-e CASSANDRA_SEEDS=10.42.42.42″。
此外,根据VM2向VM1进行广播的地址为10.43.43.43,因此我们需要将其修改为”-e CASSANDRA_BROADCAST_ADDRESS=10.43.43.43″。

因此产生了这些结果。

$ docker run --name some-cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.43.43.43 -p 7000:7000 -e CASSANDRA_SEEDS=10.42.42.42 cassandra:tag

这将成为一个命令。

第三台开始以后怎么办呢…!

Cassandra可以通过QUORUM的思想实现强一致性(而非结果一致性)和容灾能力,通过构建至少三个节点的集群,即使有一台节点故障(只剩下两台节点,超过半数),也能一直读取到最新的数据。遗憾的是在Docker Hub的网站上只提到了前两台节点的描述,但为了让Cassandra发挥其本身的能力,我也会说明如何构建三个或更多节点的集群。

实际上,这个方法很简单,就像第二台一样。也就是说,第三台是VM3,它的IP地址是10.44.44.44,按以下方式设置。

$ docker run --name some-cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=10.44.44.44 -p 7000:7000 -e CASSANDRA_SEEDS=10.42.42.42 cassandra:tag

只需要一种选项:与第二个台相同,只需指定用于向其他Cassandra广播的IP地址和用于获取参与目标集群信息的IP地址。从第四个台开始也是一样的。

如何指定多个种子?

如果VM1停止,它将无法扮演种子节点的角色,因此将无法将新的Cassandra容器添加到集群中。这样一来,Cassandra的优势就被毁了,因为原本没有单点故障(SPoF)。

实际上,种子不一定是VM1的IP地址,只要是参与的集群中的VM的IP地址即可。此外,由于可以指定多个IP地址,在正式运营中为了应对VM1的故障,通常会指定多个IP地址。但是在这种情况下,之前介绍的$ docker run的内容会稍有变化。

假设我们想要在VM1~VM5的五台虚拟机上组建一个集群。另外,我们决定将VM1~VM3作为种子节点。为了实现这一点,我们只需要将IP地址用逗号分隔排列起来,例如:

-e CASSANDRA_SEEDS=[VM1的IP地址],[VM2的IP地址],[VM3的IP地址]

你可以通过在每个虚拟服务器VMx(x=1,2,3,4,5)上以相同的格式启动Cassandra容器来构建集群。

$ docker run --name some-cassandra -d -e CASSANDRA_BROADCAST_ADDRESS=[VMxのIPアドレス] -p 7000:7000 -e CASSANDRA_SEEDS=[VM1のIPアドレス],[VM2のIPアドレス],[VM3のIPアドレス] cassandra:tag

然而,作为Cassandra的种子节点,在首次启动时不会自动搜索参与的集群,因此必须始终从最初指定为种子节点的虚拟机上启动Cassandra。启动后,通过gossip机制,集群信息将在所有Cassandra节点之间共享和存储,因此在第二次及以后的启动中,启动顺序不再重要。

为了避免基于种子信息运行的流言通信过于频繁,以下是一些注意事项。

    • seedにクラスタの全てのIPアドレスを指定しないようにする。(3個程度を目安)

 

    seedの指定内容はクラスタの全てで同じにしておく。

在Kubernetes中也有Cassandra集群。

接下来我们来谈谈Kubernetes。在Kubernetes上使用多个Pod构建Cassandra集群,基本上可以简单地使用StatefulSet,但是在官方网站的教程中只有英文版本,几乎找不到日文信息!因此,这次我们将按照官方网站的内容进行解释。

有很多内容,但关键步骤只有两个:
1. 创建 Headless 服务。
2. 创建 StatefulSet。
这就是简单的方法。

创建无头服务

这是关于“创建Cassandra无头服务”教程的部分。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: cassandra
  name: cassandra
spec:
  clusterIP: None
  ports:
  - port: 9042
  selector:
    app: cassandra

在创建普通服务时,如果指定clusterIP: None,则该服务将成为一个无头服务;实际上,这个服务不会分配集群IP。

$ kubectl get svc cassandra
NAME        TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)    AGE
cassandra   ClusterIP   None         <none>        9042/TCP   45s

由于Pod的副本之间没有差异,因此与通常的无状态服务不同的是,StatefulSet通过对Pod(和相应的卷)进行单独管理,因此不再需要代表IP地址,只需将IP地址加载到副本上进行负载平衡即可。9042端口与Docker时相同,用于Cassandra客户端查询时使用的端口。

创建 StatefulSet

在StatefulSet的说明页面中有详细的描述,但总结起来

    • Pod単位でユニークなホスト名を用意してくれる

 

    • Pod単位でいつも同じ永続ストレージを接続してくれるので同じデータを使用できる

 

    Podの起動や停止を、Podに割り振られた連番を使用していつも同じ順番で実施してくれる

具备这个特点的部署方式。
在教程”使用 StatefulSet 创建 Cassandra 环”中,情况如下。

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: cassandra
  labels:
    app: cassandra
spec:
  serviceName: cassandra
  replicas: 3
  selector:
    matchLabels:
      app: cassandra
  template:
    metadata:
      labels:
        app: cassandra
    spec:
      terminationGracePeriodSeconds: 1800
      containers:
      - name: cassandra
        image: gcr.io/google-samples/cassandra:v13
        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
        lifecycle:
          preStop:
            exec:
              command: 
              - /bin/sh
              - -c
              - nodetool drain
        env:
          - name: MAX_HEAP_SIZE
            value: 512M
          - name: HEAP_NEWSIZE
            value: 100M
          - name: CASSANDRA_SEEDS
            value: "cassandra-0.cassandra.default.svc.cluster.local"
          - name: CASSANDRA_CLUSTER_NAME
            value: "K8Demo"
          - name: CASSANDRA_DC
            value: "DC1-K8Demo"
          - name: CASSANDRA_RACK
            value: "Rack1-K8Demo"
          - name: POD_IP
            valueFrom:
              fieldRef:
                fieldPath: status.podIP
        readinessProbe:
          exec:
            command:
            - /bin/bash
            - -c
            - /ready-probe.sh
          initialDelaySeconds: 15
          timeoutSeconds: 5
        # These volume mounts are persistent. They are like inline claims,
        # but not exactly because the names need to match exactly one of
        # the stateful pod volumes.
        volumeMounts:
        - name: cassandra-data
          mountPath: /cassandra_data
  # These are converted to volume claims by the controller
  # and mounted at the paths mentioned above.
  # do not use these in production until ssd GCEPersistentDisk or other ssd pd
  volumeClaimTemplates:
  - metadata:
      name: cassandra-data
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: fast
      resources:
        requests:
          storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
  type: pd-ssd

类型:StatefulSet是指定的StatefulSet,其他部分与常规的Deployment类似的结构。虽然有点长,但接下来我们将看看与Cassandra相关的要点。

复制品:3个

请指定Cassandra集群的Pod数量。由于是数据库,因此比一般的网络应用程序消耗更多资源。随着数量的增加,将需要更多的Kubernetes资源,请注意。

图像: gcr.io/google-samples/cassandra:v13

与Docker时的情况不同,这次使用了Google的注册表中准备的Cassandra仓库。基本上是相同的,但如下所述,配置略有不同。

容器端口:

・7000
7000号端口被用于Cassandra之间进行通信的传递八卦和数据等操作。

7001端口用于进行Cassandra之间的SSL/TLS加密通信。
在本次仓库的默认设置中,加密设置已关闭,所以您实际上不需要担心。要开启加密,请在容器内的/etc/cassandra/cassandra.yaml文件中将internode_encryption设置为”all”(默认情况下为”none”)。

7199号端口是JMX监控端口,主要用于nodetool这个工具,以了解Cassandra的状态。

・9042

9042端口被用于接收客户端发出的INSERT、SELECT等请求。

IPC_LOCK 可以理解为进程间通信的一种机制,用于保持资源的访问权。

IPC_LOCK是为了防止内存数据被交换出去而存在的。
顺便提一下,如果不使用Kubernetes或Docker而使用Cassandra,通常会执行$ swap -a off命令,并编辑fstab永久禁用交换空间。

执行nodetool drain命令

由于在 preStop 中指定了,所以在 Pod 停止之前会执行 nodetool drain。
这会指示 Cassandra 在停止接收请求的同时将内存中的数据写入文件。即使没有此操作(或者操作失败),由于存在提交日志,数据也不会丢失,但是在下次启动时需要额外的时间来重放提交日志。

姓名:

・卡桑德拉种子

CASSANDRA_SEEDS是Docker中seed的表示,它会将cassandra.yaml文件中的seeds设置为指定的值。
StatefulSet的特点是会为创建的Pod分配以0为起始的递增编号,形成的主机名格式为
[StatefulSet名称]-[编号].[服务名称].[命名空间].svc.cluster.local。
因此,在seed参数中指定的是最先创建的Pod的主机名,即「cassandra-0.cassandra.default.svc.cluster.local」。

当然,就像在Docker时所解释的那样,也可以指定多个,只需要用逗号分隔,例如”cassandra-0.cassandra.default.svc.cluster.local,cassandra-1.cassandra.default.svc.cluster.local”就可以了。

・卡桑德拉集群名称

只需要一种选择:

在Cassandra集群中,我们需要给集群取个名字,可以通过设置cassandra.yaml文件中的cluster_name来完成。如果在种子节点中指定了具有不同cluster_name设置的Cassandra,那么它们将被认为是不同的集群,无法加入到同一集群中。

・CASSANDRA_DC -> 卡珊德拉数据中心

Cassandra集群内可以拥有多个逻辑数据中心,因此需要指定所属的数据中心名称。您可以在cassandra-rackdc.properties文件(/etc/cassandra/cassandra-rackdc.properties)中设置dc。内容如下:

$ cat /etc/cassandra/cassandra-rackdc.properties
dc= DC1-K8Demo
rack= Rack1-K8Demo

由于它是逻辑性的,因此可以独立于物理布局和名称进行指定。

卡桑德拉机架

设置逻辑机架名称。它会在cassandra-rackdc.properties文件中为机架进行配置。
为了提高故障容忍性,Cassandra不仅在每个逻辑数据中心之间具有数据副本,还会尽量将数据副本分配到不同的逻辑机架中。您可以在创建键空间后,在集群运行时指定在哪个逻辑数据中心上配置多少个副本。

在标准的Cassandra中(并非Google注册表版Cassandra),实际上使用的是一种称为”SimpleSnitch”的平面配置,以便在cassandra.yaml文件的endpoint_snitch中默认指定,而不涉及逻辑数据中心或逻辑机架的概念。这是为了方便验证目的而设计的,此时无需指定CASSANDRA_DC或CASSANDRA_RACK。
然而,在创建键空间时需要指定”SimpleStrategy”类,并且当复制因子设置为3时,键空间的创建CQL如下所示。

CREATE KEYSPACE mykeyspace WITH REPLICATION = { 'class': 'SimpleStrategy', 'replication_factor': 3 };

在商业使用的生产环境中,如果使用逻辑数据中心或逻辑机架,通常需要在cassandra.yaml文件的 endpoint_snitch 中设置 “GossipingPropertyFileSnitch”。然而,在此Google注册表的Cassandra仓库中,这已经在最开始就被设置好了,所以 CASSANDRA_DC 和 CASSANDRA_RACK 也被指定了。

在创建键空间时,需要指定”NetworkTopologyStrategy”类,并将虚拟数据中心名称与副本数量进行映射。例如,如果要将DC1-K8Demo的副本数量设置为3(并且还有一个名为DC2-K8Demo的虚拟数据中心,其副本数量为5),则创建键空间的CQL语句如下所示。

CREATE KEYSPACE mykeyspace WITH REPLICATION = {'class' :'NetworkTopologyStrategy', 'DC1-K8Demo' :3, 'DC2-K8Demo' : 5};

准备探查.sh

就绪探针的ready-probe.sh是在本次使用的容器镜像中特别准备的(而不是Cassandra的标准准备方式)。这是一个用来判断Cassandra启动是否完成并准备好的脚本,当返回值为0时,被视为完成状态并继续创建下一个Pod。其内容如下:

if [[ $(nodetool status | grep $POD_IP) == *"UN"* ]]; then
  if [[ $DEBUG ]]; then
    echo "UN";
  fi
  exit 0;
else
  if [[ $DEBUG ]]; then
    echo "Not Up";
  fi
  exit 1;
fi

在内部,正在执行nodetoo status。如果您尝试手动执行,您可以得到以下的输出结果。

nodetool status
Datacenter: DC1-K8Demo
======================
Status=Up/Down
|/ State=Normal/Leaving/Joining/Moving
--  Address    Load       Tokens       Owns (effective)  Host ID                               Rack
UN  10.36.0.7  99.6 KiB   32           100.0%            ee5f3194-3429-4ed2-afdb-f180e3694dc5  Rack1-K8Demo

根据参加集群的数量增加了以「UN」开头的行。
第一个字母「U」表示Cassandra处于启动状态,如果是「D」则表示停止状态。第二个字母「N」表示正常运行状态,如果不是「N」则表示状态正在发生变化。因此,如果正常启动状态,则为「UN」,如果正常停止状态,则为「DN」。
在ready-probe.sh文件中,使用Address列来确定关于自身的行,并确认其状态为「UN」。

挂载路径:/cassandra_data

標準的なCassandraでは、/var/lib/cassandraディレクトリ以下に書き込まれたさまざまなデータが保存されます。この設定はcassandra.yamlファイル内のdata_file_directories、commitlog_directory、hints_directory、saved_caches_directoryで行われます。ただし、Googleレジストリ版の場合はこれらの設定がすべて/cassandra_dataに変更されています。したがって、永続ボリュームはここにマウントする必要があります。

当然,你可以分别指定cassandra.yaml文件中的四个选项, 如data_file_directories和commitlog_directory,这并不会引起问题。但请注意,在突然停止Pod时,要确保没有丢失commitlog,因此至少需要为data_file_directories和commitlog_directory分配持久卷。

【附加内容】存储类名称:快速存储

由于Kubernetes环境可能会让您对存储类的描述内容感到困惑,特别是在Google的GKE上,将storageClassName: standard设置为简单,可能会更快且更好,可以删除kind: StorageClass以下的所有描述。(将会动态创建卷)

总结

我写下了在使用Docker和Kubernetes分别构建由多个Cassandra组成的集群时可能有参考价值的信息和问题解决点。

Apache Cassandra以及其商业版DataStax Enterprise在2018年12月5日的6.7版本中正式支持容器环境,你也可以尝试使用它们。如果是为了验证目的,你可以免费使用所有开发工具!希望这篇文章对您在容器环境中享受Cassandra有所帮助。

广告
将在 10 秒后关闭
bannerAds