The Monica CRM and Kubernetes logos.

Hello! My name is Nick and I'm going to show you how to self-host your own Monica Customer Relationship Management (CRM) server using Kubernetes.

What is Monica?

Monica is an open-source CRM system. But instead of being intended to store details of your business relationships, its purpose is to store details about your personal relationships in order to foster stronger connections between you and your friends and family.

Previously...

The first article in this series is a crash course on Kubernetes. There I introduced you to a number of important Kubernetes components, such as pods, deployments, services, secrets, volumes, and ingress objects. We then used Kubernetes to host our own Nextcloud server which you can use to save and share files in the cloud.

In the next article, we used what we learned from the previous article to host a Huginn server on our Kubernetes cluster. We then used Huginn to automatically search for apartments on Craigslist. Plus we learned a few new things such as name-based virtual hosting and how to use Amazon Simple Email Service to send notification emails.

The Purpose of This Article

In this article, I'm going to show you:

  • How to host a Monica CRM server on our Kubernetes cluster
  • How to enable HTTPS between the clients and the cluster without making any modifications to Nextcloud, Huginn, or Monica
  • How to create a Kubernetes secret from a config file instead of from a kubectl command
  • Where to safely store secret data, like the config file for a Kubernetes secret, in Git

Source Code (Optional)

If you want to get your hands on the code, click on the Github link below. Be sure to git checkout the monica tag to view the finished code. Note that in future posts, I will add additional projects to the repo and making changes that aren't reflected in this post.

nick-true-dev/usable-k8s-projects
Three projects that showcase Kubernetes. Project 1 uses K8s to host a Nextcloud server. Project 2 uses K8s to host a Huginn server. And project 3 uses K8s to host a MonicaHQ server. - nick-true-dev...

Start Up Minikube

We are going to test everything out using Minikube, the single-node development cluster. If you don't already have it installed, go here: https://kubernetes.io/docs/tasks/tools/install-minikube.

Now, start up Minikube:

minikube start

Configuring the Database Components

Here's the config file for the MySQL database deployment named monica-db.
monica-db-1

The replicas field tell us that this deployment makes sure that exactly one database pod is running at all times. In that pod runs a mysql:5.7 container.

Mount a Volume onto the DB Container

In order to ensure that the DB data will survive a container crash or a pod eviction, we need to mount a Kubernetes volume onto the directory where MySQL stores the data. We do this because volume storage is usually more durable than container storage.

Here's the code which mounts the volume.
monica-db-1a

In this case, we declare a volume named db-storage, and mount it onto the container's /var/lib/mysql directory.

The Persistent Volume Claim

Under the volumes field in the monica-db.yaml file, the db-storage volume references a PVC named monica-shared-storage-claim. In this project, this PVC will be shared between the DB and the Monica server. So let's put the PVC's YAML config document in its own file:
monica-pvc-1-1

The monica-shared-storage-claim PVC has 256Mi of storage.

The Service for the Database

Since pods can get replaced at any time, it's a good idea to put a service in front of a set of one or more pod replicas. The service acts as a communications middleman providing a consistent address/endpoint for clients to communicate with.

FYI: Services also provide TCP/UDP loadbalancing between its backing pods, if there's more than one. I just mention this to show you that services are useful in a number of different ways.

Anyway, let's add the service to the monica-db.yaml file.
monica-db-1d
This service listens on port 3306 for client requests and fowards them on to port 3306 on the monica-db-pod.

DB Environment Variables

We need to scroll back up to the deployment and add some MySQL environment variables.
monica-db-1e

The env field allows you to declare an array of environment variables. In this case, we're just declaring MYSQL_DATABASE, i.e. the name of the DB.

The envFrom.secretRef field tells the mysql container to extract the values from the monica-db-secret object and turn them into environment variables.

Store Database Credentials in a Kubernetes Secret

We need to create the monica-db-secret. Here's the command to do so:

kubectl create secret generic monica-db-secret \
	--from-literal=MYSQL_ROOT_PASSWORD=rootPassw0rd1 \
	--from-literal=MYSQL_USER=monica \
	--from-literal=MYSQL_PASSWORD=passw0rd1

Now I'm going to show you how to create a config file for the secret that we just created. First you need to output the secret in YAML format, and put it in the monica-db-secret.yaml file.

kubectl get secret monica-db-secret -o yaml > monica-db-secret.yaml

Now let's view the file.
monica-db-secret-1
This is the YAML-representation of the secret object that we just created. There's a lot of extraneous metadata that we can get rid of. I have crossed those fields out.

Here's the config file after deleting the unnecessary fields and reordering some fields for better legibility.
monica-db-secret-1a-1
Note that the data values are base64 encoded, which is why they look like strings of random characters. Note, they are not encrypted! They are effectively plain-text strings because it's trivial to decode a base64 value.

Let's create the secret using the config file to test things out. But first we must delete the existing monica-db-secret that we created a minute ago.

kubectl delete secret monica-db-secret

Now we can recreate the secret using the config file.

kubectl apply -f monica-db-secret.yaml

Safely Store Secrets in Git Using Blackbox

Generally speaking, you'll want to save Kubernetes YAML config files in a version control system like Git. But what are you supposed to do about Kubernetes secret config files? After all, they contain things like unencrypted credentials and API keys which should not be saved in your VCS. I like to solve this problem by using Blackbox.

Blackbox is a tool which helps you to encrypt sensitive files before adding them to your Git repository. This prevents credentials and API keys from falling into the wrong hands when they git clone your repo. (Go here for a more detailed explanation.)

Setting up Blackbox is system-dependent and outside of the scope of this article. I just wanted to let you know that it exists, and that it is a good solution to this problem. Just go to https://github.com/StackExchange/blackbox and follow the installation instructions for your system.

Create the Database Components

We have already created the DB secret. Now let's create the shared PVC

kubectl apply -f monica-shared-pvc.yaml

...and the database deployment and service.

kubectl apply -f monica-db.yaml

Finally, we're going to watch the pods until the pod created by the monica-db deployment is up and running. We do this by using the kubectl get pods --watch command.
term-1-3
Note that it may take a minute or two to create the pod if your cluster hasn't yet downloaded and cached the mysql:5.7 image.

Configuring the Monica Server Components

Here are the beginnings of the monica-server deployment. Like usual, we are not expecting gargantuan amount of traffic, so the deployment only creates a single pod. That pod runs a container created from the monicahq/monicahq:v2.17.0-php-apache image.
monica-server-1

Attach a Volume to the Server

In addition to storing things in the database, the Monica server also stores things in /var/www/monica/storage. Let's mount a volume onto that directory. That way, files in the directory will persist even if the container or pod dies.
monica-server-1a
The top block mounts the volume, and the bottom block is where we declare the volume. Notice that it's backed by the shared PVC.

Store Non-Sensitive Env Vars in a ConfigMap

We are going to add the following configMap to the file. It contains all of the non-sensitive environment variables that configure the server.
monica-server-1b
First off, notice that "true" and "3306" are both in quotes because configMap values must be strings, not a boolean or a number.

Second, take a look at DB_HOST: monica-db. This configures the server to use the database at the address monica-db, which is the hostname of the DB service defined in the monica-db.yaml file. Recall that the service acts as a communications intermediary between the database pod and clients such as the Monica server.

Now let's update the server container to reference the configMap.
monica-server-1c

Store Sensitive Env Vars in a Secret

We need to create a Kubernetes secret named monica-server-secret to store the APP_KEY and the server's DB credentials.

kubectl create secret generic monica-server-secret \
    --from-literal=APP_KEY=a_random_32-character_stringgggg \
    --from-literal=DB_USERNAME=monica \
    --from-literal=DB_PASSWORD=passw0rd1

Now let's update the server container to reference the secret.
monica-server-1d

Add the Service for the Monica Server

Let's add a service which sits in front of the server pod.
monica-server-1e-1
Requests sent to port 80 on the monica-server service will get forwarded to port 80 on the server pod.

Create the Monica Server Components

Now we can create the configMap, deployment, and service for the Monica server.

kubectl apply -f monica-server.yaml

These three components will get created almost instantaneously. However, it will probably take a few minutes for the deployment to create the server pod because the cluster needs to download and cache the monicahq/monicahq:v2.17.0-php-apache image.

So let's watch the pods until the server pod is up and running.
term-1a

Part 1 Summary

In this article, we went over

  • The key Kubernetes components--configMaps, deployments, PVCs, secrets, and services--for our Monica server and MySQL DB
  • How to create the config file for a Kubernetes secret
  • One way to safely store sensitive data, like the config file for a secret, in Git

In Part 2, I'm going to show you how to use an ingress object to

  • Make the Monica server accessible outside of the cluster
  • Enable HTTPS between clients and the cluster

Thanks for reading this far. See you in Part 2.