The good and bad of moving to Kubernetes
Encapsulation in Kubernetes
In 2018, for various reasons we moved our website to Kubernetes. When we started, we had little idea what Kubernetes had to offer. All we knew was that it can run a website based on containers that are full OS images. It is easier to move from one cloud provider to another. Cool! Little did we know the biggest advantage it offers: Encapsulation.
The biggest advantage of Kubernetes in my opinion is the encapsulation and composability of backend components. Any Saas product has to run a number of components from database, appserver, monitoring stack, pub/sub etc etc. Kubernetes makes everything a helm chart and some configuration away. The interfaces are neatly defined by the author of the helm chart and all the complexity is encapsulated well within a service. You could use many third party helm charts: Monitoring: logging - Elasticsearch Monitoring: metrics - Prometheus Authentication - FusionAuth Database - MongoDB Chaos Monkey - Gremlin CD - Argo ... This abstraction of complexity lets every team focus on its unique IP and easily reuse third party components. Now why was it not possible before? VM images were too big, needed a lot of time to boot and it was hard to maintain a lot of them.
Composability is not just limited to third party components. Even your own code becomes very composable with well defined interfaces. Now how should we decide the boundary between two components in a Kubernetes cluster? The same priciples that apply in designing a database or paritioning a program to classes is useful here but with slightly different parameters. When we decide what should go into a class, we use the priciple of loose coupling and strong cohesion: The interface between classes should be simple and the complexity can be captured inside the class. Also, strong cohesion means methods and properties inside the class should be related to each other: they should change together. A change in behavior should as much as possible be confined within the class.
Choosing the boundary of Kubernetes components depends on a few things apart from the Simplicity of the interface: Single process: Everything in one component should run as a single process/container. It should have the same dependencies. You dont really have to worry about this because you pretty much have little choice. Configurability of the component: IF a configuration change requires the restart of certain parts, they should be in the same component. Auto-scaling policies for the component. If one component is CPU bound and needs CPU scaling whereas other needs Memory based autoscaling, they should be in different components. We should strive to decouple state of the containers from the runtime as much as possible, by using databases and volume claims. But ideally one component should not have too many different types of state.
It can also be thought of as taking a UML component diagram and giving each component a container of its own. The concept of components in UML closely resembles an image.
Now, let's say someday that the component configuration is super easy and the configuration/maintenance of these components rests in the hands of the creator of the component itself (for a fee of course). The frontend app developer would go hire some of these backend components, configure them together and a new app is ready to run! The road will not be easy but this will be true low-code where complex services can be built by just knowing UI programming.