PHP Application with multiple containers in Kubernetes

Let’s demonstrate how to deploy a PHP application on Kubernetes. In this blog, I created a Deployment of PHP-FPM and Nginx using Pod Multiple Containers, ConfigMaps and Volumes.

Before starting, I like to share the usual way to deploy a PHP application without Docker and Kubernetes. First, we need to install PHP-FPM and Nginx on a server, then configure the Nginx for the PHP source code and PHP-FPM.

For example:

server {
   listen 80 default_server;
   client_max_body_size 500M;


   server_name localhost;
   root /app;

   error_log /var/log/nginx/error.log;
   access_log /var/log/nginx/access.log;

   index index.html index.php;

   location / {
       try_files $uri $uri /index.php$is_args$args;
   }

   location ~ ^/.+\.php(/|$) {
       fastcgi_split_path_info ^(.+?\.php)(/.*)$;
       if (!-f $document_root$fastcgi_script_name) {
           return 404;
       }
       include fastcgi_params;
       fastcgi_pass localhost:9000;
       fastcgi_index index.php;
       fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
       fastcgi_param DOCUMENT_ROOT $realpath_root;
       fastcgi_param REALPATHTEST $realpath_root;
       internal;
   }
}

In short, we just have 2 services running on 1 server, especially, both of these services must read the PHP file of the source code. So how do we do the same with Docker and Kubernetes? That’s why we need to use multiple containers in a pod and I will show you how to do it in this blog.

By the way, because this approach is based on my personal experiences, it is probably not the best way to deploy a PHP application in practice; however I have applied this way for a long time and there hasn’t been any problem, and it also helps me manage and scale these PHP applications easily. If you have any other ideas, your shares are always welcome.

Note:
PHP-FPM version 8.1.8
Nginx version 1.23.3
Kubernetes version 1.24.2

Prerequisites:
Basic knowledge of Docker and Kubernetes.
A Kubernetes cluster running. You can set this up with any kind suitable for you. For how to do this, you can follow here.
A registry server. You can use Docker registry or Harbor or the other way around. Here I use Harbor.

Step 1 - Create Dockerfile PHP-FPM, Nginx
In this step, first, we need to create an example file as index.php for demo purposes only.

File Index.php

<?php
echo phpinfo();

Then we will create the Dockerfile using images php:8.1.8-fpm-alpine, nginx:1.23.3.

File Dockerfile.php

FROM php:8.1.8-fpm-alpine
WORKDIR /app
COPY index.php .
CMD ["php-fpm"]

File Dockerfile.nginx

FROM nginx:1.23.3
WORKDIR /app
COPY index.php .

Step 2 - Build the images and push the images to registry
We have to prepare for the registry server and login to the registry server. I will have another blog about this but now I’m skipping it. I have set it up before. So I will build and push the images as below steps:

For Nginx:

docker build -t your.resgitry.com/kubernetes-in-practice/multiple-container-nginx:v1 -f Dockerfile.nginx .

And PHP:

docker build -t your.resgitry.com/kubernetes-in-practice/multiple-container-php:v1 -f Dockerfile.php .

Then we can push it to the registry server.

For Nginx:

docker push your.resgitry.com/kubernetes-in-practice/multiple-container-nginx:v1

And PHP:

docker push your.resgitry.com/kubernetes-in-practice/multiple-container-php:v1

After all, here is the result on Harbor:


Step 3 - Deploy the new PHP application
After pushing these images we need to deploy them for the registry server. Then we can start our applications.
Firstly, we use ConfigMaps to configure the Nginx server.
Creating configmap.yaml with the data below:

apiVersion: v1
kind: ConfigMap
metadata:
 creationTimestamp: null
 name: nginx-config
data:
 config : |
   server {
       listen 80 default_server;
       client_max_body_size 500M;
       server_name localhost;
       root /app;
       error_log /var/log/nginx/error.log;
       access_log /var/log/nginx/access.log;
       index index.html index.php;
       location / {
           try_files $uri $uri /index.php$is_args$args;
       }
       location ~ ^/.+\.php(/|$) {
           fastcgi_split_path_info ^(.+?\.php)(/.*)$;
           if (!-f $document_root$fastcgi_script_name) {
               return 404;
           }
           include fastcgi_params;
           fastcgi_pass localhost:9000;
           fastcgi_index index.php;
           fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
           fastcgi_param DOCUMENT_ROOT $realpath_root;
           fastcgi_param REALPATHTEST $realpath_root;
           internal;
       }
   }

Just in case, you’re wondering how we can use the localhost:9000 here. The reason is we will create a pod with multiple containers so they can share the network resource together within the same pod. You can refer to the document about multiple containers in a Pod here.

Save the file and create it on Kubernetes:

kubectl create -f configmap.yaml

We will see the following output:

configmap/nginx-config created

We also can verify it by command:

kubectl describe cm nginx-config

Secondly, we create a deployment with multiple containers and use the ConfigMap which has just been created to overwrite the file default.conf in the Nginx container.

apiVersion: apps/v1
kind: Deployment
metadata:
 labels:
   app: php-multiple-container
 name: php-multiple-container
spec:
 replicas: 1
 selector:
   matchLabels:
     app: php-multiple-container
 template:
   metadata:
     labels:
       app: php-multiple-container
   spec:
     containers:
     - image: your.registry.com/kubernetes-in-practice/multiple-container-nginx:v1
       name: nginx
       volumeMounts:
       - mountPath: /etc/nginx/conf.d
         name: nginx-config
     - image: your.registry.com/kubernetes-in-practice/multiple-container-php:v1
       name: php
     volumes:
     - name: nginx-config
       configMap:
         name: nginx-config
         items:
         - key: config
           path: default.conf

Please remember to update your registry server. After that, we can create the deployment.

kubectl create -f deployment.yaml

The output will be:

deployment.apps/php-multiple-container created

List the deployments and pods which are running:

kubectl get deploy,pod

Everything is good:

NAME                                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/php-multiple-container   1/1     1            1           21s

NAME                                          READY   STATUS    RESTARTS   AGE
pod/php-multiple-container-5655d87ff5-wqcqk   2/2     Running   0          21s

However, how can we ensure if it is working or not? It is right time for using Service, we can use NodePort with this command:

kubectl expose deployment php-multiple-container --type NodePort --port 80

The following output:

service/php-multiple-container exposed

List Service:

kubectl get svc

The following output:

kubernetes               ClusterIP   10.96.0.1     <none>        443/TCP        224d
php-multiple-container   NodePort    10.103.3.50   <none>        80:30025/TCP   6s

Finally, you can access to http://your-host:30025 and the page phpinfo will appear.


Hoping you have known how to deploy PHP applications to Kubernetes and how to implement an application with multiple containers. This not only makes it easier and more efficient to manage multiple PHP applications but also helps you to scale the PHP applications flexibly. Plus, if you’re studying for CKA and CKAD, you need to know how to work with multiple containers because this is a scenario in the exam.

Thank you for sharing your knowledge and expertise with the community.

1 Like

Great!!! A straightforward explanation, thanks.

1 Like