Working with Terraform

Figure 1041. 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 1043. Creating databases Slide presentation
Declarative schema Physical result
CREATE SCHEMA ...
  CREATE TABLE ...

  CREATE VIEW ...

...
  • Database implementation using files,or (raw) devices.

  • Creating Index / search trees.


Figure 1044. Creating cloud resources Slide presentation
Declarative schema Physical result
resource "hcloud_server" "wwwServer" {
  name         = "hello"
  image        =  "debian-13"
  server_type  =  "cx23"
}
Creating IPs, networks, servers, message queues, ...

Figure 1045. Your course documentation: Code structure Slide presentation
...
├── env.template
├── 030HelloSshPublicKey
│   ├── main.tf
│   ├── providers.tf
│   ├── Readme.md
│   └── variables.tf
└── Modules
    ├── HostMetaData
        ...
    └── SshKnownHosts
         ...

Figure 1046. The env.template file Slide presentation
export TF_VAR_login_user=devops
export TF_VAR_ssh_login_public_key=add_your_provider_token
export TF_VAR_hcloud_token=add_your_provider_token
export TF_VAR_dns_secret=add_your_ds_server_secret

To be copied, edited and used by Terraform.


Figure 1047. Getting started: The Hetzner API token Slide presentation
  • Access you cloud project by 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 subsequent read access after creation.

    In case of loss: Delete the existing and generate a new one.


Figure 1048. 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-13"
  server_type  =  "cx23"   
}

Figure 1049. 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 1050. 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 1051. 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 1052. A word on storing secrets Slide presentation

Guide to Managing Terraform secrets.

variables.tf env providers.tf
variable "hcloud_token" {
  nullable = false
  sensitive = true
}
export TF_VAR_hcloud_token=mBSxD...
provider "hcloud" {
  token = var.hcloud_token
}
. env  # sourcing environment

terraform apply

Figure 1053. 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 1054. 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 1055. 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 1056. 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 1057. 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 1058. 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_location" {
  value       = hcloud_server.helloServer.location
  description = "The server's datacenter location"
}
$ terraform output
hello_location = "nbg1"
hello_ip_addr = "159.69.152.37"
$ terraform output hello_ip_addr
"159.69.152.37"

Figure 1059. 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_location" {
  value       = hcloud_server.helloServer.location
  description = "The server's datacenter location"
}
{
  "hello_location": {
    "sensitive": false,
    "type": "string",
    "value": "nbg1"
  },
  "hello_ip_addr": {
    "sensitive": false,
    "type": "string",
    "value": "159.69.152.37"
  }
}

Figure 1060. Problem 2: VCS and visible secrets 😱 Slide presentation

For security reasons, secrets should not be under version control:

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

Figure 1061. Addressing secrets 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, added to .gitignore:

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 1062. Addressing secrets by file Slide presentation
# Configure the Hetzner Cloud API token
provider "hcloud" {
  token = file("../providertoken.key")
}

Content of file providertoken.key, added to .gitignore:

xdaGfz9LmwO8SWkg ...

Content of file providertoken.key.template:

your_api_token_goes_here

Figure 1063. Addressing secrets by Bash .env file Slide presentation
  • Again declare hcloud_token in variables.tf.

  • Add a dot.env.template file to version control:

    export TF_VAR_hcloud_token="Your token goes here"
  • Copy dot.env.template to .env, supply secret and add it to your .gitignore file.

  • Source the .env file, e.g. in a Bash shell execute:

    . .env

    Test it:

    $ . ./env
    $ echo $TF_VAR_hcloud_token
    gTwn5...

exercise No. 13

Incrementally creating a base system

Q:

Follow the subsequent steps creating basic server based on Terraform:

  1. Start from Figure 1048, “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 1062, “Addressing secrets 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_location = "hel1"
    hello_ip_addr = "95.217.154.104"