Why Pulumi?
To deploy my blog and to deploy some future projects, I decided I would like to set up infrastructure as code using Pulumi
for linode
I decided to use Pulumi
since it can be written in many languages (and not some domain specific language, for instance Hashicorp language for terraform). Pulumi
would integrate with other automation tasks that I wanted to do using python
, for instance pointing the domain names to the ingress IP address. My goal is to be able to have one contiguous entry point for automation - the ideal case is where everything runs as one program.
Why not use Terraform?
Domain specific languages are languages targeted to only one particular use case. While it is possible to automate other tasks using scripts in terraform I would much rather write infrastructure a code in a language in common language in which scripts can be written directly. Essentially, HCL
does not have any apparent advantages and generally takes a longer time to develop since there are no other compelling use cases (in my opinion).
Why Does Hashicorp Language Exist?
First, it is useful to ask where it came from.
The Origins of HCL
.
HCL
is very much inspired by (as stated in the HCL
github repo README the so-called universal configuration language, which aims to be a superset of the nginx
configuration and JSON
, a popular web-server. Some of the features of UCL
are:
Quotations are not required for strings, and braces are not required at the top level.
"breakfast" { section = protein eggs = carbohydrates toast = "required" coffee } = coffee lunch
{ "breakfast": { "eggs": "protein", "toast": "carbohydrates", "coffee": "required" }, "lunch": "coffee" }
Providing ‘paths’ as keys for nested objects.
section 'this' 'is' 'a' 'path' {= "whatever" thing }
{ "this": { "is": { "a": { "path": { "thing": "whatever" } } } } }
Creation of arrays for repeated keys.
-section' section 'my= whatever thing = the thing = heck thing }
{ "my-section": { "thing": ["whatever", "the", "heck"] } }
A variety of comment syntaxes, e.g.
#
,//
, and the multi-line/* ... */
.Macros and includes, so that a configuration may be broken up into many files and url references.
Variables.
The problem with configuration is that it tends to grow, repeat itself, and so on. Further, a number of other configuration languages (for instance TOML
and YAML
) implement similar features. I quite like UCL
, however it should remain only as a configuration language.
The Features of HCL
.
Control flow, logic, and recursion become necessary when writing out configurations to avoid violating the DRY principal. HCL
offers a number of programming language features such as
- collections like tuples and objects,
- loops using blocks with a
for
attribute, - and variables and secrets.
While this is all nice, at this point, it is necessary to ask: Why not just use an actual programming language?
Declarative Programming
The main reason to use HCL
(in my opinion) is for declarative configuration - this can save developers from the dispatching of API calls to various services and puts it on the providers. For the developers to consume the API, an understanding of each providers API
and how to authenticate against it is required.
However, this is all true of Pulumi
too. Objects are instantiated to declare resources and the developer does not have to get their hands dirty making provider API calls and interpreting responses.
A Quick Comparison
Here is a comparison of terraform
and pulumi
being used to deploy some static error-pages
in kubernetes
Setup
# Terraform with Kubernetes provider
# Set up labels
locals {
= var.domain
domain = {
labels "acederberg.io/tier" = "base"
"acederberg.io/component" = "error_pages"
}= var.error_pages_show_details ? "1" : "0"
show_details = var.error_pages_template_name != "" ? var.error_pages_template_name : "https://tarampampam.github.io/error-pages/"
template_url = 8080
port }
# Pulumi with Python
import pulumi
import pulumi_kubernetes as k8s
= 'traefik"
TRAEFIK_NAMESPACE
config = pulumi.Config()
= {
labels "acederberg.io/tier" = "base"
"acederberg.io/component" = "error_pages"
}
Kubernetes Deployment
Side by side comparison of provisioning the error pages deployment in kubernetes using pulumi
versus terraform
.
# Deployment for error pages
resource "kubernetes_deployment" "error_pages" {
metadata {= "error-pages"
name = var.TRAEFIK_NAMESPACE
namespace = local.labels
labels
}
spec {= 1
replicas
selector {= local.labels
match_labels
}
template {
metadata {= local.labels
labels
}
spec {
container {= "error-pages"
name = "ghcr.io/tarampampam/error-pages"
image
readiness_probe {
http_get {= "/500.html"
path = local.port
port
}
}
ports {= local.port
container_port
}
env {= "SHOW_DETAILS"
name = local.show_details
value
}
env {= "TEMPLATE_NAME"
name = local.template_url
value
}
}
}
}
} }
= k8s.core.v1.ContainerArgs(
container_args ="error-pages",
name="ghcr.io/tarampampam/error-pages",
image=k8s.core.v1.ProbeArgs(
readiness_probe=k8s.core.v1.HTTPGetActionArgs(
http_get="/500.html",
path=port,
port
)
),=[k8s.core.v1.ContainerPortArgs(container_port=port)],
ports=[ # type: ignore
envdict(name="SHOW_DETAILS", value=str(1 if show_details else 0)),
dict(
="TEMPLATE_NAME",
name=config.get(
value"error_pages_template_name",
"https://tarampampam.github.io/error-pages/",
),
),
],
)
= k8s.apps.v1.Deployment(
deployment "error-pages-deployment",
=metadata,
metadata=k8s.apps.v1.DeploymentSpecArgs(
spec=1,
replicas=selector,
selector=k8s.core.v1.PodTemplateSpecArgs(
template=metadata,
metadata=k8s.core.v1.PodSpecArgs(
spec=[container_args],
containers
),
),
), )
Kubernetes Service
Side by side comparison of provisioning the error pages service using pulumi
versus terraform
.
resource "kubernetes_service" "error_pages" {
metadata {= "error-pages"
name = var.TRAEFIK_NAMESPACE
namespace = local.labels
labels
}
spec {= "ClusterIP"
type = local.labels
selector
port {= "error-pages-http"
name = local.port
port = local.port
target_port
}
} }
= k8s.core.v1.Service(
service "error-pages-service",
=metadata,
metadata=k8s.core.v1.ServiceSpecArgs(
spectype="ClusterIP",
=labels,
selector=[
ports
k8s.core.v1.ServicePortArgs(="error-pages-http",
name=port,
port=port,
target_port
)
],
), )