In my previous post, I explained how to setup a simple nginx instance that could be used to sync to a private Git repo. The only drawback is that this setup will only serve static pages. What if you wanted to be able to run a server with dynamic code like PHP? I’m glad you asked! In this post, we’ll update our config to include a php-fpm instance to allow us to serve PHP pages.
I have planned these articles out so that they build on each other. With that in mind, I’m assuming you have followed my articles to date and therefore we’ll be simply extended the current deployment.
If you’re impatient like me, just scroll to the bottom and download the full files.
Setting Up The PHP-FPM Instance
First we need to get our PHP-FPM yaml setup. By default, php-fpm runs on port 9000. This means we need a service definition to expose this to the cluster. This will also need access to the git repo we created so we’ll add in the git container spec. Instead of running the nginx image, we’ll run the php-fpm image. In order to make life easy on ourselves, I’m going to use the webserver.yaml from my previous post as a template. I’m going to make the following changes to it:
- Replace any reference of “webserver” with “phpfpm”.
- Change the following in the service definition
- change the port name from http to phpfpm
- change the port number from 80 to 9000
- Remove the ConfigMap
- Remove the definition of it from the top of the file
- Remove the references to it in the spec volumes and the container volumeMounts
- Change the image of the second container from nginx:latest to php:fpm
- Change the containerPort from 80 to 9000
If we’ve done this all correctly, we should have a yaml that looks similar to the below:
apiVersion: v1
kind: Service
metadata:
name: phpfpm
labels:
tier: backend
spec:
selector:
app: phpfpm
tier: backend
ports:
- name: phpfpm
port: 9000
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: phpfpm
labels:
tier: backend
spec:
replicas: 1
selector:
matchLabels:
app: phpfpm
tier: backend
template:
metadata:
labels:
app: phpfpm
tier: backend
spec:
securityContext:
fsGroup: 65533 # to make SSH key readable
volumes:
- name: dir
emptyDir: {}
- name: git-secret
secret:
secretName: github-creds
defaultMode: 288
containers:
- env:
- name: GIT_SYNC_REPO
value: [email protected]:<some user>/mysamplerepo.git
- name: GIT_SYNC_BRANCH
value: master
- name: GIT_SYNC_SSH
value: "true"
- name: GIT_SYNC_PERMISSIONS
value: "0777"
- name: GIT_SYNC_DEST
value: www
- name: GIT_SYNC_ROOT
value: /git
name: git-sync
image: k8s.gcr.io/git-sync:v3.1.1
securityContext:
runAsUser: 65533 # git-sync user
volumeMounts:
- name: git-secret
mountPath: /etc/git-secret
- name: dir
mountPath: /git
- name: phpfpm
image: php:fpm
ports:
- containerPort: 9000
volumeMounts:
- name: dir
mountPath: /usr/share/nginx
We can now save this yaml and apply it to our cluster:
# kubectl apply -f phpfpm.yaml
service/phpfpm unchanged
deployment.apps/phpfpm configured
Assuming all went well, we should now have our webserver and phpfpm containers up and running:
# kubectl get pod
NAME READY STATUS RESTARTS AGE
phpfpm-b46969c5f-zzh6d 2/2 Running 0 103s
webserver-8fb84dc86-7xw4w 2/2 Running 0 10s
That’s just lovely but what next?
Configuring Nginx for PHP
At this point, we basically have two unassociated containers that are living independently in the same cluster. The only common bond is that they have the same set of files synched from the Git Repo. Next, we need to tell nginx to handle PHP requests and where to send them. This will require us to update our Nginx configMap. We do this by adding a location statement to handle php files like so:
location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass phpfpm:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
There’s lots going on here in this file but some important items to note. Nginx acts like a reverse proxy when handling PHP files. It simply takes the request and sends to php-fpm. The php-fpm service finds the request file locally, executes PHP on it, and sends the resulting processed output from PHP back to Nginx. Here is the full updated configMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: webserver-config
labels:
tier: backend
data:
config :
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/www/html;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/www/html;
}
location ~ .php$ {
root /usr/share/nginx/www/html;
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass phpfpm:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}
Let’s apply this to our cluster:
# kubectl apply -f configmap.yaml
configmap/webserver-config configured
With the new configuration running, we’ll need Nginx to reload it. There’s a number of different ways we could do this but I’m going to use a hack that will allow us to test the config and then restart. First step, I want to make sure the new config will work for us:
# kubectl exec -it webserver-8fb84dc86-7xw4w -c webserver -- /usr/sbin/nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
It looks like the configuration is acceptable so let’s reload Nginx.
# kubectl exec -it webserver-8fb84dc86-7xw4w -c webserver -- /usr/sbin/nginx -s reload
2019/12/28 14:01:32 [notice] 2804#2804: signal process started
We should now be ready to commit a PHP file to our repo and test.
Testing Our Configuration
Let’s create a simple PHP file in the html directory of our.
# kubectl exec -it webserver-8fb84dc86-7xw4w -c webserver -- /bin/bash
root@webserver-8fb84dc86-7xw4w:/# apt update
Hit:1 http://deb.debian.org/debian buster InRelease
Hit:2 http://deb.debian.org/debian buster-updates InRelease
Hit:3 http://security-cdn.debian.org/debian-security buster/updates InRelease
Reading package lists... Done
Building dependency tree
Reading state information... Done
All packages are up to date.
root@webserver-8fb84dc86-7xw4w:/# apt install curl
Reading package lists... Done
Building dependency tree
Reading state information... Done
curl is already the newest version (7.64.0-4).
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
root@webserver-8fb84dc86-7xw4w:/# curl localhost/index.php
hello world from php
A great question to ask is how does php-fpm know which file and where that file exists? Like I said, great question.
This is handled by the fastcgi_param SCRIPT_FILENAME entry. This means that Nginx is going to tell php-fpm that it should try to load the $document_root$fastcgi_script_name file for the request. If you look at our configMap, we define document root as /usr/share/nginx/www/html. Assuming a request comes for index.php into Nginx, Nginx will tell php-fpm to also load /usr/share/www/html/index.php. In an environment where Nginx + PHP live on the same host, this doesn’t appear to be a problem because that file will exist for sure. In our configuration, we running two separate hosts aka containers. So we need to make sure the file exists on both servers in the same location. That’s the easy part! It does! Reason being, we’re using gitsynch on both containers and mounting that synched directory to the same location!
Full Working Configs
In case you want to just cheat and load the configurations, feel free to download them and play around: