Run immudb SQL and Key-Value Database on Docker / Kubernetes

Posted on 193 views

Welcome to this guide on how to run immudb SQL and Key-Value Database on Docker / Kubernetes. Immudb can be defined as a relational (SQL) database and a key-value store with a built-in cryptographic proof and verification. This is an open-source product developed by CodeNotary, released under the Apache License 2.0.

In traditional databases, logs and transactions are mutable and you can’t be sure if the data has been manipulated. One of the outstanding features of Immudb is that it is immutable. In other words, it allows one to add and replace data but leaves old data in the disk undeleted. By doing so, data from the past can still be read. It is also lightweight and can handle millions of transactions per second.

Immudb is used in the following areas:

  • Storing CI/CD recipes to protect build and deployment pipelines
  • Storing public certificates
  • Storing log streams (i. e. audit logs) tamperproof
  • Recording the location where fish was found aboard fishing trawlers
  • Used as additional hash storage for digital objects checksums
  • Storing every update to sensitive database fields such as credit card or bank account data of an existing application database
  • Storing the last known positions of submarines

Step 1 – Run immudb SQL and Key-Value Database on Docker / Kubernetes

Let’s dive in a learn how to run immudb SQL and Key-Value Database on Docker / Kubernetes.

When immudb is installed, the following defaults are used:

  • User: immu
  • Group: immu
  • Configuration: /etc/immudb
  • Data: /var/lib/immudb
  • Logs: /var/log/immudb
  • Service Port: 3322 (immudb), 3323 (immugw)
  • Prometheus Port: 9497
  • Web console: 8080

Option 1 – Run immudb SQL and Key-Value Database on Docker

To be able to run the immudb database on docker, you need to have the Docker Engine installed. You can use the below guide to install Docker on your system.

Add your user to the Docker group:

sudo usermod -aG docker $USER
newgrp docker

Ensure the docker service has been started and enabled:

sudo systemctl start docker && sudo systemctl enable docker

Create Persistent Volumes

We will create persistent data paths for the immudb container:

sudo mkdir  -p /opt/immudb/data
sudo mkdir /opt/immudb/config
sudo mkdir /opt/immudb/logs

Set the required permissions:

sudo chmod -R 777 /opt/immudb

Configure SELinux on Rhel-based systems as below:

sudo setenforce 0
sudo sed -i 's/^SELINUX=.*/SELINUX=permissive/g' /etc/selinux/config

Now run the immudb database in a docker container with ports 3322 and 9497 exposed as shown.

docker run -d -it  \
    --name immudb \
    -p 30842:3322 \
    -p 30077:9497  \
    -p 32641:8080 \
    -v  /opt/immudb/data:/var/lib/immudb \
    -v  /opt/immudb/config:/etc/immudb  \
    -v  /opt/immudb/logs:/var/log/immudb \
codenotary/immudb:latest

Sample execution:

Unable to find image 'codenotary/immudb:latest' locally
latest: Pulling from codenotary/immudb
b94a3daa6831: Pull complete 
4a67363f400e: Pull complete 
256bed11b5e1: Pull complete 
fd6bfecfcf6f: Pull complete 
0c76a5976060: Pull complete 
Digest: sha256:9f5f1716237825cec75a2ac71f5afaf31b057a0db31f0b412513ba5cec30b53d
Status: Downloaded newer image for codenotary/immudb:latest
23bdedc141d56019c240889e4b8025624bb9edafedbd45d7e60af300e12837d2

Check if the container is running:

$ docker ps
CONTAINER ID   IMAGE                      COMMAND              CREATED          STATUS                    PORTS                                                                                                                                             NAMES
23bdedc141d5   codenotary/immudb:latest   "/usr/sbin/immudb"   35 seconds ago   Up 34 seconds (healthy)   5432/tcp, 0.0.0.0:30842->3322/tcp, :::30842->3322/tcp, 0.0.0.0:32641->8080/tcp, :::32641->8080/tcp, 0.0.0.0:30077->9497/tcp, :::30077->9497/tcp   immudb

Option 2 – Run immudb SQL and Key-Value Database on Kubernetes

You first need to have a Kubernetes cluster step up. The below guides can be used to set up a Kubernetes cluster.

Install kubectl with the command:

curl -LO "https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin

To access the cluster using kubectl, export the certificate:

# For k0s
export KUBECONFIG=/var/lib/k0s/pki/admin.conf

#1. Create a StorageClass

To be able to create a Persistent Volume for immudb, we will begin by creating a storage class:

vim storageClass.yml

Add the lines below to the file:

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: my-local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

Apply the manifest:

$ kubectl create -f storageClass.yml
storageclass.storage.k8s.io/my-local-storage created

#2. Create Local Persistent Volume

Using, the created storage class, we will create a PV:

vim local-pv.yml

The file will contain the lines below:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-local-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: my-local-storage
  local:
    path: /opt/immudb
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - node1

This requires the path available on the specified node(node1). Create the path as shown:

sudo mkdir /opt/immudb
sudo chcon -Rt svirt_sandbox_file_t /opt/immudb
sudo chmod 777 /opt/immudb

Now apply your manifest:

kubectl create -f local-pv.yml

#3. Create a Persistent Volume Claim

Create a PVC using the storageClass:

vim local-pvc.yml

Paste the lines below:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  # This name uniquely identifies the PVC. This is used in deployment.
  name: immudb-pvc-claim
spec:
  # Read more about access modes here: http://kubernetes.io/docs/user-guide/persistent-volumes/#access-modes
  storageClassName: my-local-storage
  accessModes:
    # The volume is mounted as read-write by Multiple nodes
    - ReadWriteMany
  resources:
    # This is the request for storage. Should be available in the cluster.
    requests:
      storage: 5Gi

Apply the manifest:

kubectl create -f local-pvc.yml

Check if the PV is available:

$ kubectl get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS       REASON   AGE
my-local-pv   5Gi        RWX            Retain           Available           my-local-storage            20s

#4. Create the Immudb Pod

We will create the Immudb pod and use the created PVC to persist its data.

vim immudb-pod.yml

The file will have the lines:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: immudb
spec:
  selector:
    matchLabels:
      app: immudb 
  template:
    metadata:
      labels:
        app: immudb
    spec:
      containers:
      - name: immudb
        image: codenotary/immudb:latest
        volumeMounts:
        - name: data
          mountPath: /var/lib/immudb
        ports:
         - containerPort: 3322
         - containerPort: 9497
         - containerPort: 8080
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: immudb-pvc-claim

Apply the manifest:

kubectl create -f immudb-pod.yml

Verify if the pod is running:

$ kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
immudb-67c7df5747-wz8sc   1/1     Running   0          9s

Also, the PV should be bound:

$ kubectl get pv
NAME          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                      STORAGECLASS       REASON   AGE
my-local-pv   5Gi        RWX            Retain           Bound    default/immudb-pvc-claim   my-local-storage            11m

Now create the service file:

vim immudb-svc.yml

We will set the service type as LoadBalancer, though you can still use NodePort as per your preferences.

apiVersion: v1
kind: Service
metadata:
  # This name uniquely identifies the service
  name: immudb-service
spec:
  type: LoadBalancer
  ports:
    - name: immudb
      port: 3322
      targetPort: 3322
      protocol: TCP
    - name: prometheus
      port: 9497
      targetPort: 9497
      protocol: TCP
    - name: web
      port: 8080
      targetPort: 8080
      protocol: TCP
  selector:
    # Looks for labels `app:immudb` in the namespace and applies the spec
    app: immudb

Create the service:

kubectl create -f immudb-svc.yml

Verify if the service is running:

$ kubectl get svc
NAME             TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                                        AGE
immudb-service   LoadBalancer   10.108.175.107        3322:30842/TCP,9497:30077/TCP,8080:32641/TCP   4s
kubernetes       ClusterIP      10.96.0.1                443/TCP                                        25h

Step 2 – Accessing the Immudb Web Console

The immudb web console is available on port 8080, proceed and access it via HTTP using the exposed port. In this guide, we have exposed the services as below:

  • Web console 8080 -> 32641
  • Immudb 3322 -> 30842
  • Prometheus 9497 -> 30077

Allow these ports through the firewall:

##For UFW
sudo ufw allow 30842
sudo ufw allow 30077
sudo ufw allow 32641

##For Firewalld
sudo firewall-cmd --add-port=30842/tcp --permanent
sudo firewall-cmd --add-port=30077/tcp --permanent
sudo firewall-cmd --add-port=32641/tcp --permanent
sudo firewall-cmd --reload

Now access the Immudb web console using the URL http://IP_address:32641

Run-immudb-SQL-and-Key-Value-Database-on-DockerKubernetes

The default login credentials are:

Username: immudb
Password: immudb

Here, you can easily manage your database. To create a database, navigate to the Databases tab.

Run-immudb-SQL-and-Key-Value-Database-on-DockerKubernetes-1

Create a database and provide the db name.

Run-immudb-SQL-and-Key-Value-Database-on-DockerKubernetes-2

You can also run queries.

Run-immudb-SQL-and-Key-Value-Database-on-DockerKubernetes-3

Step 3 – Install Immuclient on Linux

This is a client tool used to manage the Immudb. You can easily create, update users/databases, perform backups and restores e.t.c

The latest release is available on Github. Download the latest release file as below:

VER=$(curl -s https://api.github.com/repos/codenotary/immudb/releases/latest|grep tag_name|cut -d '"' -f 4|sed 's/v//')
wget https://github.com/codenotary/immudb/releases/download/v$VER/immuclient-v$VER-linux-amd64

Rename the file as shown and make it executable:

mv immuclient-v$VER-linux-amd64 immuclient
chmod +x immuclient

Now the client with the command:

$ ./immuclient -a 192.168.205.11 -p 30842
immuclient>login immudb
Password: immudb
Successfully logged in
immudb user has the default password: please change it to ensure proper security 
immuclient>

Remember to provide the Immudb’s IP address and port appopriately.

Obtaining help:

immuclient> help
login          Login using the specified username and password                                    args: username
logout                                                                                            
use            Select database                                                                    args: databasename
safeget        Get and verify item having the specified key                                       args: key
get            Get item having the specified key                                                  args: key
gettx          Return a tx by id    
....

Step 4 – Using Immuclient on Linux

The Immuclient can be used to run queries. Some of the common queries are:

  • Create Tables to an existing database (the default database is defaultdb)
immuclient>use testdb
Now using testdb 
immuclient> exec CREATE TABLE people(id INTEGER, name VARCHAR, salary INTEGER, PRIMARY KEY id);
  • Insert data in the table:
immuclient> exec UPSERT INTO people(id, name, salary) VALUES (1, 'Klinsmann', 10000);
Updated rows: 1 
immuclient> exec UPSERT INTO people(id, name, salary) VALUES (2, 'Mutai', 30000);
Updated rows: 1 
  • View the tables
immuclient> query SELECT id, name, salary FROM people;
+--------------------+----------------------+------------------------+
| (TESTDB PEOPLE ID) | (TESTDB PEOPLE NAME) | (TESTDB PEOPLE SALARY) |
+--------------------+----------------------+------------------------+
|                  1 | "Klinsmann"          |                  10000 |
|                  2 | "Mutai"              |                  30000 |
+--------------------+----------------------+------------------------+
  • Overwrite values. For example the value for Klinsmann
immuclient> exec UPSERT INTO people(id, name, salary) VALUES (1, 'Klinsmann', 20000);
Updated rows: 1 
immuclient> query SELECT id, name, salary FROM people;
+--------------------+----------------------+------------------------+
| (TESTDB PEOPLE ID) | (TESTDB PEOPLE NAME) | (TESTDB PEOPLE SALARY) |
+--------------------+----------------------+------------------------+
|                  1 | "Klinsmann"          |                  20000 |
|                  2 | "Mutai"              |                  30000 |
+--------------------+----------------------+------------------------+

Time Travel on Immudb

One of the amazing features associated with immudb is preserving history. Let us see if this is true. We edited the salary for Klinsmann to 20000. This has been written as the current value.

immuclient> query SELECT id, name, salary FROM people WHERE name='Klinsmann';
+--------------------+----------------------+------------------------+
| (TESTDB PEOPLE ID) | (TESTDB PEOPLE NAME) | (TESTDB PEOPLE SALARY) |
+--------------------+----------------------+------------------------+
|                  1 | "Klinsmann"          |                  20000 |
+--------------------+----------------------+------------------------+

Get the current transaction ID:

immuclient> current
database:	testdb
txID:		5
hash:		29e345077eaaf204a03cf8ef39326cc19f49bafd492594f0bcfe34b6c47fcdc4

Get the value before the update:

immuclient> query SELECT id, name, salary FROM people BEFORE TX 5 WHERE name='Klinsmann';

Sample Output:

Run-immudb-SQL-and-Key-Value-Database-on-DockerKubernetes-4

You can also get values before anything was inserted into the table:

immuclient> query SELECT id, name, salary FROM people BEFORE TX 2 WHERE name='Klinsmann';
+--------------------+----------------------+------------------------+
| (TESTDB PEOPLE ID) | (TESTDB PEOPLE NAME) | (TESTDB PEOPLE SALARY) |
+--------------------+----------------------+------------------------+
+--------------------+----------------------+------------------------+

Using the information, you can perform comparisons and see how data has changed over time:

immuclient> query SELECT peoplenow.id, peoplenow.name, peoplethen.salary, peoplenow.salary FROM people BEFORE TX 5 AS peoplethen INNER JOIN people AS peoplenow ON peoplenow.id=peoplethen.id;

Sample Output:

Run-immudb-SQL-and-Key-Value-Database-on-DockerKubernetes-5

Logout and exit:

immuclient>logout
Successfully logged out 
immuclient>exit

Step 5 – Using immudb SDKs

SDKs make it comfortable to communicate with the server using your preferred programming language. There are several SDKs. They include;

  • Go
  • Java
  • Node
  • Js
  • Ruby
  • Python

Here, I will demonstrate how to use Python. Install the immudb client for Python as below:

Install Python and PIP:

##On Debian/Ubuntu
sudo apt install python3 python3-pip git libsasl2-dev libldap2-dev libssl-dev libxml2-dev libxslt1-dev libxmlsec1-dev libffi-dev pkg-config apt-transport-https virtualenv python3-venv build-essential libmariadb-dev python3-flask -y

##On RHEL/CentOS/Rocky Linux 8/Alma Linux 8
sudo yum install python3 python3-pip git python3-devel
sudo yum install epel-release
sudo yum group install "Development Tools" 

Use PIP to install the immudb client for Python.

sudo pip3 install --upgrade pip
sudo pip3 install immudb-py

Now create a sample Python script:

vim setup.py 

Add the following lines replacing them appropriately. Create the connection to port 3322, or the port to which the service has been exposed(30842):

#!/usr/bin/env python

from immudb.client import ImmudbClient

ic = ImmudbClient("192.168.205.4:30842")
ic.login(username="immudb", password="immudb")

key = "Hello".encode('utf8')
value = "Immutable World!".encode('utf8')

# set a key/value pair
ic.set(key, value)

# reads back the value
readback = ic.get(key)
saved_value = readback.value.decode('utf8')
print("Hello", saved_value)

Run the script:

# python3 setup.py 
Hello Immutable World!

The end!

Closing Remarks

At this point, you can use the installed immudb SQL and Key-Value Database on Docker / Kubernetes to store your sensitive data. I hope this was helpful.

coffee

Gravatar Image
A systems engineer with excellent skills in systems administration, cloud computing, systems deployment, virtualization, containers, and a certified ethical hacker.