Working with Terraform

Figure 974. What's it all about? Slide presentation

Quote:

Terraform is an infrastructure as code tool that lets you build, change, and version cloud and on-prem resources safely and efficiently.


Figure 975. Terraform resources Slide presentation

Figure 976. Hetzner API token Slide presentation
  • Access you cloud project using the Hetzner GUI interface.

  • Go to Security --> API Tokens --> Generate API token

  • Provide a name and hit Generate API token.

  • Copy the generated token's value and store it in a secure location e.g. a password manager.

    Caution

    The Hetzner GUI blocks future access to the token.


Figure 977. Minimal Terraform configuration Slide presentation
# Define Hetzner cloud provider
terraform {
  required_providers {
    hcloud = {
      source = "hetznercloud/hcloud"
    }
  }
  required_version = ">= 0.13"
}

# Configure the Hetzner Cloud API token
provider "hcloud" {
  token = "your_api_token_goes_here"
}

# Create a server
resource "hcloud_server" "helloServer" {
  name         = "hello"
  image        =  "debian-12"
  server_type  =  "CX22"   
}

Figure 978. Terraform init Slide presentation
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Finding latest version of hetznercloud/hcloud...
- Installing hetznercloud/hcloud v1.46.1...
- Installed hetznercloud/hcloud v1.46.1 (signed by a HashiCorp partner, key ID 5219EACB3A77198B)

...
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above.  ...

Figure 979. Terraform plan Slide presentation
$ terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions ...
  + create

Terraform will perform the following actions:

  # hcloud_server.helloServer will be created
  + resource "hcloud_server" "helloServer" {
      + allow_deprecated_images    = false
      + backup_window              = (known after apply)
  ...
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Figure 980. Terraform apply Slide presentation
$ terraform apply
...
Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

hcloud_server.helloServer: Creating...
hcloud_server.helloServer: Still creating... [10s elapsed]
hcloud_server.helloServer: Creation complete after 14s [id=45822789]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

Figure 981. Credentials by E-Mail Slide presentation
Your server "hello" was created!

You can access your server with the following credentials:
 
IPv4	128.140.108.60
IPv6	2a01:4f8:1c1c:8e3a::/64
User	root
Password	rJ3pNvJXbqMp3XNTvFdq

You will be prompted to change your password on your first login.

To improve security, we recommend that you add an SSH key when creating a server.

Figure 982. Problems: 😟 Slide presentation
  • Firewall blocks ssh server access:

    $ ssh root@128.140.108.60
    ssh: connect to host 128.140.108.60 port 22: Connection refused

    Access by Vnc console login only

  • IP and (initial) credentials by email 😱

Solution:

  1. Add firewall inbound ssh access rule.

  2. Configure ssh public key login.


Figure 983. ssh access, firewall Slide presentation
resource "hcloud_firewall" "sshFw" {
  name = "ssh-firewall"
  rule {
    direction = "in"
    protocol  = "tcp"
    port      = "22"
    source_ips = ["0.0.0.0/0", "::/0"]
  }
}
              ...
resource "hcloud_server" "helloServer" {
              ...
  firewall_ids = [hcloud_firewall.sshFw.id]
}

Figure 984. ssh access, public key Slide presentation
resource "hcloud_ssh_key" "loginUser" {
  name       = "goik@hdm-stuttgart.de"
  public_key = file("~/.ssh/id_ed25519.pub")
}
              ...
resource "hcloud_server" "helloServer" {
              ...
  ssh_keys     = [hcloud_ssh_key.loginUser.id]
}

Note: Use the Hetzner Web GUI for removing any conflicting manually installed ssh keys beforehand.


Figure 985. Apply ssh key access Slide presentation
$ terraform apply

  # hcloud_firewall.sshFw will be created
  + resource "hcloud_firewall" "sshFw" {
       ...
  # hcloud_server.helloServer will be created
  + resource "hcloud_server" "helloServer" {
       ...
  # hcloud_ssh_key.goik will be created
  + resource "hcloud_ssh_key" "loginUser" {
       ...
Plan: 3 to add, 0 to change, 0 to destroy.
       ...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Figure 986. Output data details #1/2 Slide presentation

See terraform output documentation:

File outputs.tf Result
output "hello_ip_addr" {
  value       = hcloud_server.helloServer.ipv4_address
  description = "The server's IPv4 address"
}

output "hello_datacenter" {
  value       = hcloud_server.helloServer.datacenter
  description = "The server's datacenter"
}
$ terraform output
hello_datacenter = "nbg1-dc3"
hello_ip_addr = "159.69.152.37"
$ terraform output hello_ip_addr
"159.69.152.37"

Figure 987. Output data details #2/2 Slide presentation
File outputs.tf terraform output -json
output "hello_ip_addr" {
  value       = hcloud_server.helloServer.ipv4_address
  description = "The server's IPv4 address"
}

output "hello_datacenter" {
  value       = hcloud_server.helloServer.datacenter
  description = "The server's datacenter"
}
{
  "hello_datacenter": {
    "sensitive": false,
    "type": "string",
    "value": "nbg1-dc3"
  },
  "hello_ip_addr": {
    "sensitive": false,
    "type": "string",
    "value": "159.69.152.37"
  }
}

Figure 988. Problem 2: VCS and visible provider API token 😱 Slide presentation

Versioned file main.tf:

...
provider "hcloud" { token = "xdaGfz9LmwO8SWkg ... "}
...

Solution:

  • Declare a variable hcloud_token in a variables.tf file

  • Add a non-versioned file secrets.auto.tfvars.

  • Optional: Provide a versioned secrets.auto.tfvars.template documenting file


Figure 989. Solution by variable Slide presentation

Declaring hcloud_token in variables.tf:

variable "hcloud_token" {  # See secret.auto.tfvars
  nullable = false
  sensitive = true
}

Defining hcloud_token's value in secrets.auto.tfvars:

hcloud_token="xdaGfz9LmwO8SWkg ... "

Using hcloud_token in main.tf:

provider "hcloud" { token = var.hcloud_token }

Template file secrets.auto.tfvars.template:

hcloud_token="your_api_token_goes_here"

Figure 990. Solution by file Slide presentation
# Configure the Hetzner Cloud API token
provider "hcloud" {
  token = file("../providertoken.key")
}

Content of file providertoken.key:

xdaGfz9LmwO8SWkg ...

Content of file providertoken.key.template:

your_api_token_goes_here

exercise No. 5

Incrementally creating a base system

Q:

Follow the subsequent steps creating basic server based on Terraform:

  1. Start from Figure 977, “Minimal Terraform configuration ” adding a ssh inbound firewall rule. Enter your Hetzner provider token and create the server.

    On success you'll receive an e-mail containing your server's IP address and the root user's password for ssh login. Why does this happen? Log in to your server.

  2. Subject your configuration to version control in a Git project. Putting the previous Terraform configuration under version control might expose your cloud provider's API token. Circumvent this problem by following the steps outlined in Figure 990, “Solution by file ”.

  3. Ditch unsafe (and tedious) ssh password login in favour of public/private key access.

    Tip

    Create a resource "hcloud_ssh_key" ... and read your hcloud_server documentation regarding ssh public key configuration.

    On success you should be able to log in using your ssh private key.

  4. Currently when executing the terraform apply command both your newly created server's IP and data center location are not being shown. Add an outputs.tf file containing two corresponding output entries.

    On success when executing terraform apply you should see something like:

    terraform apply
    ...
    hcloud_server.helloServer: Still creating... [10s elapsed]
    hcloud_server.helloServer: Creation complete after 14s [id=46961197]
    
    Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    hello_datacenter = "hel1-dc2"
    hello_ip_addr = "95.217.154.104"