Running Flatcar Container Linux on Hetzner

    Hetzner Cloud is a cloud hosting provider. Flatcar Container Linux is not installable as one of the default operating system options but you can deploy it by installing it through the rescue OS. At the end of the document there are instructions for deploying with Terraform.

    Preparations

    Register your SSH key in the Hetzner web interface to be able to log in to a machine.

    For programatic access, create an API token (e.g., used with Terraform as HCLOUD_TOKEN environment variable).

    Provisioning

    Select any OS like Debian when you create the instance but boot into the linux64 rescue OS. Connect via SSH and download and run the flatcar-install script:

    curl -fsSLO --retry-delay 1 --retry 60 --retry-connrefused --retry-max-time 60 --connect-timeout 20 https://raw.githubusercontent.com/kinvolk/init/flatcar-master/bin/flatcar-install
    chmod +x flatcar-install
    ./flatcar-install -s -i ignition.json # optional: you may provide a Ignition Config as file, it should contain your SSH key
    shutdown -r +1 # reboot into Flatcar
    

    Terraform

    The hcloud Terraform Provider allows to deploy machines in a declarative way. Read more about using Terraform and Flatcar here .

    The following Terraform v0.13 module may serve as a base for your own setup. It will also take care of registering your SSH key at Hetzner.

    Start with a hetzner-machines.tf file that contains the main declarations:

    terraform {
      required_version = ">= 0.13"
      required_providers {
        hcloud = {
          source  = "hetznercloud/hcloud"
          version = "1.23.0"
        }
        ct = {
          source  = "poseidon/ct"
          version = "0.7.1"
        }
        template = {
          source  = "hashicorp/template"
          version = "~> 2.2.0"
        }
        null = {
          source  = "hashicorp/null"
          version = "~> 3.0.0"
        }
      }
    }
    
    resource "hcloud_ssh_key" "first" {
      name       = var.cluster_name
      public_key = var.ssh_keys.0
    }
    
    resource "hcloud_server" "machine" {
      for_each = toset(var.machines)
      name     = "${var.cluster_name}-${each.key}"
      ssh_keys = [hcloud_ssh_key.first.id]
      # boot into rescue OS
      rescue = "linux64"
      # dummy value for the OS because Flatcar is not available
      image       = "debian-9"
      server_type = var.server_type
      datacenter  = var.datacenter
      connection {
        host    = self.ipv4_address
        timeout = "1m"
      }
      provisioner "file" {
        content     = data.ct_config.machine-ignitions[each.key].rendered
        destination = "/root/ignition.json"
      }
    
      provisioner "remote-exec" {
        inline = [
          "set -ex",
          "curl -fsSLO --retry-delay 1 --retry 60 --retry-connrefused --retry-max-time 60 --connect-timeout 20 https://raw.githubusercontent.com/kinvolk/init/flatcar-master/bin/flatcar-install",
          "chmod +x flatcar-install",
          "./flatcar-install -s -i /root/ignition.json",
          "shutdown -r +1",
        ]
      }
    
      # optional:
      provisioner "remote-exec" {
        connection {
          host    = self.ipv4_address
          timeout = "3m"
          user    = "core"
        }
    
        inline = [
          "sudo hostnamectl set-hostname ${self.name}",
        ]
      }
    }
    
    data "ct_config" "machine-ignitions" {
      for_each = toset(var.machines)
      content  = data.template_file.machine-configs[each.key].rendered
    }
    
    data "template_file" "machine-configs" {
      for_each = toset(var.machines)
      template = file("${path.module}/machine-${each.key}.yaml.tmpl")
    
      vars = {
        ssh_keys = jsonencode(var.ssh_keys)
        name     = each.key
      }
    }
    

    Create a variables.tf file that declares the variables used above:

    variable "machines" {
      type        = list(string)
      description = "Machine names, corresponding to machine-NAME.yaml.tmpl files"
    }
    
    variable "cluster_name" {
      type        = string
      description = "Cluster name used as prefix for the machine names"
    }
    
    variable "ssh_keys" {
      type        = list(string)
      description = "SSH public keys for user 'core' and to register on Hetzner Cloud"
    }
    
    variable "server_type" {
      type        = string
      default     = "cx11"
      description = "The server type to rent"
    }
    
    variable "datacenter" {
      type        = string
      description = "The region to deploy in"
    }
    

    An outputs.tf file shows the resulting IP addresses:

    output "ip-addresses" {
      value = {
        for key in var.machines :
        "${var.cluster_name}-${key}" => hcloud_server.machine[key].ipv4_address
      }
    }
    

    Now you can use the module by declaring the variables and a Container Linux Configuration for a machine. First create a terraform.tfvars file with your settings:

    cluster_name = "mycluster"
    machines     = ["mynode"]
    datacenter   = "fsn1-dc14"
    ssh_keys     = ["ssh-rsa AA... [email protected]"]
    

    Create the configuration for mynode in the file machine-mynode.yaml.tmpl:

    ---
    passwd:
      users:
        - name: core
          ssh_authorized_keys: ${ssh_keys}
    storage:
      files:
        - path: /home/core/works
          filesystem: root
          mode: 0755
          contents:
            inline: |
              #!/bin/bash
              set -euo pipefail
              hostname="$(hostname)"
              echo My name is ${name} and the hostname is $${hostname}
    

    Finally, run Terraform v0.13 as follows to create the machine:

    export HCLOUD_TOKEN=...
    terraform init
    terraform apply
    

    Log in via ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null [email protected] with the printed IP address.

    When you make a change to machine-mynode.yaml.tmpl and run terraform apply again, the machine will be replaced.