Abstraction is key to building resilient systems because it encapsulates behavior and decouples code, letting each component perform its function independently. The same principles apply to infrastructure, where we want to declare behavior or state and not implementation details. As an industry, we've moved away from monolithic applications to distributed systems such as serverless, microservices, Kubernetes, and virtual machine deployments. In this article, we'll take a closer look at the characteristics of these architectures and how Pulumi can abstract the components that comprise these systems.
Virtual machines arose from the need to use expensive hardware more efficiently. VMs are more efficient because they share the host server, decreasing the number of physical servers needed to run applications. A group of hosts can also be aggregated so that it behaves as a single virtual host with large memory and processing capabilities. Virtual machines can emulate other environments that have specific hardware requirements or capabilities that the host machine does not have, thus reducing the need for specific hardware or operating systems. Virtualization provides the host machine isolation from the guest virtual machines by limiting access to the host machine through an abstraction layer. Finally, virtualization has the added advantage of providing a fine degree of control over resources available to the virtual machine.
If virtual machines are part of your infrastructure, Pulumi lets you create virtual machines programmatically across cloud service providers. The JavaScript example below illustrates how to deploy a virtual machine on Google Cloud Platform. The script spins up a Debian based instance and creates a network with an open port 22 to let you configure and manage the machine as needed.
When the virtual machine is created, it reads the configuration file that creates a webserver. This a basic example, but it shows how to configure virtual machines programmatically, which is useful for scaling horizontally.
Serverless is a computing architecture characterized by server-side logic running in stateless containers that are invoked by events. They are typically ephemeral (often are available for only one call), and managed by third-party cloud providers. Serverless is also called Functions as a Service, or FaaS, and well-known implementations include AWS Lambda and Fargate, Azure Functions, and Google Cloud Run and Cloud Functions.
A significant benefit of serverless is that it's a polyglot platform that allows developers to choose languages optimized for a task. For example, scripting languages like JavaScript or Python may be more responsive than a language such as Java. Serverless functions can support both synchronous and asynchronous calls. There are use cases that require an immediate response such as processing a video stream and instances where returning the result immediately is not critical as in an ETL batch job.
A key part of serverless architecture is an API Gateway that provides logical routes for mapping standard HTTP operations such as GET, PUT, POST, and DELETE to functions. The gateway makes development easier since these are standard and well-known interfaces. In addition to an API Gateway, the serverless platform should provide REST endpoints that allow you to manage the deploy with a CLI, portal, or an automation script. Finally, serverless architecture is extensible and supports integration with event sources and resources from the cloud provider through webhooks and other mechanisms.
The following example illustrates how to deploy two functions using TypeScript, one written in Python and the other written in Go. You can find the [full example](https://github.com/pulumi/examples/tree/master/gcp-ts-serverless-raw) in the Pulumi GitHub repository.
Kubernetes is a container orchestration system for deploying, scaling, and managing your application. Applications use multiple containers that contain code and dependencies needed for the code to execute. Kubernetes runs on a cluster of nodes and containers are deployed in pods. Kubernetes manages the pods by replacing containers that fail and removing containers that don't respond. It can also scale the number of containers as needed and load balance the network traffic. These are some of the features that Kubernetes provides:
The following example is based on the [Kubernetes Guestbook](https://kubernetes.io/docs/tutorials/stateless-application/guestbook/) example. The difference between this implementation and the original application is that instead of using YAML to declare the infrastructure, it uses a component written in TypeScript to create the service deployment which is implemented as `k8sjs`.
The [original example](https://github.com/kubernetes/examples/tree/master/guestbook) defines the Service and Deployment in YAML. In the example above, the YAML is abstracted in a reusable component written in a modern programming language. The [complete example](https://github.com/pulumi/examples/tree/master/kubernetes-ts-guestbook) is available on GitHub. As you can see, the ability to reuse components gets us closer to implementing architecture as code.
Microservices are based on the idea that components encapsulate a business capability. Services are independently deployable and communicate via web service requests, which has the advantage of redeploying only one or selected services and leaving the remaining components up and running. The [12-Factor app](https://12factor.net/) is the canonical pattern for microservices, which is summarized by:
In addition to Kubernetes, there are other platforms for deploying container-based applications. AWS Elastic Container Service, Google Cloud Run, and Azure Container Service provide alternatives to Kubernetes for container orchestration. The following code snippet demonstrates deploying a container with a Ruby application in Google Cloud Run. Note that the image is built using a local Docker engine and pushed to the Google Cloud Repository. The code shows that you can also set memory limits and concurrency for the container. The [full example](https://github.com/pulumi/examples/tree/master/gcp-ts-cloudrun) is available on GitHub.
We've covered the major infrastructure architectural patterns use for deploying modern applications. Whether you use Virtual Machines, Serverless, Kubernetes or Microservices with containers the goal is to create reusable components that abstract the configuration details and enable plug-and-play architecture. In subsequent articles, we'll take an in-depth look at each pattern and how to implement them on major providers using modern languages.