Today’s lab session focuses on Apache Cassandra. You will install it, run it, and learn the basic commands needed to containerise and deploy a small Cassandra NoSQL cluster.
Lab 9 focuses on how to:
- Configure Cassandra containers on GCP VMs.
- Deploy and run a Cassandra cluster.
- Use basic Cassandra CLI commands to explore and manage data.
- Interact with Cassandra using Python and Node.js
-
To run this tutorial, you will need a a GCP VM with the Ubutnu as in the previous labs, but make sure it has at least 8 GB of RAM and 30 GB of disk space (e.g., an
e2-standard-2). This is required because we will be running multiple Cassandra containers on the same VM. -
Start a new terminal session on your VM and run the following commands. Make sure you understand each command, do not just copy and paste.
-
Update the system:
sudo apt-get update💡Notes:
-
sudo apt-get update
downloads the latest package information from all configured sources. -
These sources are usually listed in/etc/apt/sources.listand/etc/apt/sources.list.d/`. -
This step ensures your system knows about updated packages and dependencies.
- Install Docker. Type
Ywhen prompted:
sudo apt-get install docker.ioDocker is now installed on your VM.
We only need to install it once—after that we can run as many containers as we like.
- Check the Docker version:
sudo docker --version- Create a new user called
docker-user, which we will use to run our containers:
sudo adduser docker-userSet a password when prompted.
You may press Enter for the additional fields and type Y at the end.
- Add the new user to the
sudogroup:
sudo usermod -aG sudo docker-userWithout this, docker-user will not be allowed to run sudo commands.
- Allow
docker-userto run Docker commands without needingsudo:
sudo usermod -aG docker docker-user- Switch to the new
docker-user:
su - docker-userThe - ensures you switch fully into the user's environment and home directory.
- Test that Docker works:
dockerYou should see a full list of Docker commands and options. This confirms that your setup is ready.
- Our next step is to create a create a Cassandra container. Let's start a new Cassandra node using Docker.
We will name the container
my-cassandra-1:
docker run --name my-cassandra-1 -m 2g -d cassandra:3.11The -m 2g option assigns 2 GB of memory to this container.
- We have now created an Apache Cassandra container called
my-cassandra-1. Check the active containers by running:
docker ps -aYou should see the container listed as running.
- Before creating our cluster, let's stop the container:
docker stop my-cassandra-1 - Then remove it:
docker rm my-cassandra-1 -
We will now create a cluster of three Cassandra nodes.
The first node will be called
cassandra-1, and for this tutorial we will use Cassandra 3.11.
-
We will interact with the cluster using
nodetool. -
nodetoolis a command-line tool for managing a Cassandra cluster.All Cassandra nodes work together as a single distributed database system.
docker run --name cassandra-1 -d cassandra:3.11- Use
docker ps -ato check if your container is up and running. It is good practice to verify each container after creation. Let’s inspectcassandra-1:
docker inspect cassandra-1- The output contains all configuration details of your container. To extract only the IP address, use:
docker inspect --format='{{ .NetworkSettings.IPAddress }}' cassandra-1
# Example output:
# 172.17.0.2-
There are different ways to configure a Cassandra cluster. The most common approach is to link nodes using IP addresses. Since all our containers run on the same VM, we can simply use their container names.
Before proceeding, ensure that
cassandra-1is running:
docker ps -a🚨 Container creation may fail for various reasons.
If that happens, stop it (docker stop <name>) and remove it (docker rm <name>), then recreate the container.
If you want to learn how to create Cassandra clusters across different VMs or servers, check Appendix A.
- Use
nodetoolincassandra-1to verify that our first node is healthy:
docker exec -i -t cassandra-1 bash -c 'nodetool status'If you see:
Error: The node does not have system_traces yet, probably still bootstrapping
This means the container is still starting up. Wait a few moments.
Expected output:
Datacenter: datacenter1 ======================= Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UN 172.17.0.2 100.22 KiB 256 100.0% abc2a2ee-9bff-415f-8cd0-f8a19295e846 rack1The
UNstatus means the node is Up and Normal.
- Now let’s create the second Cassandra node,
cassandra-2:
docker run --name cassandra-2 -d --link cassandra-1:cassandra cassandra:3.11The --link cassandra-1:cassandra option links cassandra-2 to cassandra-1, allowing them to form a cluster.
- Check the cluster status again:
docker exec -i -t cassandra-1 bash -c 'nodetool status'Sample output:
Datacenter: datacenter1 ======================= Status=Up/Down |/ State=Normal/Leaving/Joining/Moving -- Address Load Tokens Owns (effective) Host ID Rack UJ 172.17.0.3 30.47 KiB 256 ? bff8c5c1-8af3-4eb9-bfce-a6f90c049972 rack1 UN 172.17.0.2 70.9 KiB 256 100.0% abc2a2ee-9bff-415f-8cd0-f8a19295e846 rack1
UJmeans Up / Joining — still syncing.- The
?inOwnsis normal during bootstrapping.- Wait 1–2 minutes and run the command again.
After syncing
UN 172.17.0.3 70.92 KiB 256 100.0% bff8c5c1-8af3-4eb9-bfce-a6f90c049972 rack1 UN 172.17.0.2 75.93 KiB 256 100.0% abc2a2ee-9bff-415f-8cd0-f8a19295e846 rack1
- Before creating a third container, check your VM's memory:
freeExample:
total used free shared buff/cache available Mem: 4022808 3368560 117324 1076 536924 436672 Swap: 0 0 0
- With 3.36 GB used out of 4 GB, adding another node may fail.
- Scale your VM to 8 GB RAM, restart it, and reconnect using:
su - docker-user
- Run
freeagain to confirm available memory.
- Start the existing containers again:
docker start cassandra-1 cassandra-2- Recheck the cluster:
docker exec -i -t cassandra-1 bash -c 'nodetool status'- Now add the third Cassandra node:
docker run --name cassandra-3 -d --link cassandra-1:cassandra cassandra:3.11- Check all active containers:
docker ps -aYou should see three running containers:
cassandra-3 cassandra-2 cassandra-1If any container is
Exited, delete it and recreate it.
- Run
nodetoolagain (from any node):
docker exec -i -t cassandra-2 bash -c 'nodetool status'Now you should see all three nodes in the cluster:
UN 172.17.0.3 ... UN 172.17.0.2 ... UN 172.17.0.4 ...
- You may need to wait until all nodes reach
UN.- Cassandra uses a gossip protocol for cluster coordination.
- You can add more nodes, but remember each container uses CPU/RAM.
- Great! We now have a fully working 3-node Cassandra cluster running in Docker! 🎉
-
Now it is time to learn the basic Cassandra commands.
- We will interact with the cluster using the
cqlshcommand-line interface. This tool allows us to create keyspaces (databases), tables, and insert or query records. - We will run the tool inside
cassandra-1.
docker exec -it cassandra-1 bash -c 'cqlsh'
After running this command, you are inside the cqlsh CLI.
cqlsh> - We will interact with the cluster using the
-
Let’s create a database (called a KEYSPACE in Cassandra).
Our keyspace will be named music_store.
- Run this inside
cqlsh:
CREATE KEYSPACE music_store WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 3 };SimpleStrategyis a basic replication strategy used for single-datacenter setups.replication_factor = 3means our data will be copied across all three nodes.
- Run this inside
-
Select (USE) the keyspace as the active database:
USE music_store;You should now see:
cqlsh:music_store> -
Let’s create a table and insert some simple data.
CREATE TABLE music_store.music_by_category ( type text, category text, id UUID, name text, title text, PRIMARY KEY (type, id) );UUIDgenerates unique IDs automatically.
-
Insert a record:
INSERT INTO music_store.music_by_category (type, category, id, name, title) VALUES ('LP record', 'Rock', uuid(), 'Pink Floyd', 'The Dark Side of the Moon'); -
Select all rows:
SELECT * FROM music_store.music_by_category; -
Delete the table:
DROP TABLE music_store.music_by_category; -
Now let’s create a table using a more advanced data structure: a
map<int, text>(similar to a Python dictionary).CREATE TABLE music_store.music_by_category ( type text, category text, id UUID, name text, title map<int,text>, PRIMARY KEY (type, id) ); -
Insert a record with a map:
INSERT INTO music_store.music_by_category (type, category, id, name, title) VALUES ('LP record', 'Rock', uuid(), 'Pink Floyd', {1975: 'Wish you were here', 1979: 'The Wall'}); -
Insert another record:
INSERT INTO music_store.music_by_category (type, category, id, name, title) VALUES ('LP record', 'Reggae', uuid(), 'Bob Marley', {1984: 'The legend'}); -
Select all rows:
SELECT * FROM music_store.music_by_category; -
To search inside the
titlemap, we must create an index:CREATE INDEX ON music_store.music_by_category (title); -
Now we can search for records containing a particular value:
SELECT * FROM music_store.music_by_category WHERE title CONTAINS 'The legend'; -
Exit the
cqlshshell:exitWant to learn more Cassandra CQL commands? See the official documentation: https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/cqlInsert.html
-
Let's create a Python application to connect and extract data!
-
The script will connect to our cluster and select data from a table. To do this, we need the
cassandra-driver. -
Install the required packages:
sudo apt install python3-pipType
Ywhen prompted.
- Create a virtual environment and install the Python Cassandra driver:
# Install venv support
sudo apt install python3-venv python3-full
# Create venv
python3 -m venv venv
# Activate venv
source venv/bin/activate- Install the Cassandra driver:
pip install cassandra-driver- Inspect the IP addresses of the Cassandra containers:
docker inspect --format='{{ .NetworkSettings.IPAddress }}' cassandra-1 cassandra-2 cassandra-3Example output:
172.17.0.2
172.17.0.3
172.17.0.4
- Create a Python file:
sudo apt install nano
pico test-cassandra.py- Add the following Python code:
from cassandra.cluster import Cluster
cluster = Cluster(['172.17.0.2','172.17.0.3','172.17.0.4'], port=9042)
session = cluster.connect('music_store')
session.set_keyspace('music_store')
session.execute('USE music_store')
rows = session.execute('SELECT * FROM music_store.music_by_category')
for i in rows:
print(i)- Run the script:
python test-cassandra.py- Example output:
Row(type='LP record', ...)
Row(type='LP record', ...)
- Example query with variables:
search = 'The Wall'
rows = session.execute(
'SELECT * FROM music_store.music_by_category WHERE title CONTAINS %s',
[search]
)- Stop
cassandra-1to test cluster replication:
docker stop cassandra-1-
Run Python script again — data should still be available due to replication.
-
Deactivate the virtual environment:
deactivate- Create a new directory:
mkdir node-cassandra
cd node-cassandra- Install npm:
sudo apt install npm- Initialise the project:
npm init- Install the Cassandra driver:
npm install cassandra-driver- Create the script:
pico cassandra-app.js- Add the following Node.js code:
let cassandra = require('cassandra-driver');
const keyspace="music_store";
let contactPoints = ['172.17.0.2','172.17.0.3','172.17.0.4'];
let client = new cassandra.Client({
contactPoints: contactPoints,
keyspace:keyspace,
localDataCenter: 'datacenter1'
});
let query = 'SELECT * FROM music_store.music_by_category';
client.execute(query, function(error, result) {
if(error){
console.log('Error:', error);
}else{
console.log(result.rows);
}
});- Run it:
node cassandra-app.js- Example output:
[ {type: 'LP record', ...}, {type: 'LP record', ...} ]
- Query with parameters:
let query = 'SELECT * FROM music_store.music_by_category WHERE title CONTAINS ?';
let parameter = ['The Wall'];
client.execute(query, parameter, (error, result)=> {
if(error){
console.log('Error:', error);
}else{
console.log(result.rows);
}
});- Great job — Phase 6 completed!
💡 Don’t forget to stop or delete your VM when you're done.
-
You need two VMs with Docker installed.
-
Stop/delete previous Cassandra containers.
-
Open port 7000 in GCP firewall.
-
Get internal VM IPs. Example:
- VM1 →
<internal-ip-address-vm1> - VM2 →
<internal-ip-address-vm2>
- VM1 →
-
On VM1, run:
docker run --name cas-c1 -d -e CASSANDRA_BROADCAST_ADDRESS=<internal-ip-address-vm1> -p 7000:7000 cassandra:3.11- Check status:
docker exec -i -t cas-c1 bash -c 'nodetool status'- On VM2, run:
docker run --name cas-c2 -d -e CASSANDRA_BROADCAST_ADDRESS=<internal-ip-address-vm2> -e CASSANDRA_SEEDS=<internal-ip-address-vm1> -p 7000:7000 cassandra:3.11- Check cluster status on VM1:
docker exec -i -t cas-c1 bash -c 'nodetool status'-
You now have a Cassandra cluster across two VMs.
-
Apple famously runs 75,000 Cassandra nodes.
-
You can interact with this cluster using any commands from previous phases.
-
Open the VPC network menu, On the left menu: VPC network → Firewall rules
-
Click “Create firewall rule”
-
Fill in the firewall rule details.
Use the following values:
| Field | Value |
|---|---|
| Name | allow-cassandra-7000 |
| Network | default (or your custom network) |
| Direction | Ingress |
| Action | Allow |
| Targets | All instances in the network (or specify your VMs) |
| Source filter | IPv4 range |
| Source IP ranges | 0.0.0.0/0 (or restrict to internal IP ranges if preferred) |
| Protocols and ports | Select TCP, enter 7000 |
- Click “Create”. That’s it! The firewall rule is now active.
💡 Important Notes
-
Port 7000 is used for Cassandra intra-node communication (gossip protocol).
-
If your cluster is only internal, use a safer source range such as:
10.0.0.0/8 -
Cassandra also commonly uses:
- 9042 – CQL (client API)
- 7199 – JMX
- 7001 – SSL internode communication