MariaDB and Docker use cases, Part 1

Some of the most common questions asked by our users are regarding MariaDB support in Docker, and in particular how it can be used in specific development or production deployments. This series of articles will try to cover a few Docker and MariaDB use cases.

Why choose Docker for MariaDB?

  • Docker containers can be used to test, deploy and release applications within any environment.
  • Docker deployments can be automated easily, creating deployment environments and reproducing them easily in staging and production.
  • Docker is lightweight virtualization. Hypervisors are not needed, and a MariaDB Docker container should perform just as well as a normal MariaDB installation without any noticeable overhead.
  • Docker is agnostic – once you’ve installed Docker on your OS, the instructions for running containers are exactly the same, whether you’re running CentOS, Debian or Ubuntu, or even Mac OS X and Windows.

A few important points about Docker containers

  • Docker containers are immutable. They can’t be easily modified after start (unless you attach to it and break everything).
  • By default and because of the above, data is not persistent. Docker uses data volumes to remedy to this. The MariaDB container uses a volume to preserve data (more about this later).

State of MariaDB in Docker

MariaDB has always been very well supported in Docker for a couple of years, thanks to many efforts by the Docker team and community. To this day, Docker supports all three MariaDB releases: 5.5, 10.0 and 10.1. The MariaDB docker container has the following particularities:

  • The MariaDB root password can be set or generated through environment variables.
  • A new user and an empty database can be created through the same process as above.
  • The instance has a default /var/lib/mysql persistent data volume, which you can let docker manage internally or mount to a directory of your choice.
  • The container instance can be mounted on an existing data volume (for example a backup).
  • Network ports can be bound to arbitrary ports on the host side.
  • The MariaDB knowledge base has an extensive documentation article about docker. Read it!

Docker use case #1: Multi Tenancy

A common use case for MariaDB and Docker is running several instances of MariaDB on the same physical hosts. There are already existing solutions such as MySQL Sandbox and others, however none of them provide the flexibility, ease of use and power that Docker offers.

To illustrate our point let’s start three different instances of MariaDB, each of those running a different major version:

docker run -d -p 3301:3306 -v ~/mdbdata/mdb55:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=admin --name mdb55 mariadb:5.5
docker run -d -p 3302:3306 -v ~/mdbdata/mdb10:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=admin --name mdb10 mariadb:10.0
docker run -d -p 3303:3306 -v ~/mdbdata/mdb11:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=admin --name mdb11 mariadb:10.1

Docker will automatically pull the official mariadb images from the repository and launch them. Now we can simply connect to any of those instances using the provided port and password:

$ mysql -u root -padmin -h 127.0.0.1 -P3302
Welcome to the MariaDB monitor.  Commands end with ; or g.
Your MariaDB connection id is 2
Server version: 10.0.22-MariaDB-1~jessie mariadb.org binary distribution Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others. Type 'help;' or 'h' for help. Type 'c' to clear the current input statement.

Note that each of our instances will use a persistent data volume located under the ~/mdbdata directory – Docker will automatically create this directory tree for us.

Now that we’ve done that, let’s delve into advanced features of Docker. Docker supports Linux control groups (cgroups), which can be used to limit, account or isolate resource usage. Let’s say that we want our MariaDB 10.1 instance (named mdb11) to have a higher CPU priority than the two other instances. In this case we can lower the CPU shares of mdb10 and mdb55. Each instance has 1024 max CPU shares by default, so let’s recreate our mdb55 and mdb10 containers with 512 CPU shares each.

In the preamble we said that Docker containers are immutable. If we want to change our containers’ parameters we need to remove them. This is not an issue because we’ve defined persistent data volumes in ~/mdbdata, so the actual contents of our database will persist when we recreate the containers.

docker rm -f mdb55 mdb10
docker run -d -p 3301:3306 --cpu-shares=512 -v ~/mdbdata/mdb55:/var/lib/mysql --name mdb55 mariadb:5.5
docker run -d -p 3302:3306 --cpu-shares=512 -v ~/mdbdata/mdb10:/var/lib/mysql --name mdb10 mariadb:10.0

We have recreated the two MariaDB instances with 512 CPU shares each. This is a soft limit though, and is only enforced when processes compete for CPU cycles. If the other instances are idle, any instance is able to use up to 100% of all CPUs. In practice, this means that if all three instances use the CPU concurrently, each of the two first containers, which have 512 shares each, (mdb55 and mdb10) will be able to use up to 25% of all CPUs, whereas the third container, which has 1024 shares, will be able to use up to 50% of all CPUs.

Another option is to bind the instance to a specific CPU core, so let’s recreate the containers and do that:

docker rm -f mdb55 mdb10 mdb11
docker run -d -p 3301:3306 --cpuset-cpus=0 -v ~/mdbdata/mdb55:/var/lib/mysql --name mdb55 mariadb:5.5
docker run -d -p 3302:3306 --cpuset-cpus=1 -v ~/mdbdata/mdb10:/var/lib/mysql --name mdb10 mariadb:10.0
docker run -d -p 3303:3306 --cpuset-cpus=2-3 -v ~/mdbdata/mdb10:/var/lib/mysql --name mdb11 mariadb:10.1

In the example above, given a 4 CPU Core system, my containers mdb55 and mdb10 will each run on a separate, single CPU core whereas mdb11 will both remaining cores.

We can also control the way our containers access disk and memory resources, which is definitely useful on a busy system – you don’t want a runaway development query using all the disk of your load testing instances, for example. Whereas memory limits are straightforward, block IO shares work in a similar fashion as the CPU shares do, except that the default block IO share is of 500 in a 10 to 1000 range.

Let’s limit our two first containers to 512M of memory and 250 block IO shares:

docker rm -f mdb55 mdb10
docker run -d -p 3301:3306 --blkio-weight=250 --memory=512M -v ~/mdbdata/mdb55:/var/lib/mysql --name mdb55 mariadb:5.5
docker run -d -p 3302:3306 --blkio-weight=250 --memory=512M  -v ~/mdbdata/mdb10:/var/lib/mysql --name mdb10 mariadb:10.0

Similarly to what we have seen in the CPU shares example, if the three instances compete for IO, each of the two first containers will be limited to 25% of available IO capacity, the third container being limited to the remaining capacity, e.g. 50%.

There is much more to Docker runtime constraints that what we have been talking about here in this article. Please read the extensive Docker run reference to know about all possible options.