says

PHP is one of the most popular programming languages on the web. It is developed for many widely used content management systems, such as WordPress and Drupal, and provides the core code for modern server-side frameworks such as Laravel and Symfony.

Despite its popularity, PHP is notoriously slow and difficult to maintain. These bugs have become less serious in recent years, but high-performance PHP applications still require two features: OPcache and the PHP FastCGI process manager (PHP-FPM).

In this article, you’ll learn how to use custom OPcache and phP-FPM configurations to deploy a PHP application on Kubernetes to improve its performance. You will use Rancher to deploy a PHP application that uses custom environment variables to dynamically configure OPcache and PHP-FPM. We’ll show you how to build phP-FPM configuration options in your Docker image and adjust them in the container using environment variables.

Performance in PHP

First, it will be helpful to know how network requests are handled in PHP applications for the rest of this article.

PHP typically runs alongside a Web server, processing requests and sending them to a PHP application. You can run your applications using php-fpm or mod_PHP, but in this article we’ll use php-FPM because of its performance advantages and because NGINX is the web server most commonly used with php-FPM.

OPcache introduction

OPcache speeds up PHP applications by storing scripts in memory the first time they are called. Further, subsequent requests are loaded from memory instead of the file system, which can increase your speed by 74%.

OPcache provides Settings that you can adjust to improve the performance and reliability of your application. In this tutorial, you’ll learn how to set up a PHP Docker image that adjusts the OPcache memory limit, the number of cached files, and the frequency of revalidating the cache.

PHP – FPM is introduced

Php-fpm (FastCGI process manager) starts one or more processes to run your PHP application. Unlike mod_PHP, which bundles PHP as an Apache module, phP-FPM gives you precise control over the number of processes running on your server (or container) and how they should be started and stopped.

Finding an ideal PHP-FPM configuration is highly dependent on your application, the number of requests it serves, and the memory and CPU limits in the container. I recommend reading Hayden James’ article on the subject (link at the end) and testing several different configurations in a load test environment.

Deploy a PHP application on K8S

preparation

Before you go through this tutorial, you need to be prepared:

  • Install the Docker on your local machine

  • Docker Hub or other Docker image repository account

  • Rancher Deployment (follow Quick Start if you haven’t already)

  • Kubernetes cluster managed by Rancher

All of the code used in this tutorial is available on Github, or you can follow these steps to build the application from scratch.

PHP applications

The application you will be using is a PHP file that displays the current date. Create a new file and name it index.php:

<? phpecho 'The current date is ' . date('F jS, Y');
Copy the code

Create a Dockerfile and configuration file

You can get a lot of PHP Docker images on Docker Hub, but none of them offer an easy way to change OPcache or PHP-FPM configurations using environment variables. The advantage of using environment variables is that you don’t need to rebuild the PHP image every time you adjust phP-FPM or OPcache Settings. This allows you to quickly adjust your application to improve performance.

First, create a new file called opcache.ini. You will copy this file into the PHP image and add default values for each environment variable in the Dockerfile.

# See https://www.php.net/manual/en/opcache.configuration.php for all available configuration options.
[opcache]
opcache.enable=${PHP_OPCACHE_ENABLE}
opcache.memory_consumption=${PHP_OPCACHE_MEMORY_CONSUMPTION}
opcache.max_accelerated_files=${PHP_OPCACHE_MAX_ACCELERATED_FILES}
opcache.revalidate_freq=${PHP_OPCACHE_REVALIDATE_FREQUENCY}
opcache.validate_timestamps=${PHP_OPCACHE_VALIDATE_TIMESTAMPS}
Copy the code

Next, create another new file named www.conf. This file will store phP-FPM configuration options, which you can update with environment variables:

; See https://www.php.net/manual/en/install.fpm.configuration.php forall available configuration options ; Required user, group, and port options [WWW] user = www-data group = www-data listen = 127.0.0.1:9000; Process manager options pm =${PHP_FPM_PM}
pm.max_children = ${PHP_FPM_MAX_CHILDREN}
pm.start_servers = ${PHP_FPM_START_SERVERS}
pm.min_spare_servers = ${PHP_FPM_MIN_SPARE_SERVERS}
pm.max_spare_servers = ${PHP_FPM_MAX_SPARE_SERVERS}
pm.max_requests = ${PHP_FPM_MAX_REQUESTS}
Copy the code

You will need to copy these files into your Docker image and set the default environment variable values, so create a new Dockerfile in your project’s root directory. Add the following steps:

The FROM PHP: 7.4 - FPM# OPcache defaults
ENV PHP_OPCACHE_ENABLE="1"
ENV PHP_OPCACHE_MEMORY_CONSUMPTION="128"
ENV PHP_OPCACHE_MAX_ACCELERATED_FILES="10000"
ENV PHP_OPCACHE_REVALIDATE_FREQUENCY="0"
ENV PHP_OPCACHE_VALIDATE_TIMESTAMPS="0"

# Install opcache and add the configuration file
RUN docker-php-ext-install opcache
ADD opcache.ini "$PHP_INI_DIR/conf.d/opcache.ini"

# PHP-FPM defaults
ENV PHP_FPM_PM="dynamic"
ENV PHP_FPM_MAX_CHILDREN="5"
ENV PHP_FPM_START_SERVERS="2"
ENV PHP_FPM_MIN_SPARE_SERVERS="1"
ENV PHP_FPM_MAX_SPARE_SERVERS="2"
ENV PHP_FPM_MAX_REQUESTS="1000"

# Copy the PHP-FPM configuration file
COPY ./www.conf /usr/local/etc/php-fpm.d/www.conf

# Copy the PHP application file
COPY ./index.php /var/www/public/index.php
RUN chown -R www-data:www-data /var/www/public
Copy the code

Dockerfile copies the OPCache configuration, phP-fPM configuration, and PHP application files to the image and ensures that the var/ WWW /public directory containing the PHP code is owned by the php-fpm user. The ENV declaration sets the default PHP_OPCACHE_… And PHP_FPM_… Environment variables, but you can override them at any time while running the image. This will make performance tuning in real deployments much easier.

Build and push to DockerHub

At this point, you have a single-file PHP application, an OPcache configuration file, a PHP-fPM configuration file, and a Dockerfile in your project. You can now build your Docker image:

docker build -t <YOUR_USERNAME>/php-fpm .
Copy the code

Next, push the image to the Docker Hub:

docker push <YOUR_USERNAME>/php-fpm
Copy the code

Deploy a PHP-FPM workload

Now that your custom PHP-FPM image is available on Docker Hub, you can deploy it on a Kubernetes cluster as part of your workload. Using the Rancher UI, create a new Deployment, name it php-fpm and use

/php-fpm as the Docker image. You can modify any PHP_OPCACHE used in the Dockerfile above. And PHP_FPM _… Environment variables.

Before setting up your Nginx workload to service phP-FPM Deployment, check that your PHP-FPM and OPcache Settings have been correctly added to the container. In the Rancher UI, click the three dots next to PHP Deployment and then click “Execute Shell” :

To check whether the OPcache module is enabled, type php-fpm -i. This prints out the entire php.ini configuration. Take a look at the section on OPcache and you should see something like the following (any values that change are reflected) :

. opcache.blacklist_filename => no value => no value opcache.consistency_checks => 0 => 0 opcache.dups_fix => Off => Off opcache.enable => On => On opcache.enable_cli => Off => Off opcache.enable_file_override => Off => Off opcache.error_log  => no value => no value opcache.file_cache => no value => no value opcache.file_cache_consistency_checks => 1 => 1 opcache.file_cache_only => 0 => 0 opcache.file_update_protection => 2 => 2 opcache.force_restart_timeout => 180 => 180 opcache.huge_code_pages => Off => Off opcache.interned_strings_buffer => 8 => 8 opcache.lockfile_path => /tmp => /tmp opcache.log_verbosity_level => 1 => 1 opcache.max_accelerated_files => 10000 => 10000 opcache.max_file_size => 0 => 0 opcache.max_wasted_percentage => 5 => 5 opcache.memory_consumption => 256 => 256 opcache.opt_debug_level => 0 => 0 opcache.optimization_level => 0x7FFEBFFF => 0x7FFEBFFF opcache.preferred_memory_model => no value => no value opcache.preload => no value => no value opcache.preload_user => no value => no value opcache.protect_memory => 0 => 0 opcache.restrict_api => no value => no value opcache.revalidate_freq => 0 => 0 opcache.revalidate_path => Off => Off opcache.save_comments => 1 => 1 opcache.use_cwd => On => On opcache.validate_permission => Off => Off opcache.validate_root => Off => Off opcache.validate_timestamps => Off => Off ...Copy the code

Whenever you redeploy your php-FPM workload, php-FPM restarts and resets OPcache, so you usually don’t have to worry about resetting OPcache when you run php-FPM on Kubernetes. If you do flush the cache manually, the easiest way is to redeploy the workload from the Rancher UI.

To ensure that the php-FPM configuration changes take effect, type php-Fpm-tt in your shell. You should see a list of all phP-FPM options, including the section where the process manager is updated (which was added to the www.conf file and set using environment variables) :

NOTICE:  pm = dynamic
NOTICE:  pm.max_children = 10
NOTICE:  pm.start_servers = 2
NOTICE:  pm.min_spare_servers = 1
NOTICE:  pm.max_spare_servers = 2
NOTICE:  pm.process_idle_timeout = 10
NOTICE:  pm.max_requests = 1000
Copy the code

Deploy the Nginx workload

Now, you have a PHP-FPM workload, but no Web server can access it. You can use a lot of NGINX Docker mirror to provide service for your PHP applications, but I usually use the NGINX mirror (www.shiphp.com/blog/2018/n)… , which allows you to use an image for any number of PHP-FPM workloads by using environment variables.

Create a new workload in the Rancher UI on the same cluster as the PHP-FPM workload. Name it nginx, use Docker image shiphp/nginx-env, map port 80 on container to open port on cluster, and add environment variable NGINX_HOST = php-fpm:

If you name your php-FPM workload something other than php-fpm, or if you want to service a second workload, you can connect to it using the NGINX_HOST environment variable, which also allows you to run multiple phP-FPM and Nginx workloads on the same cluster. This also allows you to run multiple PHP-FPM and Nginx workloads on the same cluster.

Once your Nginx workload is available, click on the port link where it is located to open the Web application. You should see the current date your PHP script generated.

conclusion

Now that you have your PHP-FPM workload deployed to the Kubernetes cluster, you can begin the real performance tuning work. Fortunately, updating phP-FPM and OPcache Settings is now as simple as changing environment variables and redeploying the Workload. This will allow you to try out new Settings and get feedback faster than if you were building a new image.

Getting the best performance out of Web applications is an iterative process, but hopefully the Kubernetes deployments in this tutorial will help you build higher-performing PHP applications.

Reference links:

PHP performance improvements: gbksoft.com/blog/php-5-…

PHP – FPM performance advantages: www.cloudways.com/blog/php-fp…

NGINX nginx.org/en/

OPcache configuration: www.php.net/manual/en/o…

Improve performance and reliability of your OPcache application: tideways.com/profiler/bl…

Haydenjames. IO /php-fpm-tun…

PHP – FPM configuration testing: speakerdeck.com/erictendian…