How to deploy Magento 2 on Kubernetes 

In this article, we’ll learn how to deploy Magento 2 on Kubernetes

We assume that you have already been familiar with operating Magento 2, containers (Docker), and basic concepts of Kubernetes.

A running Kubernetes cluster with at least 4 CPUs and 8 GB of RAM available is needed. And Minikube is for testing the cluster locally.

We’ll also need an ingress controller (NGINX) to be able to access Magento once installed.

If you decide to install Minikube, you can start a cluster with required capabilities by running make minikube in the companion repository.

We’ll be using the following tools:

  • kubectl with the correct context configured
  • A standalone version of kustomize
  • (optional but recommended) make

Once the cluster and the tools are in place, we can start deploying Magento..

We will guide you through step by step.

Step 1: Create a minimal Magento 2 deployment

Magento

Naturally, we’ll start with a container running Magento itself.

First, we need to go through some of the aspects one needs to consider when running any PHP web application on Kubernetes.

PHP web application pod patterns

There are different patterns for deploying a PHP web application on Kubernetes – from single-process, single-container through multi-process containers to multiple single-process ones.

The simplest configuration is to run Apache 2 and mod_php in a single process in a single container; this configuration is frequently used in tutorials. Even though an all-in-one container is the simplest to set up and manage, you should still think about using NGINX as a dedicated pod or a caching reverse-proxy to serve static content.

NGINX and PHP-FPM in a single container

If you decide to run NGINX, you’ll need PHP-FPM with it. You’ll also need either a custom script or a process manager (e.g., supervisor) to run them both in a single container. Having more than one process in a container should be avoided as a general rule, though it is acceptable to do so in some circumstances. It might be worthwhile to take into account, though, as it does offer the advantages of better performance and having all of the code in a single container.

Multiple processes are acceptable, but to get the most out of Docker, avoid having one container handle multiple facets of your application as a whole. 

Single Pod running two containers

This configuration uses the network rather than a socket for communication between NGINX and PHP-FPM, which are both running in independent containers. We can now assign specific readiness and liveliness probes to each container and better manage resource allocation without the need for a supervisor.

However, there is one restriction: we must ensure that NGINX can access static and media files. There are two ways to do it: either by building a custom NGINX image with the project files inside, or by using a volume to share project files between the NGINX and PHP containers. In order to accomplish the latter, a volume that is shared by all of the containers in the pod must be created (emptyDir would work just fine here), and the files from the PHP container must be copied to the volume during pod initialization (i.e., in an init container).

To avoid having to version two images and to ensure that the deployed versions are always in sync, we’ll use the second method in this article. One of the sections below provides more information on implementation specifics.

Web server and PHP in separate Pods

This pattern is quite similar to the previous one, except it allows us to scale PHP and NGINX Pod independently. We cannot use an emptyDir volume here to share files between the pods and need to configure proper persistent volumes for static assets and media files.

Which is best?

Single-process Apache+PHP containers have the advantage of being simpler to manage, but NGINX is known for serving static content with superior speed, and putting it and PHP-FPM on separate pods enables you to scale them independently. Even so, this scalability comes at the expense of greater complexity, so it’s always best to conduct your own benchmarks, taking into account things like anticipated traffic patterns, CDN use, and caching.

There isn’t a magic solution, so it’s up to you to decide which pattern to apply to a given project.

Magento Docker image

As discussed above, we’ll use a Docker image based on php:7.2-fpm and a plain NGINX:mainline image.

Configuration from environment

When implementing Kubernetes applications, it’s usually best to configure each program by setting environment variables on its container.

ConfigMaps can be mounted to containers as regular configuration files, but this isn’t the best solution. Applications use various configuration formats, and values frequently need to be consistent among several applications. This method of managing configuration quickly adds unnecessary complexity.

On the other hand, everything is defined once for environment variables as key-value pairs. Then, just make a reference to the key (variable name) to pass those values. You will have a single source of truth for all required configuration values in this manner.

Environment variables in Magento 2

 Magento 2 have ability to pick up settings from the environment – setting an environment variable like 

<SCOPE>__<SYSTEM__VARIABLE__NAME> has the same effect as writing the setting in app/etc/config.php.

Unfortunately, this feature cannot be used to control environment-specific settings like database credentials. There are a few ways to work around it, though:

  • Mounting app/etc/env.php from a ConfigMap is not an ideal solution, as Magento checks for write access to the file at various points. It could be copied from a ConfigMap during Pod initialization to allow write access. (https://github.com/magento/magento2/issues/4955)
  • Use bin/magento to pick configuration up from environment variables, and pass it to Magento during Pod initialization. It’s mostly the same as configuring a Magento instance via CLI, but automated. It takes quite some time, though, to save the configuration, which considerably prolongs the time each Magento Pod takes to start.
  • Modify app/etc/env.php and include it in the container image. Since env.php is a regular PHP file that must return an array with the configuration, PHP’s built-in getenv() function is perfect for taking values from the environment during execution, e.g., ‘dbname’ => getenv(‘DB_NAME’). This method ensures Magento can write to the env.php file while adding no extra time during Pod initialization.

Managing logs

One more issue to consider when deploying Magento 2 on Kubernetes is to make sure all relevant logs survive container restarts and are easily accessible.

Using a PersistentVolume for the var/log and var/reports directories would be the most straightforward fix. A volume solves the issue of log persistence but may cause performance issues with many Magento instances writing to the same files. Additionally, it takes a while for the logs themselves to be navigable.

We’ll employ the sidecar pattern to meet both requirements. This pattern uses a separate container to read log files as they expand and output their contents to stdout for a different tool (Elastic stack, Fluentd) to store and process.

Tip: In this example, we’ll use one container per log file running tail -f to put logs to stdout. While this works quite well for a vanilla Magento deployment, it doesn’t scale very well with more files to process.

A better solution would be to leverage Magento’s PSR-3 compatibility and configure all relevant log handlers to log directly to stdout/stderr. Doing so would satisfy Factor XI of the Twelve-factor App methodology and make sidecar containers obsolete.

Cron

Several of Magento’s features rely on cron jobs.

In a typical deployment scenario, you would designate one of the hosts to manage crontab-based tasks and assign them to run on that host. A similar setup would not function with Kubernetes, though, as no single Magento instance (container) can be relied upon to run continuously. Because of this, we’ll employ a Kubernetes CronJob that executes bin/magento cron:run once every minute.

In this way, we free Kubernetes from having to worry about maintaining a running instance of Magento by handing off the task of running cron to it. Kubernetes then launches a new temporary container and executes the given commands until they are finished.

Ensure that each Magento CronJob is set up to run as a single process before running them as Kubernetes CronJobs. 

 This can easily be done by setting the following environment variables: Otherwise, the cron container may terminate before completing running all scheduled jobs.

Additional Jobs

Another Kubernetes Object type we’ll make use of in this project is a Job.

A Job runs one or more Pods responsible for completing a task. We’ll be using Jobs to run tasks that are required when deploying Magento, but wouldn’t be suitable to put in the init containers for Magento Pods themselves:

  • magento-unpack is responsible for unpacking all static assets (baked into the container image during build) into a volume shared between PHP and NGINX. Since the assets are precisely the same for each instance of a given version, we need to do this only once per version deployment.
  • magento-install automates application installation: it installs database schema, generates performance fixtures that we use as sample data for demonstration, and ensures all indexes are in “On Schedule” update mode. In a real-life scenario, you’d probably run bin/magento setup:upgrade here instead to update schema on each new deployment.

Tip: Bear in mind that since Job’s pod template field is immutable, it’s impossible to update the Jobs with each new release. Instead, you need to make sure to delete the old ones and create new ones for each revision deployed.

Database

We’ll simply use a StatefulSet running Percona 5.7 with the data stored in a PersistentVolume.

For small local/development clusters, a simple StatefulSet works well, but for larger deployments, you might want to set up an Xtradb cluster (using, for example, Percona Kubernetes Operator). Running appropriate benchmarks will help you determine whether the benefits of such a solution outweigh the additional resources and complexity it requires.

Ingress

At this point, all that we’re missing to have a working Magento 2 instance on Kubernetes is a way to access the frontend.

We could simply expose the magento-web Service by making its type either NodePort to expose it on a specific port or LoadBalancer to expose it via an external load balancer. In this case, however, we’ll use an Ingress Controller – this way, we’ll get TLS termination out-of-the-box, along with the possibility to manage the TLS certificates in a declarative manner (e.g., using cert-manager). We could even expose additional services with routing based on paths or (sub-)domains, should we decide to do so.

Assuming that NGINX Ingress Controller is already installed, all we need to do here is to create an Ingress definition that will proxy all traffic to the magento-web Service on the HTTP port.

Putting it all together

Run make step-1 in the companion repository to deploy the stack discussed so far. Make will download any dependencies needed to run this step and deploy everything to your Kubernetes cluster.

By now, we ought to have a functioning, if basic, Magento deployment on Kubernetes. We still lack some crucial components, namely a reliable search engine and cache. Let’s carry on and add them!

Step 2: Elasticsearch

Next, we need to make an online store that we work on can be seen when customers search on a search bar. With Magento 2 supporting Elasticsearch, all we need is to deploy Elasticsearch itself to ensure the customers can easily find what they’re looking for. Let’s get to it.

We’ll use Elastic Cloud on Kubernetes Operator to simplify the process.

Elasticsearch Cluster

After installing the Operator, we need to tell it about the desired Elasticsearch cluster architecture and make a connection between  Magento and that cluster.

Due to constrained resources (Minikube on a laptop), we’ll go for a simple, single-node Elasticsearch 6.x cluster with somewhat limited resources. Since it is not exposed to any public networks, we can disable authentication and TLS for simplicity.

For a larger project with higher traffic you may develop in the future, you might consider setting up an Elastic cluster with more nodes and more resources each. You should also run benchmarks specific to your project, to make sure you have the configuration that works for you.

To deploy and configure Elastic Stack, run make step-2.

Tip: Similarly, Elastic Cloud on Kubernetes Operator can be used to deploy another Elasticsearch cluster along with Kibana to manage logs.

Magento configuration

Magento must now be directed to the newly established Elasticsearch instance. This is simple to accomplish by extending aux.env configuration with Kustomize, Kustomize  will take care of combining configuration files and sending them to Magento as environment variables.

Step 3: Redis and auto-scaling

Now that Elasticsearch has been set up in the previous section, we have completed the functionality-related steps. However, you may have noticed that Magento’s performance in this setup isn’t exactly stellar. Don’t worry; we haven’t utilized nearly any caching yet!

Now let’s get it to work quickly using Redis and auto-scaling.

Redis

Redis serves two crucial functions in a successful Magento 2 deployment:

  • For multiple application instances to track session data between requests, fast session storage is necessary.
  • Storage for the internal Magento cache, including configuration, design, and HTML snippets

Again, we’ll run a single Redis instance with separate databases for sessions and cache using a straightforward StatefulSet. We won’t attach any PersistentVolumes because we don’t need to.

The final step is to instruct Magento to use the recently deployed Redis instance, just as we did with Elasticsearch. Like before, we’ll modify aux.env with a few keys and let Kustomize handle combining the parts:

Horizontal Pod Autoscalers

Redis has been installed,  allowing us to run multiple instances of Magento that share session information and cache. Let’s create Horizontal Pod Autoscalers and let Kubernetes figure out the optimum number at any given time.

We’ll make a fresh HorizontalPodAutoscaler to accomplish this. It keeps track of the resources being used by the Pods in  scaleTargetRef, and starts new ones when targetCPUUtilizationPercentage goes above the defined threshold until it reaches maxReplicas.

Tip: In this article, we’ve purposely assigned limited resources to Magento Pods to make it easier to show auto-scaling in action. When deploying Magento 2 on Kubernetes in a real-life scenario, you should make sure to tune both PHP settings and Pod resource constraints, as well as scaling rules in the HorizontalPodAutoscaler configuration.

Like before, to deploy Redis and auto-scalers, simply run make step-3.

Step 4: Varnish

A caching reverse-proxy is the final component needed to relieve some of Magento’s load. Naturally, Varnish will be used since it is pre-supported.

We’ll begin by creating a Varnish Deployment, just as we did in the previous steps. Two notable things here are that we expose two ports and run a custom command to start the container, first starting varnish in daemon mode, and then running varnishncsa.

By opening two ports, we can set up basic access rules in Varnish VCL, allowing Magento to clear its cache using one while opening the other to the public without risk.

Next, we need to tell Magento how to connect to Varnish, by extending aux.env configuration file as before:

Finally, we want the Ingress to send Varnish all incoming requests.  Doing so requires changing the destination Sevice specified in the Ingress definition from before. By using Kustomize’s patchesJson6902, updating the Ingress definition is simple.

Tip: While Varnish excels at taking some of the load off the other components and improving the performance of rarely changing pages, it does not improve the performance of the interactive elements such as shopping cart, checkout, or customer area.

To deploy and configure Varnish, run  make step-4.

You now have a comprehensive overview of all the prerequisites for running Magento 2 on Kubernetes, with Magento deployment set up using environment variables, cronjobs, Elasticsearch, Redis, autoscaling, and Varnish.

Since Kustomize manages all manifests and configuration files, they can easily be customized to meet the requirements of any specific project.

Although we wouldn’t advise using it in production as-is, it should serve as a good starting point for developing a configuration that is prepared for production and is unique to your project.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top