Mysql On Docker How To Containerize Your Database
Mysql On Docker How To Containerize Your Database
Mysql On Docker How To Containerize Your Database
2
Table of Contents
1. Introduction 5
2. Introduction to Docker 6
2.1. Concept 6
2.2. Components 6
2.3. Benefits 7
2.4. Installation 8
3. MySQL Images 9
3.1. Commit changes of a container 9
3.2. Using Dockerfile 10
3.3. Building the Image 11
3.4. Sharing the Image 12
3.5. Best Practice for MySQL 12
4. Networking in Docker 14
4.1. Host Network 14
4.2. Bridge Network 15
4.2.1. Default Bridge 16
4.2.2. User-defined Bridge 17
4.3. Multi-host Network 18
4.3.1. Default Overlay 18
4.3.2. User-defined Overlay 19
4.4. Network Plugins 20
4.5. Accessing the MySQL Containers 21
4.5.1. Container’s exposed port 21
4.5.2. Container’s published port 21
4.5.3. Attaching to the active container 22
3
6.6. Backup and Restore 39
6.6.1. Backup 39
6.6.2. Restore 40
6.7. Upgrades 40
6.7.1. Logical Upgrade 40
6.7.2. In-Place Upgrade 41
6.8. Housekeeping 42
7. ClusterControl on Docker 43
7.1. Running ClusterControl as Docker Container 43
7.2. Automatic Database Deployment 44
7.3. Manual Database Deployment 48
7.4. Add Existing Database Containers 48
8. Summary 50
About ClusterControl 51
About Severalnines 51
Related Whitepapers 52
4
Introduction
Docker is quickly becoming mainstream, as a method to package and deploy self-
sufficient applications in primarily stateless Linux containers. Yet, for a stateful service
like a database, this might be bit of a headache. How do we best configure MySQL in
a container environment? What can go wrong? Should we even run our databases in a
container environment? How does performance compare with e.g. running on virtual
machines or bare-metal servers? How do we manage replicated or clustered setups,
where multiple containers need to be created, upgraded and made highly available?
This whitepaper covers the basics you need to understand considering to run a MySQL
service on top of Docker container virtualization. Take note that this whitepaper does
not cover MySQL container orchestration on multiple hosts.
5
Introduction to Docker
The key benefit of Docker is that it allows users to package an application with all
of its dependencies into a standardized unit (container). Running many containers
allows each one to focus on a specific task; multiple containers then work in concert to
implement a distributed system.
2.1. Concepts
Think about a container as a “lightweight virtual machine”. Unlike virtual machines
though, containers do not require an entire operating system, or all required libraries
including the actual application binaries. The same Linux kernel and libraries can
be shared between multiple containers running on the host. Docker makes it easy
to package software in self-contained images, where all software dependencies are
bundled and deployed in a repeatable manner. An image will have exactly the same
software installed, whether we run it on a laptop or on a server.
Every container has its own file system, process space and also a network stack, where
each container will be assigned with at least one network interface. Due to this isolation,
containers won’t affect the machine host process or file system. More on this in Chapter
5 - MySQL Container and Volume.
2.2. Components
Since version 17.x, Docker has split into two editions:
• Docker Community Edition (CE)
• Docker Enterprise Edition (EE)
Docker Community Edition (CE) is a free version, ideal for developers and small teams
looking to get started with Docker and experimenting with container-based apps.
Docker CE has two update channels, stable and edge:
• Stable - New updates every quarter
• Edge - New features every month
6
• Docker Image - The file system and configuration of our application which are
used to create containers.
• Docker Container - Running instances of Docker images. A container includes
an application and all of its dependencies. It shares the kernel with other
containers, and runs as an isolated process in user space on the host OS.
• Registry - A registry of Docker images, where you can find trusted and
enterprise ready containers, plugins, and Docker editions.
2.3. Benefits
Generally, containerization allows you to get a greater density on the physical host. It
reduces conflicts between various deployment environments, like development, testing,
staging, production, and also speeds up deployment.
There are real benefits of running MySQL on Docker. Some of the management and
administration practices are different to the conventional day-to-day operational
practices. More on this in Chapter 6 - Monitoring and Management.
7
2.4. Installation
Docker can run on most platforms, from your workstation or servers to cloud
infrastructure. For desktop, it supports Windows and Mac. AWS and Azure support
Docker on their cloud infrastructure. For servers, it supports Windows Server, and
popular Linux distribution - RedHat, CentOS, Debian, Ubuntu, SUSE, Fedora and Oracle
Linux. Take note that containers aren’t VMs. They offer isolation, not virtualization.
The host and container OSs must be the same. You cannot use a Linux container on a
Windows machine or vice-versa.
In this paper, we are going to use Docker Community Edition (CE) on an Ubuntu 16.04
LTS. The installation steps are pretty straightforward, as shown below:
1 $ sudo add-apt-repository \
2 “deb [arch=amd64] https://download.docker.com/linux/ubun-
tu \
3 $(lsb_release -cs) \
4 stable”
5. Update the apt package index and install the latest version of Docker CE:
At this point, Docker engine is running as a daemon. You can verify this with the
systemctl status command. For more details, please refer to the documentation.
8
MySQL Images
In order to run a Docker container, you need to have an image to start with. An image
is like a template. It has all the required things you need to run the container. That
includes software, including the code or the binaries, dependencies, libraries, interpreter,
environment variables and config files. There are two ways to create an image:
• Commit all the changes of a container.
• Use a more declarative approach defined inside a text file, called Dockerfile,
together with the docker build command.
One would start by pulling a base image, and then start a container based on this
image. Then, perform the required changes directly into the container, like installing
MySQL packages and editing the MySQL configuration file. Once everything is in place,
commit the container into a single image with the following formatting:
• Username: “myimage”
• Followed by a “/”
• Followed by an image name: “mysql”
9
• End with an optional tag: “5.7”. If undefined, the tag will default to “latest”.
When the container is being committed, the running processes will be paused to reduce
the risk of data corruption.
To run a container based on this image, one would use the docker run command with
the image name, followed by the execution command which is “mysqld” followed by
the configuration variables for MySQL.
Dockerfile is the preferred way to maintain your Docker images. It can be integrated
with a registry service like Docker Hub to provide continuous integration and it is easier
to see what is provided by the image. The following table compares the equivalent
steps with the standard deployment vs Dockerfile approach:
By looking at the content of our Dockerfile, one can easily tell that the Docker image
runs on Ubuntu 16.04. It has a MySQL server installed through the Debian OS package
repository, with a custom my.cnf that will be copied over inside the container under
/etc/mysql/my.cnf. By default, Docker will expose port 3306 when the container is
started. To build the image, use docker build command with a proper naming and
tag.
10
3.3. Building the Image
When building a custom image with Docker, each action taken, for example
apt-get install mysql-server, forms a new layer on top of the previous one. An
image is simply a collection of layers stacked on top of each other and this layer will be
read-only once it is built. These base images can then be used to create new containers.
The following diagram illustrates building an image based on the Dockerfile from the
previous section:
d51f459239fb
f6016aab25f1 Dockerfile
724179e94999
937bbdd4305a
35369f9634e1
0639788facc8
ubuntu:16.04
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: myimage/mysql:5.7
The layers in red are related to our Dockerfile commands, while the green layers are
inherited from the Ubuntu 16.04 base image that we have defined in the Dockerfile. You
can see the layers by using the docker history command:
11
From the above output, we can see that the second layer is larger in size, where we
defined apt-get install mysql-server with 370MB in size. This layer consists of
MySQL components and all of its dependencies.
The other method is to push the image directly to the Docker registry. The registry is
a stateless, highly scalable server side application that stores and distributes Docker
images. Docker Hub is the most popular public registry. It stores thousands of Docker
images and supports automated build for continuous integration (CI) where it can hook
to a code repository like Github and trigger a build when it sees a new commit being
pushed. You can use an image published and maintained by the application vendor
itself, or use other user contributed image, or you can create your own images to suit
your needs.
A public registry like Docker Hub is very helpful for public and open-source images.
However, for a organisation’s private images, you should go for a private registry, either
by subscribing to hosted services like quay.io, Amazon EC2 Container Registry (ECR),
Google Cloud Registry (GCR) and Bintray, or you can also opt for a self-hosted private
registry if you have enough resources for processing, bandwidth and storage. The
following command can be used to start your own Docker Registry service running on
port 5000 on the Docker host:
12
Try to avoid unnecessary packages inside the image to increase the deployment speed.
Only install packages required by the MySQL server and client. There is also a popular
tiny image called “alpine”, which comes with its own package manager, and includes
MySQL and MariaDB packages as well. You can use alpine as base image and build your
own MySQL server around it. For the sake of comparison, the base image is just 5MB in
size while the Ubuntu 16.04 base image is around 190MB in size.
When you want to build your own custom MySQL image, and you want to run an
entrypoint script at container startup, make sure the last command of the script starts
with “exec” followed by the main process of the container, in this case is “mysqld”. This
process will hold PID 1 during container runtime, and is critical when stopping the
container later on. Details on this in Chapter 6.1 - Service Control.
Finally, try to follow the general guidelines when writing a Dockerfile, especially when
building a public image. In this way, anyone else can easily understand the content of
the image and contribute to it.
13
Networking in Docker
Networking is critical in MySQL, it is fundamental to manage access to the database
server from client applications and other database servers (e.g. in master-slave
replication setups). The behaviour of a containerized MySQL service is determined
by how the MySQL container is started with the docker run command. A MySQL
container can be run in an isolated environment (only reachable by containers in the
same network), or an open environment (where the MySQL service is totally exposed to
the outside world) or the instance simply runs with no network at all.
Once Docker Engine is installed, Docker will create 3 types of networks by default:
• host
• none
• bridge
The bridge network also known as docker0, is the default network when you create
a container without specifying any networking related parameter (--net or -n). The
none network is using the “null” driver and no interface will be configured inside the
container, other than localhost. Bear in mind that host and none networks are not
directly configurable in Docker.
You can get a list of created networks in Docker with the docker network command:
1 $ docker network ls
2 NAME DRIVER SCOPE
3 bridge bridge local
4 db_multi overlay swarm
5 db_single bridge local
6 docker_gwbridge bridge local
7 host host local
8 ingress overlay swarm
9 none null local
Docker also supports multi-host network out-of-the-box through the Docker Swarm
overlay network. You can also use other network plugins to extend the Docker network
capabilities, as described in the upcoming chapters.
In order to run a container in this network, specify --net=host in the docker run
command:
14
1 $ docker run -d \
2 --name mysql-host \
3 --net host \
4 -e MYSQL_ROOT_PASSWORD=mypassword \
5 mysql:5.7
The container will be attached directly to the network stack of the hosts. If the host has
two network interfaces, the container will also have them. There is no isolation in this
network, which means containers created on this network are reachable by containers
created inside the bridge network.
eth0
Host
eth0 eth0
Container 1 Container 2
Docker
The container does not need any forwarding rules using iptables since it is already
attached to the same network as the host. Hence, port mapping and container linking
are not supported and Docker will not manage the firewall rules of containers that run
in this type of network. That means, you can only run one MySQL container per host
under this network.
This network is only helpful if you want to dedicate the host machine as a MySQL server,
and manage it under Docker.
Each outgoing connection will appear to originate from the host machine’s IP address.
15
4.2.1. Default Bridge
The default bridge network, also known as docker0, will be automatically created
by Docker upon installation. Basically, if you do not specify --net parameter in the
docker run command, Docker will create the container under this docker0 network:
1 $ docker run -d \
2 --name mysql-bridge \
3 -p 3308:3306 \
4 -e MYSQL_ROOT_PASSWORD=mypassword \
5 mysql:5.7
On the machine host, you will see a new virtual Ethernet interface, “veth” as illustrated
by the red box in the diagram below, and one Ethernet interface, “eth0” inside the
container:
eth0
Host
docker0
(virtual Ethernet bridge)
vethxxx vethyyy
eth0 eth0
Container 1 Container 2
Docker
Docker utilises iptables to manage packet forwarding to the bridge network via docker-
proxy. It redirects connections to the correct container through NAT. Thus, it supports
--publish parameters where you can run multiple containers with the same image
through different ports. Container linking is also supported, where you can expose
environment variables and auto-configured host mapping through /etc/hosts file
inside the linked containers.
To access the MySQL service, simply point the MySQL client to the Docker host and the
assigned port of the container, 3308 in this case. Only one default bridge network is
allowed per Docker host.
16
4.2.2. User-defined Bridge
In order to use user-defined bridge, the user has to create the network beforehand:
Then, pass the network name using --net or -n parameter to run the container under
this network:
1 $ docker run -d \
2 --name mysql1 \
3 --net db_single \
4 -p 3308:3306 \
5 --ip 192.168.10.10 \
6 --hostname mysql1 \
7 -e MYSQL_ROOT_PASSWORD=mypassword \
8 mysql:5.7
The main difference between the default bridge network and user-defined network is
the embedded DNS service. You can resolve a container’s name in the same network
to an IP address, which is practical for simple service discovery among database
containers. This network also provides a sticky IP address and hostname, using --ip
and --hostname parameters.
eth0
Host
db_single
DNS (virtual Ethernet bridge)
vethxxx vethyyy
eth0 eth0
Container 1 Container 2
Docker
17
This is the recommended network to run single-host MySQL containers, because
MySQL relies on a proper hostname or IP address for authentication and authorization.
There is also a performance consideration. By using IP address when granting a user,
MySQL does not need to perform reverse DNS lookup when a user is authenticating.
The reason is that resolving a name can potentially cause a problem if the DNS
resolver does not respond in a timely manner. But this is rarely happening in Docker’s
networking.
You can have multiple user-defined bridge networks in a host and you can run a
container connected to multiple networks as well by repeating the --net parameter for
each network:
1 $ docker run -d \
2 --name mysql1 \
3 --net db_single \
4 --net backend \
5 --net database \
6 -p 3308:3306 \
7 --ip 192.168.10.10 \
8 --hostname mysql1 \
9 -e MYSQL_ROOT_PASSWORD=mypassword \
10 mysql:5.7
Only containers within the same network can communicate with each other.
18
External
VXLAN
udp/4789
eth0 eth0
Host Host
docker_gw docker_gw
br0 br0
bridge bridge
Container 1 Container 2
Docker Docker
In the default overlay network, each container will have two network interfaces, eth0
and eth1. One is connected to docker_gwbridge as shown in black, while the other is
connected to VXLAN tunnel network through another bridge, represented in green.
The one that is connected to docker_gwbridge is the interface that faces the public
network. If you were trying to ping google.com, the container would go through eth0.
If from Container 1, you try to ping container 2 through eth0, you won’t get any reply.
The other interface eth1 is solely for container-to-container communication. You would
usually get two IP addresses for the overlay network interface. One is the container IP
address or in Swarm term “Task IP address”, and another one is the Service VIP or virtual
IP address.
19
External
VXLAN
udp/4789
eth0 eth0
Host Host
DNS
docker_gw docker_gw
br0 br0
bridge bridge
Container 1 Container 2
Docker Docker
This is the preferred network when deploying a MySQL container as a Swarm service
in a multiple Docker host environment due to its isolation and it’s ability to resolve the
container’s name. Docker orchestration is out of the scope of this whitepaper.
To summarize our options, Calico is the winner performance wise because it operates
on Layer 3 of the OSI layer. Weave is easy to configure and comes with DNS service.
Flannel and Contiv have been a long time on the market, and are pretty stable. Each of
20
them has its advantages and disadvantages, and it is strongly recommended to test and
compare, based on your workload.
1 $ docker ps
2 CONTAINER ID IMAGE COMMAND
CREATED STATUS PORTS
NAMES
3 8864688449d4 mysql:5.7.20 “docker-entry-
point...” About an hour ago Up About an hour 3306/tcp
my-test
Under PORTS column, we can tell that port 3306 is exposed for this container (as
defined inside Dockerfile for this image, or through --expose flag) without any
mapping to the host network. The exposed port is only accessible by containers within
the same Docker network (docker0).
1 $ docker run -d \
2 --name my-test \
3 --publish 8000:3306 \
4 --env MYSQL_ROOT_PASSWORD=mypassword \
5 mysql
21
To verify the port mapping from the host to the container, use docker ps command:
1 $ docker ps
2 CONTAINER ID IMAGE COMMAND
CREATED STATUS PORTS
NAMES
3 2198fb16b979 mysql:5.7.20 “docker-en-
trypoint...” 24 seconds ago Up 23 seconds
0.0.0.0:8000->3306/tcp my-test
Under PORTS column, we can see that port 3306 exposed for this container (as defined
inside the Dockerfile for this image, or through --expose flag) is mapped to port 8000
of the host network. The MySQL instance in this container is now remotely accessible
within Docker bridge network on port 3306 and port 8000 to the external network.
You can also use --publish-all or -P to publish all exposed ports to random ports
automatically. If the container is running on the host network with --net=host, the
exposed port will automatically become the published port.
You can then execute the necessary commands just like the normal terminal access
does. You can also pass any shell command to be running inside the container. The
following command executes a MySQL client within the container directly from the host
console:
This method is similar to treating the container as an independent host, and is usually
used by the administrators who have local access to the Docker host. You don’t have
to know the exposed or published ports for this container, it is enough to know the
container’s name or ID.
22
MySQL Containers and
Volumes
A container is a running instance of Docker images. MySQL is a disk-based relational
database management system (RDBMS) and in order to run a MySQL container, one
has to understand how Docker stores and manages its data.
1 $ docker run -d \
2 --name my-test \
3 --env MYSQL_ROOT_PASSWORD=mypassword \
4 mysql
In this example, we did not define any volume (--volume or -v). If a container is
running without a volume, all the directory or file modifications in the container will be
written in the container layer, which is the upper layer on top of the image layers, as
illustrated in the following diagram:
23
my-test
Container layers
d51f459239fb (RW)
4B
937bbdd4305a
35369f9634e1
0639788facc8
937bbdd4305a
Image layers
35369f9634e1 (RO)
0639788facc8 412MB
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: docker.io/mysql:latest
The image layers (shown in green) are read-only while the container layers (red) are
writable. The writable layer is also known as thin-pool layer. These two layers run on
union filesystem, depending on the Docker host operating system. For Ubuntu, it
defaults to AUFS. For RHEL/CentOS, it defaults to overlayfs or overlayfs2. If you use
Docker EE on RHEL, CentOS, or Oracle Linux, you must use the devicemapper storage
driver with direct-lvm approach, as this is the only supported storage driver by Docker
in a production system.
The container layer only utilizes around 4 bytes of disk space once started and you can
verify this using docker ps -s command:
1 $ docker ps -s
2 CONTAINER ID IMAGE NAMES SIZE
3 d51f459239fb mysql my-test 4B (virtual 412MB)
1 $ docker run -d \
2 --name my-test2 \
3 --env MYSQL_ROOT_PASSWORD=mypassword \
4 mysql
24
When creating another MySQL container using the same image, you are actually
creating a new writable layer to store data for the container. Each container has its own
writable container layer, which means multiple containers can share access to the same
underlying image and yet have their own data state.
my-test my-test2
Container layers
d51f459239fb (RW) e9de9ed50ced
4B + 4B
937bbdd4305a
35369f9634e1
0639788facc8
937bbdd4305a
Image layers
35369f9634e1 (RO)
0639788facc8 412MB
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: docker.io/mysql:latest
In this example, these two containers occupy 8 bytes of disk space once started with
two independent MySQL instances. The container layer will grow once we start to make
changes to the container, for example, if we create a new database, add new tables and
start inserting rows of data.
When you remove a container, you are basically removing the writable layer. Thus all the
changes inside the container layer will be removed as well. This layering and unification
technique is very space efficient, minimizes IO operations on the host and supports
caching.
25
my-test
New 2
416f2a97355f Branch
c33a2b88395d
d51f459239fb
Copy-on-Write
(CoW)
937bbdd4305a 1
35369f9634e1
0639788facc8
937bbdd4305a
35369f9634e1
0639788facc8
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: docker.io/mysql:latest
In example operation number 2, we are trying to create a new schema and table,
which means a new directory and file will be existed in the container layer. When you
create a new file inside a container, Docker will create a directory, or “branch” in AUFS
terminology with a random SHA-256 hash name. It will then place the file inside that
directory with metadata including a pointer to the parent directory. You can see this file
directly inside /var/lib/docker/aufs.
Docker supports a number of CoW filesystems and devices like overlayfs, btrfs, device
mapper and ZFS. Every storage driver handles the implementation differently, but all
drivers use stackable image layers with this Copy-on-Write strategy.
The following table shows the path required by MySQL for persisting data in a
container:
26
Description Variable Name Default Value (5.7)
{datadir}/{hostname}-slow.
Slow query log slow_query_log_file
log
Error log log_error {datadir}/{hostname}.err
General log general_log_file {datadir}/{hostname}.log
Most of the files or the directory generated by MySQL are default to datadir. Apart
from those, if you would like to use your custom my.cnf file, you should preserve
/etc/my.cnf.d, the MySQL configuration directory and store your my.cnf under this
directory. If you are using any other MySQL plugins, you might also want to preserve
{basedir}/lib/plugin directory. This directory can be mounted as RO using the
--mount flag in Docker. This is the recommended way to prevent the MySQL plugin
from executing any arbitrary code.
As you can see here, hostname is an important aspect of logging. Try to stick with
the same container hostname, especially when running another container, to avoid
confusion later on. Otherwise, you can use a custom filename for every logging option.
Using named volumes is much simpler and more independent. Docker creates a
directory under /var/lib/docker/volumes and the volume can be referred to by
other containers using the volume’s name, so you don’t have to remember the static
physical path. The volume contents exist outside of the container lifecycle. Since Docker
manages the directory’s content of the named volume, it can be extended with an
external volume plugin instead of relying on the local volume driver.
This opens the door for stateful applications to run efficiently in Docker. You don’t have
to worry about container upgrades, or the container being killed or crashing. If you run
with persistent storage mounted inside the container, another container can pick up
from the last stored state and continue from there.
You can also have another volume dedicated to storing non-persistent data in RAM (or
swap if memory is low). MySQL can leverage this type of volume for temporary data
directory (tmpdir).
27
1 $ docker run -d \
2 --name mysql-local \
3 -p 3308:3306 \
4 -e MYSQL_ROOT_PASSWORD=mypassword \
5 -v local-datadir:/var/lib/mysql \
6 mysql:5.7
mysql-local
/var/lib/mysql
416f2a97355f
937bbdd4305a
35369f9634e1
0639788facc8
937bbdd4305a
35369f9634e1
local-datadir
0639788facc8
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: docker.io/mysql:5.7
Volumes are often a better choice to persist your data, instead of using the container’s
writable layer, because using a volume does not increase the size of the containers
using it. The contents of the volume exists outside the lifecycle of the container. Starting
from version 17.06, you can also use --mount which is much more verbose. The
following command does the same thing:
1 $ docker run -d \
2 --name mysql-local \
3 -p 3308:3306 \
4 -e MYSQL_ROOT_PASSWORD=mypassword \
5 -v local-datadir:/var/lib/mysql \
6 mysql:5.7
28
Originally, the --mount option was meant for Swarm services and not for standalone
containers. With mount, the order of the key value pairs is not significant, and the value
of the flag is easier to understand.
1 $ docker run -d \
2 --name mysql-tmp \
3 -p 3308:3306 \
4 -e MYSQL_ROOT_PASSWORD=mypassword \
5 -v /mysql1/data:/var/lib/mysql \
6 --tmpfs /tmp:rw,size=1g,mode=177 \
7 mysql
When the container stops, the tmpfs mount is removed. If a container is committed, the
tmpfs mount is not saved. The following diagram illustrates a MySQL containers with
both persistent and non-persistent volumes:
3308
mysql-tmp
/tmp RAM
/var/lib/mysql /mysql1/data
416f2a97355f
937bbdd4305a
35369f9634e1
0639788facc8
937bbdd4305a
35369f9634e1
0639788facc8
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: docker.io/mysql:latest
29
However, there is one caveat. Beware of large sort operations that could occupy more
than the limit, because when this happens, the host would start swapping. To avoid this,
you could also use the standard named volume for /tmp. This feature must be used
with caution and only works for Linux containers, not for Windows.
3308
mysql-nfs
/var/lib/mysql /nfs/mysql-nfs
416f2a97355f
937bbdd4305a
35369f9634e1
0639788facc8
937bbdd4305a
35369f9634e1
0639788facc8 192.168.1.100
/storage/docker
f6016aab25f1
de70fa77eb2b
ad74af05f5a2
Image: docker.io/mysql
The remote directory can be mounted locally on the client host, which is the Docker
host:
30
You can then use the bind mount approach to mount the directory inside the Docker
container:
1 $ docker run -d \
2 --name mysql-nfs \
3 -p 3308:3306 \
4 -e MYSQL_ROOT_PASSWORD=mypassword \
5 -v /nfs/mysql-nfs:/var/lib/mysql \
6 mysql
In this example, we mounted the NFS path in the Docker host under /nfs directory,
from a remote host, 192.168.1.100. Also note that there are a number of considerations
when running MySQL on NFS. See Using NFS for MySQL for details.
The following diagram shows Docker volume drivers and storage platforms:
Flocker
Google PD
Convoy REX-ray
EMC Infinit
Contiv Horcrux
FUSE NFS
Fuxi Netshare
vSphere SMB
OpenStorage
iSCSI BeeGFS
OpenStack
CIFS NetApp Cinder Minio CephFS
The orange boxes in the outer layer are storage platforms or file systems that are
supported by various Docker volume plugins, the latter are represented in green. There
are lots more, you can view a more complete list on Docker documentation page.
31
Install the plugin you want to use. In this example, we’ll use REX-Ray, an open-source
volume driver plugin for Docker. First, install the volume plugin for EBS:
Then, verify if the driver is correctly installed. We use another tool here called ‘jq’, a
JSON query tool to beautify the output:
1 $ docker run -d \
2 --name=mysql-ebs \
3 --volume-driver=rexray/ebs \
4 -v ebs-datadir:/var/lib/mysql \
5 mysql:5.7
1 $ docker volume ls
2 DRIVER VOLUME NAME
3 local local-datadir1
4 local local-datadir2
5 rexray ebs-datadir
You can then manage the volume using the docker volume command directly. When
you delete the volume, the volume driver itself will do the necessary cleanup for you.
Volume drivers also allow you to extend Docker volume capabilities. For instance, some
storage systems support encryption-at-rest, volume snapshots and backups.
32
Sysbench Create Tables - 1,000,000 rows x 24 tables
(lower is better)
60
45
Time (seconds)
30
15
0
Local NFS NFS-tweaked EBS-standard
Here is a simple benchmark comparing the local disk and other remote storage with a
simple create table job using sysbench. The tests were repeated for 50 times on every
storage driver. You can see the green dots which represent performance of remote NFS
storage is around 40% slower than the local disk. With some further tweaking using a
couple of NFS mount options like noatime, nodiratime, hard, intr, we could get
around 10% improvement over the default NFS as shown by the orange dots. The red
dots represent Amazon Elastic Block Storage (EBS) volume without provisioned IOPS,
where the results are pretty inconsistent.
33
Monitoring and Management
6.1. Service Control
If you look at the entrypoint script for the MySQL image, you should see it executes the
mysqld process as a foreground process at the very last line with exec command. The
following snippet is from the end of the entrypoint script used in the MySQL image:
1 ...
2 ...
3 exec mysqld “$@”
This is to ensure that the mysqld process holds process ID 1 when the container is
started. The PID 1 has a special meaning in a container. When stopping a container, only
the process with PID 1 will receive the signal. Take a look at this example:
When we run docker stop command, Docker will send a signal to the container called
SIGTERM, to indicate process termination. Only the mysqld process will receive it, so
it will start terminating and perform any clean up required to shut down in a graceful
manner. Other processes like bash (pid 62) and ps (pid 86) will not receive this signal.
However, using docker stop is risky, because there is a risk that Docker will not allow
proper termination of the mysqld process. By default, docker stop command will send
SIGTERM and wait for 10 seconds, before it sends a SIGKILL to the container to force for
termination if it’s still running. You can change the grace period to something higher.
In some cases, the MySQL termination process requires more time. MySQL, after
receiving a SIGTERM, will:
1. stop receiving new connections,
2. then complete executing whatever queries are still pending (this can take a
while, depending on your workload and number of concurrent clients)
3. then start flushing data to disk (this can take many seconds, again depending
on your workload, configurations, available memory, and storage engine
choices),
4. after all the data-flushing is done, MySQL will then deallocate whatever
memory it allocated during the initialization stage (this can also take a while,
depending on the amount of memory allocated by MySQL),
5. close all file handles still open, and then call “exit(0)”.
34
To be safe when performing a graceful shutdown, use docker kill command with
a proper termination signal. This command doesn’t have a grace period so it won’t
interrupt the process cleanup operation by mysqld:
You can then verify the termination status by looking at the container’s log. Ensure you
got the “[Note] mysqld: Shutdown complete” at the end of the line. Stopping a
container won’t delete the container, so you can start it up again using docker start
command.
Memory is a very important resource in MySQL. This is where the buffers and cache are
stored. It is a critical resource for MySQL, as it reduces the impact of hitting the disk too
often. On the other hand, swapping is bad for MySQL performance. If --memory-swap
is set to the same value as --memory, and --memory is set to a positive integer, the
container will not have access to swap. If --memory and --memory-swap are set
to the same value, this will prevent containers from using any swap. This is because
--memory-swap is the amount of combined memory and swap that can be used, while
--memory is only the amount of physical memory that can be used. If --memory-swap
is not set, container swap defaults to --memory multiply by two.
You can also increase the limit of open file descriptors, or “nofile” to something higher.
This is to cater for the number of files that the MySQL server can open simultaneously
with --ulimit parameter. Setting this number a bit high is recommended. Example of
the docker run command with a recommended configuration for memory, swap and
open files:
1 $ docker run -d \
2 --name mysql-staging \
3 --memory 4g \
4 --memory-swap 4g \
5 --ulimit nofile=16824:16824 \
6 mysql:5.7.6
Some of the container resources like memory and cpu can be controlled dynamically
(without restart) through docker update, as shown below:
1 $ docker update \
2 --memory 6g \
3 --memory-swap 6g \
4 mysql-staging
35
6.3. Resource Monitoring
Docker provides a way to summarize the container resource consumption through
docker stats command. It can report in real-time similar to the top command. The
following command shows example of using the docker stats command with custom
output formatting:
The docker stats command is an interface to the stats application program interface
(API) endpoint. The stats API exposes all of the information in the stats command and
more. To see for yourself, run the following command on Docker host:
The output of the above command is wrapped in a JavaScript Object Notation (JSON)
array, which is ready to be ingested by third-party tools.
Inside a container, free and top commands are not accurate because these tools rely
on the value reported under /proc on the host. To monitor these resources correctly,
you have to see the cgroup directory. These paths are mounted as read-only in the
container as well if you run the container in unprivileged mode:
• Memory - /sys/fs/cgroup/memory/memory.*
• CPU - /sys/fs/cgroup/cpu/cpu.*
• Disk IO - /sys/fs/cgroup/blkio/blkio.*
There are also external open-source command line tools like sysdig and dockviz. Sysdig
hooks into the host’s kernel, which means it doesn’t entirely rely on getting metrics from
the Docker daemon. It also allows you to add orchestration context by hooking directly
into the orchestrator, thereby allowing you to troubleshoot by container resources like
pod, cluster, namespace and service.
Container visibility and monitoring are much better with graphical user interface. There
are a number of open-source tools you can use to monitor the Docker resources such
as cAdvisor, Portainer, Shipyard, Rancher and Prometheus. cAdvisor and Portainer can
be considered the most popular, super lightweight and easy to install. Most of them can
be installed and running with a one-liner command. These tools make use of Docker
unix socket file to connect to the daemon process and retrieve monitoring data.
You can also opt for paid solution vendors like New Relic, Dynatrace, Datadog and
CoScale. Most of them provide full-stack monitoring and alerting service that monitors
everything from the server and container resources, up to application performance.
36
6.4. Configuration Management
Most of MySQL configuration parameters can be changed during runtime, which means
you don’t need to restart to load up the changes. Check in the MySQL documentation
page for details. If the parameter changes require restarting MySQL, set the changes
inside /etc/my.cnf.d/my.cnf on the host, and map the directory to a named volume
like in the example below:
1 $ docker run -d \
2 --name mysql2 \
3 -e MYSQL_ROOT_PASSWORD=mypassword \
4 -v mysql2-conf:/etc/my.cnf.d \
5 mysql:5.7.6
You can also use the bind mount approach to map a file on the host directly into the
container:
1 $ docker run -d \
2 --name mysql2 \
3 -e MYSQL_ROOT_PASSWORD=mypassword \
4 -v /root/docker/mysql2/config/my.cnf:/etc/my.cnf \
5 mysql:5.7.6
Also, you can append the configuration options, right after the image name, passed as
flags to the mysqld process inside the container:
1 $ docker run -d \
2 --name mysql3 \
3 -e MYSQL_ROOT_PASSWORD=mypassword \
4 mysql:5.7.6 \
5 --innodb_buffer_pool_size=1G \
6 --max_connections=100
Generally, standard MySQL and InnoDB optimization applies to the container, for
example, innodb_buffer_pool_size, innodb_log_file_size and max_connections. This is
highly dependent on the resource allocated to the containers (as explained in Chapter
6.2 - Resource Control), as well as the underlying disk sub-system (as explained in
Chapter 5 - MySQL Containers and Volumes).
6.5. Security
Docker Secrets was introduced in v17.1 to handle sensitive information during container
runtime. If you look at the previous example commands, we usually have to define an
environment variable called MYSQL_ROOT_PASSWORD equal to your root password, in
clear text format. This method exposed the password in clear text and can be traced
through docker inspect or using Linux history command.
37
To create a secret, use the docker secret create command and send the sensitive
data through stdin. Then, when running a container, specify the secret name to be
mounted under /run/secrets directory as a file. To create a secret, simply:
The image that we used here supports Docker Secrets, so we can define it through the
MYSQL_ROOT_PASSWORD_FILE environment variable and the image will read the secret
file and pass it as the MySQL root password when initializing the container:
Another security aspect that we have to look at is the runtime privileges. There are two
types of runtime privileges:
• Unprivileged mode is default and recommended.
• Privileged mode.
38
6.5.2. Privileged Container
On some occasions, you would probably need to modify the kernel parameters like
sysctl.conf or /proc to suit your database needs and workloads. In that case, you have
to start the container with --privileged flag. Docker will mount all devices with read-
write, so you can make those changes accordingly. With --privileged mode, the
container has almost all the capabilities of the host machine.
Use --privileged with caution, and only if you really need to modify kernel
parameters (sysctl, /proc, /sys) or you want to run a container inside another container.
6.6.1. Backup
Taking a logical backup is pretty straightforward because the MySQL image also
includes mysqldump and mysqlpump (MySQL 5.7.8+). You simply use the docker exec
command to run mysqldump and redirect the stdout output to a path on the host:
Binary backup like Percona Xtrabackup and MariaDB Backup requires the process to
access the MySQL data directory directly. You have to either install these tools inside the
container, or through the machine host or use a dedicated image for it. The following
example shows how to perform an xtrabackup backup, using “perconalab/percona-
xtrabackup” image to create the backup and store it inside /tmp/backup on the host:
You can also apply a global lock or stop the container and just copy over the data
volume to another location, as shown in the following example:
1. Flush all tables to disk:
1 $ cp /var/lib/docker/volumes/mysql-datadir/_data /desti-
nation/in/physical/host
39
3. In the same terminal as in step 1, release the lock:
6.6.2. Restore
For mysqldump, restoration is pretty similar to the backup procedure. You can simply
redirect the stdin into the container from the Docker host:
You can also use the standard mysql client command line remotely with a proper
hostname and port value instead of using this docker exec command.
For Xtrabackup, you have to prepare the backup beforehand, and the prepared backup
then can be used as the mysql datadir in another container:
If the backup was taken using the snapshot, it’s easy to duplicate the database state
and run on multiple containers for testing purposes. With bind mount, you can directly
mount the directory as a volume. If you would like to use a named volume, you need to:
1. Create a volume.
2. Copy the content to the volume’s directory.
3. Start a new container by mounting the volume.
6.7. Upgrades
You can perform MySQL upgrade operation using two ways - logical or in-place
upgrade. Keep in mind that MySQL only supports upgrade from one previous version.
If you are on 5.5 and would like to upgrade to 5.7, you have to upgrade to MySQL 5.6
first, followed by another upgrade step to MySQL 5.7. You can use the in-place upgrade
to achieve this pretty easily.
40
The logical upgrade procedure is summarized in the following table:
# Step Command
1 Pull the new image, M. $ docker pull mysql:5.7.18
$ docker run -d \
--name=mysql-new \
Start new container, B with
2 -e MYSQL_ROOT_PASSWORD=mypassword \
new image, M.
-v local-datadir:/var/lib/mysql \
mysql:5.7.18
$ docker exec -it mysql-old mysqldump
3 Export MySQL on A.
-uroot -p > dump.sql
4 Stop A. $ docker kill --signal=TERM mysql-old
$ docker exec -it mysql-new mysql -uroot
5 Import into B.
-p < dump.sql
$ docker exec -it mysql-new mysql_upgrade
6 Run mysql_upgrade inside B.
-uroot -p
7 OK? Remove A. $ docker rm -f mysqld-old
$ docker stop mysqld-new
8 Fallback? Stop B, start A.
$ docker start mysqld-old
# Step Command
1 Pull the new image, M. $ docker pull mysql:5.7.18
$ docker exec -it mysql-old mysql -uroot
Set innodb_fast_shutdown=0
2 -p -e ‘SET GLOBAL innodb_fast_shutdown =
inside container.
0’
3 Stop the container, A. $ docker kill --signal=TERM mysqld-old
$ docker run -d \
--name mysql-new \
Start a new container, B with --publish 3307:3306 \
4
new image, M. -e MYSQL_ROOT_PASSWORD=mypassword \
-v local-datadir:/var/lib/mysql \
mysql:5.7.18
$ docker exec -it mysql-new mysql_upgrade
5 Run mysql_upgrade inside B.
-uroot -p
6 OK? Remove A. $ docker rm -f mysqld-old
$ docker stop mysqld-new
7 Fallback? Stop B, start A.
$ docker start mysqld-old
41
6.8. Housekeeping
When working with multiple MySQL containers, you might find that your host machine’s
disk space starts to run out, especially when you build the image directly on the host.
Occasionally, some of the intermediate images might still there. It is safe to remove all
dangling images with this command:
To clean up unused volumes, you can use the docker volume prune command to
achieve this. For example, if you would like to remove all unused volumes (volumes that
are not associated with any running container any longer):
Housekeeping has to be done regularly to ensure MySQL has enough resources to run.
It’s recommended to set up a resource monitoring and alerting system for the Docker
host, so you can take proactive actions before you run out of resources.
42
ClusterControl on Docker
ClusterControl is a management and automation platform for open source databases. It
can be used to deploy, manage, monitor and scale highly available clusters. It supports
all types of MySQL high availability setups, including MySQL Replication, MySQL Cluster,
MySQL Group Replication and Galera Cluster. It also supports MongoDB ReplicaSets
and Sharded Clusters, as well as streaming replication for PostgreSQL.
Severalnines, the team behind ClusterControl, has built an image specifically to make
ClusterControl run well on Docker. This image comes with ClusterControl and all of its
components and dependencies - ClusterControl 1.5 suite, including controller, REST API
interface, web user interface, notification and web-ssh package installed via repository.
The image is also pre-configured with all applications required by ClusterControl like
MySQL, Apache, SSL certificates as well as an SSH key for ClusterControl usage.
Then, run the container and specify it under this network, with a static IP address and
hostname:
43
We use supervisord to manage daemons inside the container. So if you would like to
perform service control without restarting the container, you have to use supervisorctl
command line. The following services inside the container are managed by supervisord:
• sshd
• mysqld
• httpd
• cmon
• cmon-ssh
• cmon-events
• cmon-cloud
• cc-auto-deployment
The following example shows how we should restart ClusterControl Controller service
(cmon) inside the container:
By default, the ClusterControl UI is exposed on port 80 and 443 of the Docker host. You
can run it in the host network, so you can monitor and manage other DB nodes outside
of the Docker network (e.g., DB nodes running on physical hosts connected to the same
switch, or VM instances running in the cloud). Or you can run ClusterControl under the
user-defined bridge network, and have your database cluster pack in one single host.
1 $ for i in {1..3}; do
2 docker run -d \
3 --name galera${i} \
4 -p 666{$i}:3306 \
5 --link clustercontrol:clustercontrol \
6 -e CLUSTER_TYPE=galera \
7 -e CLUSTER_NAME=mygalera \
8 -e INITIAL_CLUSTER_SIZE=3 \
9 severalnines/centos-ssh
10 done
44
The database deployment flow is described as below:
1. Create the database containers through docker run command.
2. The cc-auto-deployment script picks up the registered containers and creates a
deployment job.
3. ClusterControl deploys the cluster once the INITIAL_CLUSTER_SIZE is reached.
Docker
master slave1
slave2 slave3
proxysql clustercontrol
45
1. Create a Docker network, 192.168.10.0/24 (db-cluster), for persistent IP
addresses and hostnames:
3. Run containers for the MySQL Replication instances. Make sure ClusterControl
container starts first and then start the MySQL master, 192.168.10.100 on port
6000:
1 $ for i in {1..3}; do
2 docker run -d --name slave${i} \
3 -v /storage/slave${i}/datadir:/var/lib/mysql \
4 -h slave${i} \
5 -p 600${i}:3306 \
6 --network db-cluster \
7 --ip 192.168.10.10${i} \
8 -e AUTO_DEPLOYMENT=0 \
9 -e CC_HOST=192.168.10.10 \
10 severalnines/centos-ssh
11 done
46
5. Log into the ClusterControl UI at https://{docker_host}:5001/clustercontrol,
register the default admin user and go to “Deploy” -> “MySQL Replication” to
start the deployment. Take note that AUTO_DEPLOYMENT is turned off, so we
have better control of the installation via the ClusterControl UI. The centos-ssh
image should already have pre-configured the SSH key, which is located at
/root/.ssh/id_rsa inside the ClusterControl container. Enter the following
details in the deployment wizard:
1 $ docker run -d \
2 --name proxysql1 \
3 -v /storage/proxysql1/datadir:/var/lib/proxysql \
4 -p 6033:3306 \
5 --network db-cluster \
6 --ip 192.168.10.201 \
7 -e AUTO_DEPLOYMENT=0 \
8 -e CC_HOST=192.168.10.10 \
9 severalnines/centos-ssh
7. Go to ClusterControl -> your cluster -> Manage -> Load Balancers ->
ProxySQL -> Deploy ProxySQL. Specify 192.168.10.201 as the ProxySQL Address
and fill in the remaining input fields. Click Deploy to start the deployment
process.
After the deployment completes, you will have a 4-node MySQL Replication setup with
ProxySQL as load balancer.
47
7.4. Add Existing Database Containers
SSH is the main communication channel for ClusterControl, so having it run on the
monitored host or container is mandatory. This can be a bit of a hassle though,
because most of the MySQL images do not have SSH packages installed. This is one
of the reasons we came up with the “severalnines/centos-ssh” image to simplify the
deployment process.
If you already have a database cluster running on Docker, and you would like
ClusterControl to manage it, you can simply run the ClusterControl container in the
same Docker network as the database containers. The only requirement is to ensure the
target containers have SSH related packages installed (openssh-server, openssh-clients).
Then allow passwordless SSH from ClusterControl to the database containers. Once
done, use the “Add Existing Server/Cluster” feature to import the database setup into
ClusterControl.
Let’s say we have a physical host, 192.168.50.130 installed with Docker, and assume there
is a three-node Galera Cluster running under the standard Docker bridge network.
We are going to import the cluster into ClusterControl, which is running in another
container on the same host. The following is the high-level architecture diagram:
Docker
clustercontrol
Physical host
192.168.50.130
Install OpenSSH related packages on each of the database containers, allow root login,
start it up and set the root password:
48
Start the ClusterControl container as daemon and forward port 80 on the container to
port 5000 on the host:
The last step is setting up passwordless SSH to all database containers. Attach to the
ClusterControl container interactive console:
1 $ ssh-copy-id 172.17.0.2
2 $ ssh-copy-id 172.17.0.3
3 $ ssh-copy-id 172.17.0.4
Start importing the cluster into ClusterControl. Open a web browser and go to
Docker’s physical host IP address with the mapped port e.g, http://192.168.50.130:5000/
clustercontrol and click “Add Existing Cluster/Server” and specify the necessary details
like MySQL vendor, version, MySQL root user and password and so on, as well as the IP
address or hostname of the database containers.
Ensure you got the green tick when entering the hostname or IP address, indicating that
ClusterControl is able to communicate with the node. Then, click the Import button and
wait until ClusterControl finishes the job. The database cluster will be listed under the
ClusterControl dashboard once it is imported.
49
Summary
Docker is probably the most talked about technology in the past few years. It has a
very promising future and is currently well on its way to establish itself as a mainstream
application deployment standard. Despite the challenges in using Docker for stateful
database services, MySQL as well as PostgreSQL and MongoDB are typically in the top
10 deployed technologies in Docker surveys from different technology vendors. Both
Docker and MySQL have huge user bases, each with their own vibrant community. We
will most probably see more adoption of these technologies together, especially as the
ecosystem of database tools and utilities for Docker expands.
Although Docker can help automate deployment of MySQL, the database still has
to be managed and monitored. Crashes do not fix themselves, and care has to be
taken in order to ensure data integrity. Operation teams need to be notified in case
of performance issues or failures, backups need to be taken, administrators and
developers need access monitoring metrics in order to understand and optimize
performance. There is a lot to think about when managing a database environment,
and combining with something like ClusterControl can provide a complete operational
platform for production database workloads.
50
About ClusterControl
ClusterControl is the all-inclusive open source database management system for
users with mixed environments that removes the need for multiple management
tools. ClusterControl provides advanced deployment, management, monitoring, and
scaling functionality to get your MySQL, MongoDB, and PostgreSQL databases up-
and- running using proven methodologies that you can depend on to work. At the core
of ClusterControl is it’s automation functionality that let’s you automate many of the
database tasks you have to perform regularly like deploying new databases, adding and
scaling new nodes, running backups and upgrades, and more. Severalnines provides
automation and management software for database clusters. We help companies
deploy their databases in any environment, and manage all operational aspects to
achieve high-scale availability.
About Severalnines
Severalnines provides automation and management software for database clusters. We
help companies deploy their databases in any environment, and manage all operational
aspects to achieve high-scale availability.
Severalnines’ products are used by developers and administrators of all skills levels to
provide the full ‘deploy, manage, monitor, scale’ database cycle, thus freeing them from
the complexity and learning curves that are typically associated with highly available
database clusters. Severalnines is often called the “anti-startup” as it is entirely self-
funded by its founders. The company has enabled over 12,000 deployments to date
via its popular product ClusterControl. Currently counting BT, Orange, Cisco, CNRS,
Technicolor, AVG, Ping Identity and Paytrail as customers. Severalnines is a private
company headquartered in Stockholm, Sweden with o ces in Singapore, Japan and the
United States. To see who is using Severalnines today visit:
https://www.severalnines.com/company
51
Related Whitepapers
DIY Cloud Database on Amazon Web Services:
Best Practices
Over the course of this paper, we cover the details of AWS
infrastructure deployment, considerations for deploying your
database server(s) in the cloud, and finish with an example
overview of how to automate the deployment and management
of a MongoDB cluster using ClusterControl.
Download here
Download here
Download here
Download here
52
53