Use Docker Secrets with MySQL on Docker Swarm
Today we will use Docker Secrets, more specifically store our MySQL Passwords in Secrets, which will be passed to our containers, so that we don't use clear text passwords in our Compose files.
What is Docker Secrets:
In Docker, Docker Secrets are encrypted during transit and at rest in a Docker Swarm Cluster. The great thing about Docker Secrets is that you can manage these secrets from a central place, and the fact that it encrypts the data and transfers the data securely to the containers that needs the secrets. So you authorize which containers needs access to these secrets.
So instead of setting the MySQL Root Passwords in clear text, you will create the secrets, then in your docker-compose file, you will reference the secret name.
Deploy MySQL with Docker Secrets
We will deploy a Stack that contains MySQL and Adminer (WebUI for MySQL).
We will make the MySQL Service Persistent by setting a constraint to only run on the Manager node, as we will create the volume path on the host, and then map the host to the container so that the container can have persistent data. We will also create secrets for our MySQL Service so that we dont expose any plaintext passwords in our compose file.
Our Docker Compose file:
version: '3.3'
services:
db:
image: mysql
secrets:
- db_root_password
- db_dba_password
deploy:
replicas: 1
placement:
constraints: [node.role == manager]
resources:
reservations:
memory: 128M
limits:
memory: 256M
ports:
- 3306:3306
environment:
MYSQL_USER: dba
MYSQL_DATABASE: mydb
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
MYSQL_PASSWORD_FILE: /run/secrets/db_dba_password
networks:
- appnet
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- type: bind
source: /opt/docker/volumes/mysql
target: /var/lib/mysql
adminer:
image: adminer
ports:
- 8080:8080
networks:
- appnet
secrets:
db_root_password:
external: true
db_dba_password:
external: true
networks:
appnet:
external: true
Dependencies:
As we specified our secrets and networks as external resources, it needs to exist before we deploy our stack. We also need to create the directory for our mysql data, as the data will be mapped from our host to our container.
Create the Overlay Network:
$ docker network create --driver overlay appnet
Create the Secrets:
$ openssl rand -base64 12 | docker secret create db_root_password -
$ openssl rand -base64 12 | docker secret create db_dba_password -
List the Secrets:
$ docker secret ls
ID NAME CREATED UPDATED
jzhrwyxkiqt8v81ow0xjktqnw db_root_password 12 seconds ago 12 seconds ago
plr6rbrqkqy7oplrd21pja3ol db_dba_password 4 seconds ago 4 seconds ago
Inspect the secret, so that we can see that theres not value exposed:
$ docker secret inspect db_root_password
[
{
"ID": "jzhrwyxkiqt8v81ow0xjktqnw",
"Version": {
"Index": 982811
},
"CreatedAt": "2017-11-23T14:33:17.005968748Z",
"UpdatedAt": "2017-11-23T14:33:17.005968748Z",
"Spec": {
"Name": "db_root_password",
"Labels": {}
}
}
]
Create the Directory for MySQL:
$ mkdir -p /opt/docker/volumes/mysql
Deployment Time!
Deploy the stack:
$ docker stack deploy -c docker-compose.yml apps
Creating service apps_adminer
Creating service apps_db
As you can see the data of our MySQL container resides on our host, which makes the data persistent for the container:
$ ls /opt/docker/volumes/mysql/
auto.cnf ca-key.pem ca.pem client-cert.pem client-key.pem ib_buffer_pool ibdata1 ib_logfile0 ib_logfile1 ibtmp1 mydb mysql performance_schema private_key.pem public_key.pem server-cert.pem server-key.pem sys
Connect to MySQL
The value of our secrets will reside under /run/secrets/
in our container, as we have mapped it to our mysql container, lets have a look at them:
$ docker exec -it $(docker ps -f name=apps_db -q) ls /run/secrets/
db_dba_password db_root_password
View the actual value of the db_root_password
:
$ docker exec -it $(docker ps -f name=apps_db -q) cat /run/secrets/db_root_password
mRpcY1eY2+wimf10
Connecting to MySQL:
$ docker exec -it $(docker ps -f name=apps_db -q) mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 5.7.20 MySQL Community Server (GPL)
Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
As we have deployed adminer, you can access the Adminer WebUI on the Host's IP and the Defined Port.
Testing Data Persistance:
$ docker exec -it $(docker ps -f name=apps_db -q) mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
mysql> create database ruan;
Query OK, 1 row affected (0.00 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb |
| mysql |
| performance_schema |
| ruan |
| sys |
+--------------------+
6 rows in set (0.00 sec)
mysql> exit;
Bye
Verify the hostname of our container, before we kill the container:
$ docker exec -it $(docker ps -f name=apps_db -q) hostname
bdedb54bbc2b
Kill the container:
$ docker kill $(docker ps -f name=apps_db -q)
bdedb54bbc2b
Verify the status of the MySQL Service, as we can see the service count is 0, so the container was succesfully killed.
$ docker service ls -f name=apps_db
ID NAME MODE REPLICAS IMAGE PORTS
nzf96q05fktm apps_db replicated 0/1 mysql:latest *:3306->3306/tcp
After waiting for a couple of seconds, we can see the service is in service again, then check the hostname so that we can confirm that its a new container:
$ docker service ls -f name=apps_db
ID NAME MODE REPLICAS IMAGE PORTS
nzf96q05fktm apps_db replicated 1/1 mysql:latest *:3306->3306/tcp
$ docker exec -it $(docker ps -f name=apps_db -q) hostname
95c15c89f891
Logong to MySQL again and verify if our perviously created database is still there:
$ docker exec -it $(docker ps -f name=apps_db -q) mysql -u root -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mydb |
| mysql |
| performance_schema |
| ruan |
| sys |
+--------------------+
6 rows in set (0.01 sec)
By design docker is stateless, but as we mapped the host's path to the container our data is persistent. As we have set a constraint so that the container must only spin up on this node, the container will always have access to the data path.