How to build a scalable Symfony application on Kubernetes
Modern web applications are complex. The expectations of your users regarding your application are constantly increasing: nowadays, an application needs to be fast, convenient, easy to use and beautiful.
Meeting these demands can become another difficulty in the path towards creating a great product. Even if you tackle a real issue, you need to implement it the right way in order to make a living from it.
To ease this new difficulty and decrease the time spent on creating and maintaining these expected features, a modern application usually leverages many different components, from content delivery networks (CDN) to full text search services and load balancers.
This architecture aims to build an application on top of generic services (cache, full text search, job queue, etc.). This naturally decreases the time required to maintain these services as they are usually maintained by someone else (and sometimes open-source).
When you use such infrastructure, being able to interact easily with all its components from within your application is critical. This is where Kubernetes and Symfony are working together to help you achieve incredible results, extremely quickly.
Kubernetes: a Docker containers orchestrator
A few years ago, the Docker project started to emerge as a way to let developers easily create infrastructures such as the previous one. With a few lines of configuration, any developer using Docker is able to create a network of interconnected containers, abstracting the complexity of setting up each of the services.
This project revolutionized the way many developers thought about infrastructure. Nowadays, it is common to use the term DevOps, referring to people able to develop applications while having the suited infrastructure in mind.
Kubernetes is the natural following step: it creates a production-ready environment for Docker containers to run, providing security, resilience and scalability. With the help of Docker and Kubernetes, you will be able to easily create applications leveraging a full set of generic services, helping you build great products much quicker.
In this article, I will focus more precisely on Kubernetes on Google Cloud Platform (GCP) to have an example, but this could be applied to any cloud provider.
Using Symfony in Kubernetes
It is always useful to think about the integration of your application in your infrastructure before developing it. It allows you to determine what services you need based on your business requirements and how to interact with them from your application.
One of the most important element to keep in mind when you create an application is its ability to scale.
“To scale an application” actually means to increase the number of production instances of your application’s code to handle more requests. Thus, to create a “scalable application”, there is a single main idea to always keep in mind: don’t store the state of your application in the code container. If your code container has a state, this state will be duplicated during scaling, leading to consistency issues which could break your application.
As I explained in my article about creating a fast test suite with Symfony, there are two main places where the state of our application lives: the database and the filesystem.
Use Flysystem to store your application files in the managed file store
Pretty much all the cloud providers have at least some way of storing file-like elements externally (GCP has Google Cloud Storage). Don’t hesitate to rely on them to store your application files: they are infinitely extensible, provide an easy-to-configure CDN on top of them, and are fast and reliable.
I usually uses Flysystem to access and interact with such services. Flysystem is a library providing an abstraction for the filesystem. In addition to help you create a better test suite, Flysystem is also compatible with many providers, among which there is almost certainly your cloud provider.
For Google Cloud Storage, I personally use https://github.com/Superbalist/flysystem-google-cloud-storage.
Configure Doctrine to use the provided SQL service
Most cloud providers will also give you the opportunity to rely on their own managed SQL service. GCP has the Google Cloud SQL service, which support MySQL and PostgreSQL.
These products are a great way to store the database state of your application in a scalable, reliable place. If you use Google Cloud SQL with Kubernetes, I recommend you to use the Cloud SQL proxy. It will create a proxy for your database that you will be able to use itas a classical database with Doctrine.
Use Redis for your cache and your sessions
Your application cache and sessions are a part of the state of your project. They need to be shared between your instances in order to avoid issues.
Redis is perfectly suited for these use cases: as a memory key-value store, it’s extremely fast and it’s able to handle hundreds of thousands of connections in parallel. It probably won’t be the bottleneck of your application.
Fortunately, Symfony is already designed by nature to allow the configuration of Redis as a session handler and as a cache backend:
- to configure it as a session handler, I personally use https://github.com/snc/SncRedisBundle
- to configure it as a cache backend, a few lines of configuration are enough:
Use a shared location for your logs
While your application logs are not technically a part of your state, having them dispatched in many different containers is a nightmare to debug issues. It’s usually a good idea to store them in a shared location instead of inside the container.
There are many handlers available in Monolog which can help on this level: ElasticSearch, MongoDB, ... However, I tend to like Sentry: it’s a super useful service creating really detailed reports on your issues, automatically. I will talk a bit more about it and how to use it in Symfony in a future article :) .
Use environment variables to configure your application from within Kubernetes
There is a state we can easily forget: credentials and secrets. While they won’t change at runtime, they shouldn’t be stored in your code container either for security reasons.
Fortunately, it is possible to do better with Kubernetes. Actually, there are even two ways to do it better:
- either you can rely on Kubernetes environment variables to inject the values directly in your containers and use the environment variable features of Symfony.
- or you can use the dedicated secret management system of Kubernetes which allow you to store, manage and mount secrets as files in your containers, which is supported by Symfony with environment variables processors (env(file:your_secret_file)).
This idea of not storing the state of an application close to its code is becoming something standard. The concepts I listed here are just the tip of the iceberg: don’t hesitate to have a look at The Twelve-Factor App and Reproducible Builds to learn more.
Have something else to add to this article? Don’t hesitate to comment!