acederberg.io
  • Home
  • Projects
    • Projects
    • Blog
    • Nvim Configuration
    • Blog and Demo Automation
    • Captura
  • Resume
  • Posts
  • Kaggle
  • Leetcode

On this page

  • Why not use Terraform?
    • Why Does Hashicorp Language Exist?
  • A Quick Comparison
    • Setup
    • Kubernetes Deployment
    • Kubernetes Service

Why Pulumi?

Author

Adrian Cederberg

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.

    section "breakfast" {
      eggs = protein
      toast = carbohydrates
      coffee = "required"
    }
    
    lunch = coffee
    {
      "breakfast": {
        "eggs": "protein",
        "toast": "carbohydrates",
        "coffee": "required"
      },
      "lunch": "coffee"
    }
  • Providing ‘paths’ as keys for nested objects.

    section 'this' 'is' 'a' 'path' {
      thing = "whatever"
    }
    {
      "this": {
        "is": {
          "a": {
            "path": {
              "thing": "whatever"
            }
          }
        }
      }
    }
  • Creation of arrays for repeated keys.

    section 'my-section'
      thing = whatever
      thing = the
      thing = heck
    }
    {
      "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 {
  domain       = var.domain
  labels       = {
    "acederberg.io/tier"      = "base"
    "acederberg.io/component" = "error_pages"
  }
  show_details = var.error_pages_show_details ? "1" : "0"
  template_url = var.error_pages_template_name != "" ? var.error_pages_template_name : "https://tarampampam.github.io/error-pages/"
  port         = 8080
}
# Pulumi with Python
import pulumi
import pulumi_kubernetes as k8s

TRAEFIK_NAMESPACE = 'traefik"

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 {
    name      = "error-pages"
    namespace = var.TRAEFIK_NAMESPACE
    labels    = local.labels
  }

  spec {
    replicas = 1

    selector {
      match_labels = local.labels
    }

    template {
      metadata {
        labels = local.labels
      }

      spec {
        container {
          name  = "error-pages"
          image = "ghcr.io/tarampampam/error-pages"

          readiness_probe {
            http_get {
              path = "/500.html"
              port = local.port
            }
          }

          ports {
            container_port = local.port
          }

          env {
            name  = "SHOW_DETAILS"
            value = local.show_details
          }

          env {
            name  = "TEMPLATE_NAME"
            value = local.template_url
          }
        }
      }
    }
  }
}
container_args = k8s.core.v1.ContainerArgs(
    name="error-pages",
    image="ghcr.io/tarampampam/error-pages",
    readiness_probe=k8s.core.v1.ProbeArgs(
        http_get=k8s.core.v1.HTTPGetActionArgs(
            path="/500.html",
            port=port,
        )
    ),
    ports=[k8s.core.v1.ContainerPortArgs(container_port=port)],
    env=[  # type: ignore
        dict(name="SHOW_DETAILS", value=str(1 if show_details else 0)),
        dict(
            name="TEMPLATE_NAME",
            value=config.get(
                "error_pages_template_name",
                "https://tarampampam.github.io/error-pages/",
            ),
        ),
    ],
)

deployment = k8s.apps.v1.Deployment(
    "error-pages-deployment",
    metadata=metadata,
    spec=k8s.apps.v1.DeploymentSpecArgs(
        replicas=1,
        selector=selector,
        template=k8s.core.v1.PodTemplateSpecArgs(
            metadata=metadata,
            spec=k8s.core.v1.PodSpecArgs(
                containers=[container_args],
            ),
        ),
    ),
)

Kubernetes Service

Side by side comparison of provisioning the error pages service using pulumi versus terraform.

resource "kubernetes_service" "error_pages" {
  metadata {
    name      = "error-pages"
    namespace = var.TRAEFIK_NAMESPACE
    labels    = local.labels
  }

  spec {
    type     = "ClusterIP"
    selector = local.labels

    port {
      name       = "error-pages-http"
      port       = local.port
      target_port = local.port
    }
  }
}
service = k8s.core.v1.Service(
    "error-pages-service",
    metadata=metadata,
    spec=k8s.core.v1.ServiceSpecArgs(
        type="ClusterIP",
        selector=labels,
        ports=[
            k8s.core.v1.ServicePortArgs(
                name="error-pages-http",
                port=port,
                target_port=port,
            )
        ],
    ),
)