Story 1: Do I know Kubernetes? A little bit… but I know how to run a PHP platform using good container design principles!
We spent two years, at Cốc Cốc, learning, building, and bringing a Kubernetes (K8s) cluster on bare-metal to production. Building K8s is a hopeful journey for our team, and we were excited to introduce it to our beloved developers. We were telling others how cool it was, encouraging them to containerize their application and move to K8s. But then, slowly I realized that it’s not that important what container orchestration system we use or how powerful it is. Indeed, we can only make the best use of it if we first equip our-self with good principles to design container. Afterward, a good choice is the one that can leverage those principles in making robust container-native applications.

There are plenty of articles or blog posts about designing container best practices but, in my opinion, there is no “best practice”. It all depends on how your application is expected to run and behave on your working technical stack. However, we still can have some “simple and good enough” rules to stick with. And I love simple things.
First, let’s define what properties do we expect our application to have. Generally, we want our application reliable to faults, scalable to load, and maintainable.
There are quite many PHP web applications in our company and we even worked with developers to agree on a common design template that helps us to quickly and efficiently make them container-native and K8s-ready. Next, I will go to the detail of that specific template to illustrate two important principles.
Single Concern
| Each container addresses a single concern and does it well.
It’s the fundamental principle of container application that you will need to keep in mind. The word “concern” refers to a particular set of the application’s functionality. This principle recommends running each function of your application in one container.
On one hand, if a container addresses a single concern, in a feature-complete way, it’s replaceable by another one addressing the same concern and can be reused in different applications. That makes your application flexible and maintainable. In addition, since each concern is dedicated to one container, it’s easy to trace which one is broken if you have a bug and if you break something, you break only one thing.
On the other hand, your micro-service usually needs to address multiple concerns and requires them to be tightly coupled in one deployment unit. It means that no matter how a container is deployed, the other ones must be present at the same time and same place. And K8s addresses this requirement very well by its original concept, a multi-container POD, the smallest deployable unit that you can create and manage. It’s a group of containers with natively shared networking and storage — filesystem volumes.
Let’s say that a PHP web application needs a deployment unit containing a web server, Nginx is the major one in our company, to receive requests from clients, then connect to PHP-FPM and pass on the request type, data, and headers to it. Also, they need access to the same source code. In the other words, the application has two main concerns: one to be the first to contact the outside world and one manages the request execution.

Our application has 2 “coupled” concerns, and one is useless without the other, so the application pod will have two main containers running:
- Image-based on PHP-FPM official image stores your application code.
- Nginx official image.
- In addition, a volume pre-provisioned by a run-to-complete container, called InitContainer in K8s, to share files between the two containers.
cp -R /var/www/public/. /app-public/
Process Disposability
| Execute the app as one or more stateless processes
| Maximize robustness with fast startup and graceful shutdown
Containers are fundamentally designed to be stateless and immutable. Stateless means that any state (persistent data of any kind) is stored outside a container. This external storage can take several forms, depending on what you need: file, information, and even per-environment configuration. Immutable means that a container won’t be modified during its life: updating the application code or applying a patch requires building a new image and deploying it.
In the real world, there are many reasons to replace a container other than deploying a new container image, such as application crashing, migrating the container to a different host, or host resource starvation. This principle covers two of The Twelve-Factor App methodology — Process and Disposability — that recommends keeping application state externalized or distributed and redundant. It also means the application should be quick in starting up and shutting down. Your container will be as ephemeral as possible and ready to be instantly replaced by another container at any point in time without any manual intervention/attention. In other words, it makes your application fault-tolerant, and easy to scale.
Going back to our PHP application, we currently use:
- CephFS and Ceph S3-compatible to store files according to specific needs.
- Redis to store temporary/caching data like session and MySQL to store persistent data.
- Another original K8s concept is called ConfigMap, to store per-environment configuration for PHP, PHP-FPM, Nginx, and environment variables.

Summary
There are quite a few other popular principles to design a container application and the two principles above are indeed part of the Principles of container-based application design white paper. I choose these two principles in the first story of our Kubernetes journey, since they are, in my honest opinion, must-at-least strictly followed principles. They help to make our application cloud-native — responsive, scalable, fault-tolerant — and effectively automated by orchestration platforms like Kubernetes. They also support the implementation of other principles. We will target them within our lesson-learned stories after 2 years of running Kubernetes in production in future articles.