I was running multiple docker containers on DO cloud droplet with low 1G memory and 1 vCore, specifically –
- Traefik Reverse Proxy x 1
- MariaDb x 3
- WordPress Apache x 3
- Adminer x 3
All in all a cool 3 DBs, and 7 webservers. Recipe for disaster right. No wonder as Apache spooled up worker processes and MariaDb’s InnoDb engine started fulfilling requests, one of the containers ran out of memory and the droplet CPU was running all time high.
docker ps -a
docker stats
docker logs 4703f6a45c3a
htop
free -m
Now I still wish to serve up all these sites, so what do I do ? First take backup of the databases.
docker inspect 4703f6a45c3a | grep IPAddress
mysqldump -u USER -p -h XXX.XXX.XX.XX blog1 | gzip > blog1.gz
Decrease Number of Containers
Only one database now serves up all three websites and has its one Adminer interface.
- Traefik Reverse Proxy x 1
- MariaDb x 1
- WordPress Apache x 3
- Adminer x 1
Managing Docker Networks
Docker container has two networks connected as:
- Web – for Traefik – browser, Adminer – browser, and WP/Apache-browser communications
- Internal – for MariaDb – WP and MariaDb – Adminer communications
Both networks were defined externally (and not in docker-compose).
docker network ls
docker network create web
docker network create --driver bridge internal
docker network inspect internal
Access /Edit files and Bash inside Docker Containers
First access the containers name and id. Then access the relevant container and copy the relevant file to the current directory. Edit the file and copy it back to the container. Restart container
docker ps -a
docker cp 4703f6a45c3a:/etc/mysql/my.cnf .
nano my.cnf
docker cp my.cnf 4703f6a45c3a:/etc/mysql/my.cnf
docker restart 4703f6a45c3a
Code language: JavaScript (javascript)
One can also access the bash shell of the container
docker exec -it 4703f6a45c3a bash
Traefik Configuration
Generate the traefik dashboard access password
htpasswd -nb admin secure_password
traefik.toml file.
#https://www.digitalocean.com/community/tutorials/how-to-use-traefik-as-a-reverse-proxy-for-docker-containers-on-ubuntu-18-04
defaultEntryPoints = ["http", "https"]
[entryPoints]
[entryPoints.dashboard]
address = ":8080"
[entryPoints.dashboard.auth]
[entryPoints.dashboard.auth.basic]
users = ["traefikusername:$hasedPassword"]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[api]
entrypoint="dashboard"
[acme]
email = "mail@provider.com"
storage = "acme.json"
entryPoint = "https"
onHostRule = true
[acme.httpChallenge]
entryPoint = "http"
[docker]
domain = "blog1.com"
watch = true
network = "web"
Code language: PHP (php)
acme.json file to store certificates
touch acme.json
chmod 600 acme.json
Code language: CSS (css)
Command to spin up Traefik container
docker run -d \
-v /var/run/docker.sock:/var/run/docker.sock \
-v $PWD/traefik.toml:/traefik.toml \
-v $PWD/acme.json:/acme.json \
-p 80:80 \
-p 443:443 \
-l traefik.frontend.rule=Host:traefik.blog1.com \
-l traefik.port=8080 \
--network web \
--name traefik \
traefik:1.7.2-alpine
Code language: PHP (php)
Docker-Compose configuration
Docker-compose for MariaDb Database
version: "3"
services:
db:
image: mariadb:10.4.3
environment:
MYSQL_RANDOM_ROOT_PASSWORD: '1'
networks:
- internal
labels:
- traefik.enable=false
volumes:
- ./datadir:/var/lib/mysql
adminer:
image: adminer:4.6.3-standalone
labels:
- traefik.backend=adminer
- traefik.frontend.rule=Host:adminer.blog1.com
- traefik.docker.network=web
- traefik.port=8080
networks:
- internal
- web
depends_on:
- db
networks:
web:
external: true
internal:
external: true
Code language: JavaScript (javascript)
Note the MariaDb root password and keep it safe
docker ps -a
docker logs dbContainerName | grep password
Connect to the MySQL container through a mysql client. Once in the mysql shell, create a User and Database in MariaDb for your blog and grant that user privileges to your database.
docker inspect logs dbContainerName | grep IPA
# NOte the IP address
mysql -u root -h XXX.XXX.XX.XX -p
# Enter the root password to reach the database shell >
CREATE USER 'MariaDbUser'@'%' IDENTIFIED BY 'MariaDbUserPassword';
GRANT ALL ON blog1.* TO 'username'@'%';
flush privileges;
Code language: PHP (php)
Docker-compose for Apache/Wordpress images.
version: "3"
services:
blog1:
image: wordpress:5.1.1-apache
environment:
WORDPRESS_DB_HOST: db
WORDPRESS_DB_USER: MariaDbUser
WORDPRESS_DB_PASSWORD: MariaDbUserPassword
WORDPRESS_DB_NAME: blog1
labels:
- traefik.backend=blog1
- traefik.frontend.rule=Host:www.blog1.com, blog1.com
- traefik.docker.network=web
- traefik.port=80
- traefik.frontend.headers.SSLRedirect:true
- traefik.frontend.headers.SSLForceHost:true
- traefik.frontend.headers.SSLHost:www.blog1.com
- traefik.frontend.redirect.regex:https://blog1.com([/](.*))*
- traefik.frontend.redirect.replacement:https://www.blog1.com$${1}
volumes:
- ./wordpress_files:/var/www/html
- ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini
networks:
- internal
- web
networks:
web:
external: true
internal:
external: true
Code language: JavaScript (javascript)
PHP Configuration
A note about uploads.ini. It is to ensure uploading of files and plugins to the wordpress site. Its contents include –
file_uploads = On
memory_limit = 128M
upload_max_filesize = 128M
post_max_size = 20M
max_execution_time = 600
WordPress Installation and Database Restore
Now I could have visited www.blog1.com and install WordPress. However, I first restore my MySQL database of blog1. This was easily accomplished by the adminer.blog1.com. While logging in, for host, use ‘db’, and either login as root or as the MariaDbUser with the appropriate password.
Apache Tuning
The WordPress Apache image can start consuming a lot of memory very fast. The defaults can be viewed by logging on to the calendar
docker exec -it dbContainerName bash
cat /etc/apache2/apache2.conf
# View the enabled Modules
apache2ctl -M
# View the prefork module settings
cat /etc/apache2/mods-enabled/mpm_prefork.conf
# https://www.jeffgeerling.com/blog/3-small-tweaks-make-apache-fly
# Apache thread memory consumption
ps aux | grep 'httpd' | awk '{print $6/1024 " MB";}'
ps aux | grep 'httpd' | awk '{print $6/1024;}' | awk '{avg += ($1 - avg) / NR;} END {print avg " MB";}'
Code language: PHP (php)
apache2.conf defaults
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
HostnameLookups Off
LogLevel warn
ErrorLog ${APACHE_LOG_DIR}/error.log
PidFile ${APACHE_PID_FILE}
DefaultRuntimeDir ${APACHE_RUN_DIR}
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf
Include ports.conf
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /usr/share>
AllowOverride None
Require all granted
</Directory>
<Directory /var/www/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
AccessFileName .htaccess
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent
Code language: HTML, XML (xml)
apache2ctl -M
Loaded Modules:
core_module (static)
so_module (static)
watchdog_module (static)
http_module (static)
log_config_module (static)
logio_module (static)
version_module (static)
unixd_module (static)
access_compat_module (shared)
alias_module (shared)
auth_basic_module (shared)
authn_core_module (shared)
authn_file_module (shared)
authz_core_module (shared)
authz_host_module (shared)
authz_user_module (shared)
autoindex_module (shared)
deflate_module (shared)
dir_module (shared)
env_module (shared)
expires_module (shared)
filter_module (shared)
mime_module (shared)
mpm_prefork_module (shared)
negotiation_module (shared)
php7_module (shared)
reqtimeout_module (shared)
rewrite_module (shared)
setenvif_module (shared)
status_module (shared)
Code language: JavaScript (javascript)
Among these, the mpm_prefork module is required for serving up PHP pages and is the primary target of tuning.
mpm_prefork.conf defaults
# prefork MPM
# StartServers: number of server processes to start
# MinSpareServers: minimum number of server processes which are kept spare
# MaxSpareServers: maximum number of server processes which are kept spare
# MaxRequestWorkers: maximum number of server processes allowed to start
# MaxConnectionsPerChild: maximum number of requests a server process serves
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 150
MaxConnectionsPerChild 0
</IfModule>
Code language: PHP (php)
Editing the config files
This is better done on host machine since the container lacks vim or nano.
docker cp dbContainerName:/etc/apache2/apache2.conf .
cp apache2.conf apache2.conf.default
nano apache2.conf
docker cp apache2.conf dbContainerName:/etc/apache2/apache2.conf
docker cp dbContainerName:/etc/apache2/mods-available/mpm_prefork.conf .
cp mpm_prefork.conf mpm_prefork.conf.default
nano mpm_prefork.conf dbContainerName:/etc/apache2/mods-available/mpm_prefork.conf
Code language: JavaScript (javascript)
Some settings for apache2.conf for low traffic WP sites
MaxKeepAliveRequests 30
KeepAliveTimeout 3
LogLevel error
Some settings for mpm_prefork.conf for low traffic WP sites
# https://www.linode.com/docs/web-servers/apache-tips-and-tricks/tuning-your-apache-server/
# https://httpd.apache.org/docs/2.4/misc/perf-tuning.html
<IfModule mpm_prefork_module>
StartServers 2
MinSpareServers 2
MaxSpareServers 6
MaxRequestWorkers 15
MaxConnectionsPerChild 200
</IfModule>
Code language: PHP (php)
Restart the WP/Apache container
docker containerId restart
Check apache version inside container
apachectl -v
Server version: Apache/2.4.25 (Debian)
Server built: 2018-11-03T18:46:19
Download and Extract the 64 bit deb installer files from https://www.modpagespeed.com/doc/download . After extraction, you will have two more zip files that again need to be extracted. we are looking for etc, var and usr folders and their contents in the data.tar.gz archive inside the deb file. Move these there folders to a data folder.
docker cp ./path/to/mod_pagespeed_24/data/. containerID:/
Enable the module and restart the docker container
docker exec -it containerID a2enmod pagespeed
docker restart containerID
Check that the pagespeed module is active or not LINK
Hopefully, these tips will help you in achieving better performance with small resources !