Lab 03 - Managing Appache Casandara with Portworx Snapshots
Understanding Apache Cassandra
Apache Cassandra is a distributed NoSQL database designed to manage large amounts of data across multiple servers, providing high availability and fault tolerance. It ensures there is no single point of failure by spreading data evenly across the servers in the network.
Understanding 3DSnap
A 3DSnap
(Three-Dimensional Snapshot) allows you to take consistent snapshots across multiple volumes in your cluster simultaneously. This is particularly useful for distributed applications like Cassandra, which use multiple volumes for their data.
By using 3DSnap, you can ensure that all volumes in a Cassandra cluster are snapped at the exact same point in time. This guarantees consistency across the entire dataset, making recovery easier and preventing issues that can arise from having data at different points in time. This level of consistency is crucial for proper recovery or cloning of the cluster.
To enable this feature in a production environment, you would add the fg=true
parameter to your StorageClass
, which ensures that Portworx places each volume and its replicas on separate nodes. This prevents a failover scenario where volumes might end up on the same node, thus improving reliability and performance.
In this lab, we’ll deploy Cassandra on OpenShift using Portworx for dynamic storage provisioning.
Step 1: Create a Portworx Volume for Cassandra
Before deploying Cassandra, we need to create a Portworx PersistentVolumeClaim
(PVC).
To do that, we first need a StorageClass, which defines the type of storage available.
Create StorageClass
Take a look at the storage class configuration below:
-
The replication factor (
repl
) is set to 2 to ensure high availability and accelerate Cassandra node recovery. -
The
group
parameter is used to create a group name for Cassandra, allowing for consistent 3DSnaps across the cluster. -
In a larger production cluster, you would also add the parameter
fg=true
to ensure that Portworx places each Cassandra volume and its replicas on separate nodes, avoiding a scenario where Cassandra fails over to a node where it already runs.
For a 3-volume group and 2 replicas, a minimum of 6 worker nodes is required to ensure redundancy. |
Create the storage class using:
cat <<EOF | oc apply -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: px-storageclass
provisioner: pxd.portworx.com
parameters:
repl: "2" # Sets replication factor
priority_io: "high" # Prioritizes I/O for critical operations
group: "cassandra_vg" # Volume group name for 3DSnap consistency
EOF
Now that we have the StorageClass created, let’s deploy Cassandra.
Step 2: Deploy Cassandra StatefulSet
In this step, we will deploy a 3-node Cassandra application using a StatefulSet
. To learn more about StatefulSets, see here.
Create the Cassandra StatefulSet
First, let’s configure our Security Context Contraints to allow anyuid
oc adm policy add-scc-to-user anyuid -z default -n default
Create a Cassandra StatefulSet
that uses a Portworx PVC.
cat <<EOF | oc apply -f -
apiVersion: v1
kind: Service
metadata:
labels:
app: cassandra
name: cassandra
spec:
clusterIP: None # Headless service for StatefulSet
ports:
- port: 9042 # Cassandra query language port
selector:
app: cassandra
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
spec:
serviceName: cassandra
replicas: 1 # Start with a single replica
selector:
matchLabels:
app: cassandra
template:
metadata:
labels:
app: cassandra
spec:
schedulerName: stork # Use stork scheduler for efficient placement
terminationGracePeriodSeconds: 1800 # Graceful termination for Cassandra
containers:
- name: cassandra
image: gcr.io/google-samples/cassandra:v14
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:
privileged: true
capabilities:
add:
- IPC_LOCK
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "PID=\$(pidof java) && kill \$PID && while ps -p \$PID > /dev/null; do sleep 1; done"] # Graceful shutdown
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: CASSANDRA_AUTO_BOOTSTRAP
value: "false"
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
exec:
command:
- /bin/bash
- -c
- ls
initialDelaySeconds: 15
timeoutSeconds: 5
volumeMounts:
- name: cassandra-data
mountPath: /cassandra_data
volumeClaimTemplates:
- metadata:
name: cassandra-data
spec:
storageClassName: px-storageclass # Reference to the Portworx StorageClass
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: cqlsh
spec:
containers:
- name: cqlsh
image: mikewright/cqlsh
command:
- sh
- -c
- "exec tail -f /dev/null"
apiVersion: stork.libopenstorage.org/v1alpha1
kind: Rule
metadata:
name: cassandra-presnap-rule
rules:
- podSelector:
app: cassandra
actions:
- type: command
value: nodetool flush
EOF
The above configuration uses a headless service to expose the StatefulSet.
PVCs are dynamically created for each member of the StatefulSet based on volumeClaimTemplates
.
Step 3: Verify Cassandra Pod is Ready
To monitor the Cassandra pod until it’s ready, use the following command:
watch oc get pods -o wide
This may take a few minutes. When the cassandra-0
pod is in STATUS Running
and READY 1/1
, hit ctrl-c
to exit.
Step 4: Inspect the Portworx Volume
Next, inspect the underlying volumes for our Cassandra pod:
pxctl volume inspect $(oc get pvc | grep cassandra | awk '{print $3}')
Look for:
-
State
: Indicates the volume is attached and shows the node. -
HA
: Number of configured replicas. -
Labels
: PVC name associated with the volume. -
Replica sets on nodes
: Portworx nodes with volume replicas.
Step 5: Create a Table and Insert Data
Start a CQL Shell session:
oc exec -it cqlsh -- cqlsh cassandra-0.cassandra.default.svc.cluster.local --cqlversion=3.4.4
If you receive a traceback error, the Cassandra pod might not be ready yet. Wait a few seconds and try again. |
Create a keyspace and insert some data:
CREATE KEYSPACE portworx WITH REPLICATION = {'class':'SimpleStrategy','replication_factor':3};
USE portworx;
CREATE TABLE features (id varchar PRIMARY KEY, name varchar, value varchar);
INSERT INTO portworx.features (id, name, value) VALUES ('px-1', 'snapshots', 'point in time recovery!');
INSERT INTO portworx.features (id, name, value) VALUES ('px-2', 'cloudsnaps', 'backup/restore to/from any cloud!');
INSERT INTO portworx.features (id, name, value) VALUES ('px-3', 'STORK', 'convergence, scale, and high availability!');
Step 6: Flush Data to Disk
oc exec -it cassandra-0 -- nodetool flush
Flushing data to disk ensures data persistence for failover tests.
Step 7: Simulate Node Failure and Verify Failover
Cordon the node where Cassandra is running:
oc delete pod $(oc get pods -l app=cassandra -o wide | grep -v NAME | awk '{print $1}')
Delete the Cassandra pod:
oc delete pod $(oc get pods -l app=cassandra -o wide | awk 'NR>1 {print $1}')
This will cause Kubernetes to reschedule the pod on another node.
To verify the new pod is running:
watch oc get pods -l app=cassandra -o wide
Once the new pod is Running
and READY(1/1)
, press ctrl-c
to exit.
Uncordon the node:
oc adm uncordon ${NODE}
Step 8: Verify Data Availability After Failover
Start a CQL Shell session again:
oc exec -it cqlsh -- cqlsh cassandra-0.cassandra.default.svc.cluster.local --cqlversion=3.4.4
Select rows from the keyspace:
SELECT id, name, value FROM portworx.features;
Verify that the data is still present, which confirms that failover was successful.