Overview
I'll try to disclose this topic in details and show you the best ways to solve such problems.
So, let's imagine that your project is developed on PHP and it was dockerized before. You're using a single node for your whole stack and a remote MySQL server as a database server.
You've launched a marketing campaign today and a huge wave of visitors has flown to your website, but something is wrong. Surprisingly everything began slowing down and users are experiencing a bad experience. Or it can be even worse: your website started to throw errors and just stopped opening at all. Am I talking about something familiar? So let's go!
Ideally, you should use applications in Docker when they're serverless, in other words when they're not dependent on permanent files or data writing to the disk, so we're able to easily launch them on any available server. But in most cases the situation is different, so let's review the example of usage of Wordpress inside of Docker (not the best option), but it will work to provide you with clear examples of how to correctly use scaling for PHP.
Also, we will show you in live how to use corresponding docker commands to resolve such situations.
Let's begin from the review of the scaling schema of PHP Docker high available cluster:
- NFS storage - we'll use it as shared storage for our WordPress. We'll carry out
wp-contentdirectory there, where plugins and static content (i.e images) are stored. Of course, the best way would be to store images in some kind of CDN, but that's totally another story. - HAproxy Load Balancer - will be used for redirecting users to the correspondent container and distributing the load accordingly.
- Code container - we'll keep Wordpress core here and run Nginx with PHP-FPM.
- Cron container - this is a container with your code, but it will be used only for cron tasks execution. I'd like to draw your attention that Wordpress has several options of cron executions.
The first one is the usage of the standard CRON function when the task is executed when the client appeals to the website, and the second option is the usage of system cron.
If we're talking about high load usage of system crons is the better option, as it will reduce the load on the servers.
High Available Cluster Info
Here's what was used in order to get started:
Two servers with Ubuntu 22.04 in Digital Oceanthedockerexperts-wp-01&thedockerexperts-wp-02
Test domainwp.thedockerexperts.com
Installed and configured Rancherrancher.example.com
Private registry
Remote MySQL Server
Docker-ce Installation
Before the start, we need to check the compatibility of Docker versions and your Rancher. When the article was written I used Rancher 2.6 and versions were checked via official rancher site
I installed docker-ce 20.10.x-ce on all hosts:
curl https://releases.rancher.com/install-docker/20.10.sh | sh
NFS server setup
I've chosen thedockerexperts-wp-01 to be the NFS server:
apt-get install nfs-kernel-server
Now we're creating a directory www-data with permissions that will be shared between hosts:
shell
1mkdir -p /srv/nfs-server/wp.thedockerexperts.com/shared/wp-content2chown -R www-data:www-data /srv/nfs-server/wp.thedockerexperts.com/shared/wp-contentTo limit the access from trusted hosts, let's edit file /etc/exports and add this string:
shell
1/srv/nfs-server/wp.thedockerexperts.com/shared 167.99.251.41(rw,sync,no_root_squash,fsid=0) 46.101.159.236(rw,sync,no_root_squash,fsid=0) 127.0.0.1(rw,sync,no_root_squash,fsid=0)You need to change the data according to your IP addresses and dirs.
IMPORTANT! I suggest using an internal network with private IPs for NFS and also covering all of that with a firewall for security reasons.
service nfs-kernel-server restart
Now we're setting up NFS server on all servers:
apt-get install nfs-common
To check let's mount the dir /srv/nfs-server/wp.thedockerexperts.com/shared with previously added content of wp-content:
shell
1mount -t nfs -o proto=tcp,port=2049 :/ /mnt2ls -la /mnt/3total 124drwxr-xr-x 3 root root 4096 Apr 22 19:45 .5drwxr-xr-x 23 root root 4096 Apr 22 19:41 ..6drwxr-xr-x 4 www-data www-data 4096 Apr 22 19:48 wp-contentDocker build Build and publish Docker images
To ease the process our company has to prepare a ready-to-go Docker image with NginX and PHP-FPM, which is available on Docker Hub. We'll use it for building images of cronjob container and container with your code.
Directories structure:
yaml
1├── cron2│ ├── conf3│ │ ├── cron4│ │ │ └── crontab5│ │ └── supervisor6│ │ └── supervisord.conf7│ └── Dockerfile8└── lemp79├── conf10│ └── nginx11│ └── wp.thedockerexperts.com12├── Dockerfile13└── public_html14├── index.php15├── license.txt16├── readme.html17├── wp-activate.php18├── wp-admin19│ ├── about.php20│ ├── admin-ajax.php21│ ├── admin-footer.php22│ ├── admin-functions.php23│ ├── admin-header.php24│ ├── admin.php25│ ├── admin-post.php26│ ├── async-upload.php27│ ├── comment.php28│ ├── credits.php29│ ├── css30│ │ ├── about.css31│ │ ├── about.min.css32│ │ ├── about-rtl.css33│ │ ├── about-rtl.min.css34│ │ ├── admin-menu.css35│ │ ├── admin-menu.min.css36│ │ ├── admin-menu-rtl.css37│ │ ├── admin-menu-rtl.min.css38│ │ ├── code-editor.css39│ │ ├── code-editor.min.css40│ │ ├── code-editor-rtl.css41│ │ ├── code-editor-rtl.min.css42│ │ ├── color-picker.css43│ │ ├── color-picker.min.css44│ │ ├── color-picker-rtl.css45│ │ ├── color-picker-rtl.min.css46│ │ ├── colors47--More--Disabling of standard wp-cron:
To disable standard wp-cron and use system one we need to add the next string to wp-config.php file:
define('DISABLE_WP_CRON', true);
NginX Docker Image
We put code of your app to NginX Docker container with PHP-FPM & excluding wp-content and called it CODEBASE:
Content of lemp7/Dockerfile:
dockerfile
1FROM dockerexperts/nginx-php72COPY conf/nginx/wp.thedockerexperts.com /etc/nginx/sites-enabled/3RUN mkdir -p /var/www/wp.thedockerexperts.com4COPY public_html/ /var/www/wp.thedockerexperts.com/public_html/5RUN chown -R www-data:www-data /var/www/wp.thedockerexperts.com/public_htmlTo configure NginX we use simple config lemp7/conf/nginx/wp.thedockerexperts.com:
shell
1upstream php {2server 127.0.0.1:9000;3}4server {5## Your website name goes here.6server_name wp.thedockerexperts.com;7## Your only path reference.8root /var/www/wp.thedockerexperts.com/public_html;9## This should be in your http block and if it is, it's not needed here.10index index.php;11location = /favicon.ico {12log_not_found off;13access_log off;14}15location = /robots.txt {16allow all;17log_not_found off;18access_log off;19}20location / {21# This is cool because no php is touched for static content.22# include the "?$args" part so non-default permalinks doesn't break when using query string23try_files $uri $uri/ /index.php?$args;24}25location ~ \.php$ {26#NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini27include fastcgi_params;28fastcgi_intercept_errors on;29fastcgi_pass php;30}31location ~* \.(js|css|png|jpg|jpeg|gif|ico)$ {32expires max;33log_not_found off;34}35}Don't forget that you need to change paths in Dockerfile as well as NginX configuration file according to your project's specs.
Docker commands for image building ( you can also read our overview about top docker commands which every expert should know )
shell
1cd lemp72ls -la3total 204drwxr-xr-x 4 root root 4096 Apr 22 19:58 .5drwxr-xr-x 4 root root 4096 Apr 22 19:51 ..6drwxr-xr-x 3 root root 4096 Apr 22 19:51 conf7-rw-r--r-- 1 root root 286 Apr 22 19:58 Dockerfile8drwxr-xr-x 4 root root 4096 Apr 22 19:57 public_html9docker build .10Sending build context to Docker daemon 24.33MB11Successfully built 16c0c7fef405Docker commands for image publishing
shell
1docker tag 16c0c7fef405 /lemp7:wptest2docker push /lemp7:wptestCRON image
CRON container will use files of CODEBASE image, so all PHP modules dependencies are followed and your code is in place.
Content of cron/Dockerfile:
dockerfile
1FROM /lemp7:wptest2COPY conf/supervisor/supervisord.conf /etc/supervisor/supervisord.conf3COPY conf/cron/crontab /tmp/4RUN crontab /tmp/crontabContent of cron/conf/supervisor/supervisord.conf:
shell
1[supervisord]2nodaemon = true3logfile = /var/log/supervisord.log4logfile_maxbytes = 10MB5pidfile = /var/run/supervisord.pid6[program:rsyslog]7command=rsyslogd -n8autostart=true9autorestart=true10stdout_logfile=/dev/stdout11stdout_logfile_maxbytes=012stderr_logfile=/dev/stderr13stderr_logfile_maxbytes=014[program:cron]15command = /usr/sbin/cron -f16autostart = true17autorestart = true18stdout_logfile=/dev/stdout19stdout_logfile_maxbytes=020stderr_logfile=/dev/stderr21stderr_logfile_maxbytes=022[program:cron_log]23command = tail -F /var/log/syslog24stdout_events_enabled=true25stderr_events_enabled=true26stdout_logfile_maxbytes = 10MB27stdout_logfile_backups = 028[eventlistener:stdout]29command = supervisor_stdout30buffer_size = 10031events = PROCESS_LOG32result_handler = supervisor_stdout:event_handlerContent of cron/conf/cron/crontab:
shell
1*/10 * * * * cd /var/www/wp.thedockerexperts.com/public_html; php wp-cron.php &ampgt; /dev/null 2&ampgt;&ampamp;1Docker commands for image building
shell
1cd cron/2docker build .3Successfully built 828412c9b982Docker commands for image publishing
shell
1docker tag 828412c9b982 /lemp7:wpcron2docker push /lemp7:wpcronRancher
Create Base Environment
Click on the button Add environment in envs. management, which can be found by following the link in your Rancher: https://rancher.example.com/settings/env
Let's enter the new environment and start adding hosts.
In your environment, we'll see a notification that before container launch we need to add hosts with compatible Docker versions. You're able to check them by following the link.
Add Hosts
Infrastructure 〉 Add Host
https://www.youtube.com/watch?v=-h_xoPXJwAI
We copy the content of the 5th point and add it to the console of our servers. When the installation process of the rancher-agent is over, you'll have to see similar data, as it's shown on the below screenshot:
Connect Private Registry
Infrastructure 〉 Registry 〉 Add registry
Stacks and services
Now when everything is ready, we can start adding services to Rancher.
NFS Rancher
Go to environment 〉 Catalog - let's use the search to find NFS.
Click on `View Details` and fill in the fields:
NFS Server:<nfs-server-IP>
Export Base Directory:/
On Remove:Retain
We'll see the next picture after the launch:
Now we're adding volume. Let's proceed in Infrastructure 〉Storage 〉Add Volume:
Name: wp-contentMyScalableWP stack
We need to add stack, where we'll launch our services. Keep in mind that wp-content needs to be connected from NFS.
Service configuration
Volumes configuration
MySQL External Service
Let's add MySQL as an external service for convenience:
Load balancing
We need to schedule Load Balancer to 2 hosts, where we add balancer=true labels. This menu is available while editing hosts.
Load Balancer service
DNS records
nslookup wp.thedockerexperts.com
Server: 67.207.67.3
Address: 67.207.67.3#53
Non-authoritative answer:
Name: wp.thedockerexperts.com
Address: 167.99.251.41
Name: wp.thedockerexperts.com
Address: 46.101.159.236
CRON service
To deploy cron container we need to add new service:
Volumes
Testing
Let's try to install W3 Total Cache WP plugin:
We see that plugin was installed in shared dir:
shell
1root@thedockerexperts-wp-01:~/mywp/lemp7# ls -la /srv/nfs-server/wp.thedockerexperts.com/shared/wp-content/plugins/2total 523drwxr-xr-x 4 www-data www-data 4096 Apr 22 20:40 .4drwxr-xr-x 5 www-data www-data 4096 Apr 22 20:40 ..5drwxr-xr-x 4 www-data www-data 4096 Apr 3 20:19 akismet6-rw-r--r-- 1 www-data www-data 2230 Mar 17 20:27 hello.php7-rw-r--r-- 1 www-data www-data 28 Jun 5 2014 index.php8drwxr-xr-x 9 www-data www-data 28672 Apr 22 20:40 w3-total-cacheLet's try to add one more host.
Don't forget that we need to update /etc/exports file on the NFS server and add new host.
Conclusion
In this blog post, I tried to show you how you can use Docker for scaling of your PHP project in high available cluster. WordPress was used just for example's clear visibility.
However something is not considered in this article and you should always remember that when you're ready to scale your project.
- NFS is a bottleneck in this case. And for HA infrastructure it doesn't fit really well, because if NFS server goes down your whole project will be down. Also, the speed of NFS doesn't always fit for production mode - consider CDN to serve static content. Alternative for NFS is GLusterFS, it's more appropriate for HA but the data transfer speed won't be really high if we're comparing it with regular local disks.
- Take into consideration where your sessions are stored. In WordPress's case, it's not actual, but with other PHP projects, you'll need to put them into shared storage. In my opinion, the best option here would be Redis or a database.
- Don't forget about the size of your docker images. Always try to cut your image, so the time spent for its build and deploy is reduced.
I'll be really glad to see any kind of comments, questions or suggestions!
