Terraform For Local Environments (podman+kind)

Summary

I do most of my containerization work locally using podman & kind. It’s an easy way to spin up a local environment. From time to time I want to upgrade the K8s version or just completely blow it away.

With kind it is pretty simple…

kind delete cluster --name=<cluster_name>

kind create cluster --name=<cluster_name>

I then load in my Mozilla SOPS key. Then I run my bootstrap script for FluxCD.

But Then I got Lazy

Over the weekend, there was an interesting podman Desktop Bug which caused my kube-apiserver to peg the CPU. It took a bit of fiddling and recreating the cluster a few times.

So I got lazy and wrote some terraform to do it for me.

Providers

For this I used a few terraform providers, namely tehcyx/kind, alekc/kubectl, integrations/github and hashicorp/kubernetes.

TF Resources

For everything we need a kind cluster. This is pretty simple. The key is we want to wait_for_ready because we’ll be doing further actions. The node_image is option and it will just pick the latest.

resource "kind_cluster" "this" {
  name = var.kind_cluster_name
  node_image = var.kind_node_image
  wait_for_ready = true
}

We then want to apply two manifests since Flux has already been bootstrapped and setup.

These two data sources will pull the appropriate manifests from the repository. The components are just that and the base dependencies. The sync manifest is the actual sync configuration data (what to sync, where to sync from, etc).

data "github_repository_file" "gotk-components" {
  repository          = "${var.github_org}/${var.github_repository}"
  branch              = "main"
  file                = var.gotk-components_path
}

data "github_repository_file" "gotk-sync" {
  repository          = "${var.github_org}/${var.github_repository}"
  branch              = "main"
  file                = var.gotk-sync_path
}

Because these manifests have multiple documents, we need to use another data source since kubectl_manifest can only apply a single document at a time.

data "kubectl_file_documents" "gotk-components" {
    content = data.github_repository_file.gotk-components.content
}

data "kubectl_file_documents" "gotk-sync" {
    content = data.github_repository_file.gotk-sync.content
}

We then loop through with a foreach on the components

resource "kubectl_manifest" "gotk-components" {
  depends_on = [ kind_cluster.this ]
  for_each  = data.kubectl_file_documents.gotk-components.manifests
  yaml_body = each.value
}

Before we can apply the sync section, we need to ensure the Mozilla SOPS age.key is applied. We have sensitive data in this environment and key allows us to decrypt it. In other environments this may be a key vault or KMS.

resource "kubernetes_secret" "sops" {
  depends_on = [kubectl_manifest.gotk-components]
  metadata {
    name = "sops-age"
    namespace = "flux-system"
  }
  data = {
    "age.agekey" = file(var.sops_age_key_path)
  }
}

Finally we now want to apply the sync configurations and we’re done!

resource "kubectl_manifest" "gotk-sync" {
  depends_on = [ kind_cluster.this, kubernetes_secret.sops ]
  for_each  = data.kubectl_file_documents.gotk-sync.manifests
  yaml_body = each.value
}

Finale!

From here its terraform apply and we’re off!