

























When it comes to the infrastructure of a software system, there are some features that are virtually always needed, independently of the project nature, and some that are additional, optional, or useful only in some projects and contexts. Coming from a position of being aware of what we actually need and what our project is about, we can make more informed and rational decisions; hopefully reducing the complexity of our system, which is something that we should care about. Simplicity should be one of our most important goals, when designing software systems, because simple systems are easy to understand, debug, maintain and change. Infrastructure is a crucial component of every software system: what do we need from it?
Some of my choices might seem arbitrary, so a few words of comment might be helpful. Here are my observations, assumptions, thoughts and opinions - based on the experience of implementing various systems from scratch and then maintaining, changing and extending them over the years:
In general, I would argue that many, if not most, systems that we create these days are highly over-engineered and can be greatly simplified, without taking away any of their functionality or ability to handle load. I acknowledge that there are edge cases, when we do need nearly infinite scalability and resources, but the reality is that 99% of systems are neither Google nor Amazon or Netflix. Keeping this in mind, let's assess how Kubernetes aligns with our infrastructure requirements and needs.
Before we can answer this question, let's try to define what Kubernetes is:
Kubernetes is a container orchestration platform. It allows us to automatically deploy, scale and manage containerized applications.
In essence, it is a sophisticated container scheduler. We need to set it up as a cluster, on a set of machines; once established, applications can be defined as collections of Kubernetes objects and Kubernetes will take care of the rest. The rest means lots and lots of details: deploying application to one or many nodes (machines) that can satisfy its resource requirements, scaling it to required number of instances, restarting, if it is not healthy, and making it available to other applications in the cluster network or to the external world, if that is what we need.
Virtually everything in Kubernetes is configurable, dynamic and extendable. It is possible to have variable numbers of nodes (machines) that make up a Kubernetes cluster; it is also possible to have dynamic number of replicas for some or all applications, depending on their cpu/memory usage or any other criteria. It has built-in service discovery and load balancing mechanisms and can handle practically all network communication use cases.
In a nutshell: it tries to handle basically all theoretically possible infrastructure problems in the most abstract and configurable way possible.
Kubernetes is a powerful beast, but as they say:
No such thing as a free lunch.
For all these features we pay a high price: Complexity; Kubernetes is also a complex beast. It is mostly so, because it delivers so many features. There are numerous Kubernetes-specific concepts and abstractions that we need to learn. What is more, despite the fact that there are many managed Kubernetes services (Amazon EKS, Google GKE, DigitalOcean Kubernetes) that make setting up and operating a Kubernetes cluster significantly easier, it still needs to be learned and configured properly - we are not freed from learning and understanding how Kubernetes works. By we, I mean mostly the person/people/team who operate a cluster, but also to some extent developers, because they will be the ones who will configure and deploy applications (or at least they should be).
Is the price of Kubernetes worth it? As with everything, it depends. If we have multiple teams and dozens of (micro)services then probably yes, but I am biased towards simplicity, so in that case I would ask:
Do we really need to have tens and hundreds of microservices?
Sometimes, the answer will be yes, but we have to make sure that it is really a resounding yes, because it will bring lots of additional complexity that we are far better off avoiding.
Moreover, what is worth emphasizing, Kubernetes itself is not enough to solve all our infrastructure-related problems. We still need to have other tools and scripts to build, package and deploy our applications. Once we have a properly set up Kubernetes cluster, which itself is not an easy task, we are only able to deploy something. We then need to at least figure out:
Sadly, to make Kubernetes a complete platform, we need to use additional tools and that means even more complexity. This is a very important factor to keep in mind when evaluating the complexity of a set of custom scripts and tools to build, deploy and manage containerized applications.
As said, most systems can be implemented as just one or a few services, each deployed in one to several instances. If this is the case, Kubernetes is an overkill, it is not needed, and we should not use it. The question then remains: what is the alternative?
Before discussing the do-it-yourself (DIY) approach, I wanted to mention a few managed alternatives from various Cloud Service Providers; most notably, there is Amazon Fargate, Google Cloud Run and Azure Container Instances. They are all similar: they are based on containers and give most of the Kubernetes features for the price of vendor lock-in and additional cost for service. We give up control and need to pay more, but we get speed and convenience. They run containers for us, somewhere on their infrastructure, we do not have to worry about servers; we just need to learn a few abstractions to define services/tasks in their service-specific config file format, but then this is it, it just works. Compared to using Kubernetes, even as a managed service, most things are preconfigured and chosen for us with sensible defaults; we do not need to worry about the details, most of the time. We will still have to write a couple of scripts/pipelines to integrate our builds and deployments with these managed services, but significantly less than in the do-it-yourself approach, plus we are free not to care about infrastructure. However, it is important to note that these services have certain limitations and constraints - we have to make sure that they cover all our needs; depending on the situation, this dependency might be a tradeoff worth making. Nevertheless, even if we choose this path, I still recommend at least learning a do-it-yourself approach to have a better perspective on this decision and to know what the real tradeoffs are. Having this in mind, let's discuss a possible solution.
Building a solution from scratch, most, if not all, of our needs can be covered by:
docker run with all necessary parameters (like --restart unless-stopped), environment variables, runs pre/post scripts around it, stops previous version and so on. Running it would be just calling bash run_app.bash - the initialized docker container of our app with all required parameters will be then started/etc/hosts mechanism described below/etc/hosts file with our app names and private ip addresses of the machines, where they run. For example, on every machine we would have entries like 10.114.0.1 app-1, 10.114.0.2 app-2 and so on - that is our service discovery mechanism; we are then able to make requests to app-1:8080 instead of 10.114.0.1:8080. As long as the number of machines and services is reasonable, it is a perfectly valid solution
That is a lot, but we have basically covered all infrastructure features and needs for 99% of systems. Additionally, that is really all - let's not forget that with Kubernetes we have to use extra, external tools to cover these requirements; Kubernetes is not a complete solution. Another benefit of this approach is that depending on our system specificity, we can have a various number of scripts of varying complexity - they will be perfectly tailored towards our requirements. We will have minimal, essential complexity, there will only be things that we actually need; what is more, we have absolute control over the solution, so we can extend it to meet any arbitrary requirements.
We have gone over all possible requirements and expectations that a software infrastructure needs to meet. As it turns out, there are quite a lot of them; we need to think about and cover many things when designing our infrastructure.
We have found out that we should evaluate infrastructure complexity similarly to how we judge code or software architecture complexity. After all, infrastructure is also a part of our software system, arguably one of the most important ones. What is more, its complexity is tied up with our software architecture complexity. The more accidental complexity in our software architecture we have, the more it leaks into the infrastructure, making it unnecessary complex too. Same as we do with code and architecture, we should tailor infrastructure to our needs. We should not blindly follow current trends, but always stop and ask:
Is this really what we need? What are the tradeoffs and hidden costs?
We have then learned that Kubernetes is a powerful beast, but also highly complex one - and as we know or will learn in the future: Complexity is our Eternal Enemy.
If we really, really need to use a complex solution, then yes, we should go for it, but if we can avoid it, we should work hard to avoid it.
In conclusion: if we have a single or few teams, we should just build a modular monolith or few modular services. In that case, we do not need to use Kubernetes; we can just use managed services like AWS Fargate, Google Cloud Run or Azure Container Instances, or rollout a few Python/Bash scripts what will build, deploy and manage our containerized applications on a set of virtual private servers. We can also combine these two approaches, to whatever degree we want to be dependent on the cloud service providers or to be independent from them. Of course, there are instances where it makes sense to use Kubernetes; in the vast majority of cases however, by sticking to a reasonable number of services and avoiding Kubernetes, we will greatly simplify our system and make it easier to understand, maintain and extend. Then, rather than dealing with accidental complexity that we ourselves have created, we can focus on delivering next features and value to our users and customers.
So, let's forge ahead and keep simplifying!
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。