Terraform

Parts:

  1. New skill to learn? Oh no
  2. Case study - Rebuild this site (PL)
  3. Getting started yourself

New skill to learn? Oh no

Jobs: https://justjoin.it/?q=Terraform@skill

Terraform is currently the most popular Infrastructure as Code tool out there. Terraform is extremely helpful because it is vendor-agnostic and offers hundreds of unique Terraform Providers built by technology companies to manage their solutions through Intrastructure as Code.

With many companies adopting a DevOps / DevSecOps culture and automating as much as possible of their infrastructure, Terraform can be a very welcome asset as you only need to write HCL and have the ability to manage multiple vendors.

– Mike Elissen, Akamai

Chef, Puppet, Ansible, and SaltStack are all configuration management tools, which means they are designed to install and manage software on existing servers. CloudFormation and Terraform are provisioning tools, which means they are designed to provision the servers themselves (as well as the rest of your infrastructure, like load balancers, databases, networking configuration, etc), leaving the job of configuring those servers to other tools. These two categories are not mutually exclusive, as most configuration management tools can do some degree of provisioning and most provisioning tools can do some degree of configuration management. But the focus on configuration management or provisioning means that some of the tools are going to be a better fit for certain types of tasks.

In particular, we’ve found that if you use Docker or Packer, the vast majority of your configuration management needs are already taken care of. With Docker and Packer, you can create images (such as containers or virtual machine images) that have all the software your server needs already installed and configured. Once you have such an image, all you need is a server to run it. And if all you need to do is provision a bunch of servers, then a provisioning tool like Terraform is typically going to be a better fit than a configuration management tool (here’s an example of how to use Terraform to deploy Docker on AWS).

https://blog.gruntwork.io/why-we-use-terraform-and-not-chef-puppet-ansible-saltstack-or-cloudformation-7989dad2865c

Configuration management tools such as Chef, Puppet, Ansible, and SaltStack typically default to a mutable infrastructure paradigm. For example, if you tell Chef to install a new version of OpenSSL, it’ll run the software update on your existing servers and the changes will happen in-place. Over time, as you apply more and more updates, each server builds up a unique history of changes. This often leads to a phenomenon known as configuration drift, where each server becomes slightly different than all the others, leading to subtle configuration bugs that are difficult to diagnose and nearly impossible to reproduce.

If you’re using a provisioning tool such as Terraform to deploy machine images created by Docker or Packer, then every “change” is actually a deployment of a new server (just like every “change” to a variable in functional programming actually returns a new variable). For example, to deploy a new version of OpenSSL, you would create a new image using Packer or Docker with the new version of OpenSSL already installed, deploy that image across a set of totally new servers, and then undeploy the old servers. This approach reduces the likelihood of configuration drift bugs, makes it easier to know exactly what software is running on a server, and allows you to trivially deploy any previous version of the software at any time. Of course, it’s possible to force configuration management tools to do immutable deployments too, but it’s not the idiomatic approach for those tools, whereas it’s a natural way to use provisioning tools.

https://blog.gruntwork.io/why-we-use-terraform-and-not-chef-puppet-ansible-saltstack-or-cloudformation-7989dad2865c

Case study - Rebuild this site

Plik main.tf:

terraform {
  required_providers {
    linode = {
      source = "linode/linode"
      version = "1.29.2"
    }
    template = {
      source = "hashicorp/cloudinit"
      version = "2.2.0"
    }
    local = {
      source = "hashicorp/local"
      version = "2.2.3"
    }
  }
}

provider "linode" {
    token = var.token
}

data "local_file" "user_data" {
    filename = "${path.module}/${var.service_name}/docker-compose.yaml"
}

#resource "local_file" "foo" {
#    content  = data.local_file.user_data.content
#    filename = "${var.service_name}/docker-compose.yaml"
#}

resource "linode_instance" "example_instance" {
    label = "example_instance_label"
    image = "linode/ubuntu22.04"
    region = "us-central"
    type = "g6-nanode-1"
    authorized_keys = [var.ssh_key_personal, var.ssh_key_professional]

  connection {
    type     = "ssh"
    host     =  self.ip_address
    user     = "root"
  }

  provisioner "file" {
    source      = "notes.kocielnik.pl/notes"
    destination = "/srv"
  }

  provisioner "remote-exec" {
    inline = [
      "mkdir ${var.service_name}",
      "apt update && apt install -y docker-compose",
    ]
  }

  provisioner "file" {
    source      = "${var.service_name}/docker-compose.yaml"
    destination = "${var.service_name}/docker-compose.yaml"
  }

  provisioner "remote-exec" {
    inline = [
      "ls ${var.service_name}",
      "cd ${var.service_name} && docker-compose up -d",
    ]
  }
}

output "ip" {
  value = resource.linode_instance.example_instance.ip_address
}

variable "token" {}
variable "ssh_key_personal" {}
variable "ssh_key_professional" {}
variable "service_name" {}

Kroki:

  1. Zapisujesz sobie ten plik,
  2. W tym samym katalogu, co ten plik, uruchamiasz: terraform init (instaluje "providery" z pierwszej sekcji – krok podobny do instalacji brakujących pakietów w Pythonie).
  3. Zmienne z końca tego pliku wypełniasz danymi w innym, "sekretnym" pliku (terraform.tfvars). Program git-crypt pomaga trzymać taki plik w postaci zaszyfrowanej, razem z pozostałymi nie zaszyfrowanymi, w tym samym repo.
  4. Po zmianie w pliku main.tf, terraform plan pokazuje zmiany do wprowadzenia na zdalnych maszynach. terraform apply zastosowuje zmiany.

Podejście deklaratywne

Domyślnie Terraform nie rekonfiguruje dotychczasowej infrastruktury. "niszczy" ("destroys") stare hosty, i tworzy nowe w ich miejsce.

To sprytny myk na uniknięcie "dryfu konfiguracji", gdzie maszyny postawione dawno temu mają niby "tę samą" konfigurację, co stawiane dziś (szczególnie: te same pakiety), ale jednak nie do końca.

Przykładem maszyny, która "podryfowała" będzie serwer, na którym wersja Ubuntu jest sprzed sześciu lat, wersje pakietów zainstalowanych są cztery lata do tyłu, a dostępnych w oficjalnym repozytorium - dwa lata do tyłu.

Jako skrupulatny admin instalowaliśmy aktualizacje do paczek prosto z GitHuba jeśli nie było ich w oficjalnym repo, ale w kwestii odtwarzania takiej konfiguracji w razie awarii: "Panie, kto by tam to wszystko spamiętał."

A właściwie – poprawka: Dryf to nie samo dezaktualizowanie się konfiguracji z czasem, ale "odpływanie" zapisanej gdzieś docelowej konfiguracji od faktycznych wymogów użytkowników danego serwera.

Przykład: Prowadzimy chmurę do hostowania własnych instancji pewnego discordowego bota. (👋)

Na początku spisaliśmy wymagania bota i zapisaliśmy to do pliku .tf.

Userzy potrzebowali nowej paczki – GNU Hello (okazała się im niezbędna do życia).

Nie chcieliśmy bawić się z Terraformem, więc wbiliśmy i zrobiliśmy apt install hello. Zadziałało, wróciliśmy do grania.

Było jeszcze trochę takich przypadków. Nikt już nie pamięta, jakie tam rzeczy wychodziły.

Po roku serwery padły. Infrastrukturę odtworzyliśmy... z dokładnością do pierwszej konfiguracji z main.tf. Z resztą będziemy się bawili dwa tygodnie, odbierając przy tym telefony od zdenerwowanych użytkowników.

Wyobrażam sobie, że przed takimi właśnie scenariuszami chcą się obronić ci oferujący specom od "stawiania chmur" (Terraform etc.) kwoty jak tam na stronie.

Provisioner-y

We wklejonym wyżej pliku main.tf, gdziekolwiek pada słowo provisioner, chodzi o "deklaratywne" dostarczenie czegoś na serwer (poza miejscem provisioner "remote-exec").

Może to być plik, który trzeba skopiować z dysku lokalnego:

provisioner "file" {
    source      = "notes.kocielnik.pl/notes"
    destination = "/srv"
}

(notes.kocielnik.pl/notes to lokalny katalog)

Można też wprowadzać zmiany nie-deklaratywnie ("imperatywnie").

Służy do tego m.in. provisioner remote-exec.

Producent Terraforma, HashiCorp, zaleca jednak, by gdzie się da, stosować deklaratywne metody. To ułatwia (albo wręcz umożliwia) Terraformowi porównywanie obecnej konfiguracji z zadaną.

Gdy zmiany są naprawdę minimalne, podobno wprowadza je nawet bez niszczenia zdalnej maszyny. Takie niszczenie-stawianie jako pojedynczy cykl trwa około 3 minut (na Linode).

remote-exec przyjmuje listę komend, i wykonuje je zdalnie.

Przykład z pliku powyżej:

  provisioner "remote-exec" {
    inline = [
      "mkdir ${var.service_name}", # mkdir notes.kocielnik.pl
      "apt update && apt install -y docker-compose",
    ]
  }

Lamerstwo w skali Terraforma, ale zawsze pozostaje jako opcja, gdy ktoś nie kojarzy, jak to zrobić deklaratywnie.

W tym miejscu chcę pozdrowić samego siebie, bo jeszcze nie wiem, jak zainstalować pakiet zdalnie. Dowiem się! :P

Getting started yourself

I recommend Linode for getting started with Terraform.

After that, you should be able to move between cloud providers quite easily.

Linode's "Getting Started" for Terraform: https://www.linode.com/docs/guides/beginners-guide-to-terraform/.

If you have a GitHub account, you can use that to access Linode's services.