Introduction
Not every application infrastructure requires a full blown GCP, AWS or Azure Kubernetes / Mesos apparatus. Docker Swarm (Mode) offers a comparable and simpler alternative to Kubernetes / Mesos. Swarm mode allows provisioning a Docker cluster starting with a single server. This tutorial demonstrates how you can leverage Terraform with Hetzner’s cost effective cloud platform to quickly deploy a Traefik (v2.3) reverse proxy / loadbalancer container to serve applications all on a single Docker Swarm host.
Network Layout - Docker Swarm Host + Traefik + Services.
This deployment utilises three pre-built Terraform modules.
- (docker-host) for provisioning the Docker Swarm Host & Cloud Volume on Hetzner
- (traefik-v2) for provisioning a Traefik container on the Docker host.
- (vault-dev) for provisioning a Vault container on the Docker host as an example app.
The docker-host module provisions a single Docker Swarm Host on Hetzner Cloud. A Cloud Volume is also provisioned, attached and mounted on the Docker Host for use as the Docker root directory where persisted data such as images and volumes are stored. The modules default variables provision a 2GB RAM / 1 vCPU cx11
server type @ € 2.49* monthly and a 10GB
cloud volume @ € 0.40* monthly (see Hetzner’s website for current pricing)
For routing HTTP/TCP/UDP requests and certificate management via Let’s Encrypt the traefik-v2 module deploys a Traefik v2 reverse proxy container on the pre-provisioned Docker Host.
Finally the vault-dev module deploys a HashiCorp Vault container on the Docker Host as the example application.
You’re welcome to fork and tweak any of the above modules on GitHub to suit your needs.
Prerequisites
- Hetzner Cloud Account & API Key (with Read/Write permissions)
- SSH Key pair
- Terraform ≥ v0.13
- Knowledge of Docker & Terraform
Step 1 - Create the Terraform Configuration
Clone the example repository https://github.com/colinwilson/example-terraform-modules/tree/docker-host
git clone -b docker-host https://github.com/colinwilson/example-terraform-modules
example-terraform-modules/
|-- .gitignore
|-- README.md
|-- main.tf
|-- outputs.tf
|-- terraform.tfvars
`-- variables.tf
Alternatively, manually create the above file structure by copying the following code snippets. (click to expand)
Copy and paste the following code to your main.tf
file:
# main.tf
terraform {
required_providers {
hcloud = {
source = "hetznercloud/hcloud"
}
}
}
provider "hcloud" {
token = var.hcloud_token
}
module "hcloud-docker-host" {
source = "github.com/colinwilson/terraform-hcloud-docker-host"
ssh_public_key = var.ssh_public_key
//server = var.server
//ssh_public_key_name = var.ssh_public_key_name
//volume_size = var.volume_size
//volume_filesystem = var.volume_filesystem
}
server
,ssh_public_key_name
,volume_size
andvolume_filesystem
are all optional input variables.
Copy and paste the following code to your outputs.tf
file:
# outputs.tf
output "server_ip" {
description = "Docker Host IP address"
value = module.hcloud-docker-host.ipv4_address
}
output "volume_size" {
description = "Size of provisioned Cloud Volume"
value = module.hcloud-docker-host.volume_size
}
output "volume_mount_point" {
description = "Mountpoint of provisioned Cloud Volume"
value = module.hcloud-docker-host.volume_mount_point
}
Copy and paste the following code to your variables.tf
file:
# variables.tf
variable "hcloud_token" {
description = "Hetzner Cloud API Token"
type = string
}
variable "ssh_public_key" {
description = "SSH Public Key"
type = string
}
//variable "ssh_public_key_name" {}
//variable "server" {}
//variable "volume_size" {}
//variable "volume_filesystem" {}
Step 2 - Set Values for the Required Input Variables
Following the example below add both the Hetzner Cloud project API Token generated earlier and your SSH Public Key as input variables to your terraform.tfvars
file:
# terraform.tfvars (example)
hcloud_token = "l113WfwCFwZCbVcxVsQHHuMAJINQV6K8hhyVjzOMymotxb2z1oBh6ANwheFvV2lF"
ssh_public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJNcwP5mhs5/F2T9GFHmg4z6E6sbOG+Ynx2iPERKeOGm"
Step 3 - Provision the Docker Host
Switch to your example-terraform-modules
directory and initialize your configuration by running terraform init
.
terraform init
Terraform will proceed to download the required provider plugins.
Initializing modules...
- hcloud-docker-host in ..\terraform-hcloud-docker-host
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hetznercloud/hcloud...
- Installing hetznercloud/hcloud v1.23.0...
- Installed hetznercloud/hcloud v1.23.0 (signed by a HashiCorp partner, key ID 5219EACB3A77198B)
# ...
Now run terraform apply
to create your Docker (Swarm) Host.
terraform apply
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.hcloud-docker-host.hcloud_server.server will be created
+ resource "hcloud_server" "server" {
+ backup_window = (known after apply)
+ backups = false
+ datacenter = (known after apply)
+ id = (known after apply)
+ image = "ubuntu-20.04"
+ ipv4_address = (known after apply)
+ ipv6_address = (known after apply)
+ ipv6_network = (known after apply)
+ keep_disk = false
+ location = "nbg1"
+ name = "docker-host"
+ server_type = "cx11"
+ ssh_keys = [
+ "default",
]
+ status = (known after apply)
+ user_data = "BfHwIc7NggsC/JJGTzbYp+r+Au0="
}
# module.hcloud-docker-host.hcloud_ssh_key.default will be created
+ resource "hcloud_ssh_key" "default" {
+ fingerprint = (known after apply)
+ id = (known after apply)
+ name = "default"
+ public_key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJNcwP5mhs5/F2T9GFHmg4z6E6sbOG+Ynx2iPERKeOGm"
}
# module.hcloud-docker-host.hcloud_volume.master will be created
+ resource "hcloud_volume" "master" {
+ automount = true
+ format = "ext4"
+ id = (known after apply)
+ linux_device = (known after apply)
+ location = (known after apply)
+ name = "docker_data_volume"
+ server_id = (known after apply)
+ size = 10
}
Plan: 3 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:
Respond to the prompt with yes
to apply the changes and continue.
Terraform will now create the Docker Host and you should see the Docker Host IP address in the configuration output along with some additional volume configuration info.
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
server_ip = 116.207.49.143
volume_mount_point = /dev/disk/by-id/scsi-0HC_Volume_8179379
volume_size = 10
Step 4 - Check Docker’s Persistent Data is Stored on the Cloud Volume
To ensure Docker data such as volumes and images are indeed stored on the mounted Hetzner Cloud Volume, login via SSH, create a test volume and inspect it.
Once logged in check the provisioned cloud volume (/dev/sdb
) exists and is mounted to the (/mnt/docker_data_volume
) directory.
root@docker-host:~# df -h
Filesystem Size Used Avail Use% Mounted on
udev 953M 0 953M 0% /dev
tmpfs 194M 728K 194M 1% /run
/dev/sda1 19G 2.4G 16G 14% /
tmpfs 970M 0 970M 0% /dev/shm
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 970M 0 970M 0% /sys/fs/cgroup
/dev/sda15 61M 2.7M 58M 5% /boot/efi
/dev/sdb 9.8G 37M 9.3G 1% /mnt/docker_data_volume
tmpfs 194M 0 194M 0% /run/user/0
Create a test Docker volume.
root@docker-host:~# docker volume create test_volume
test_volume
Now inspect the volume using the following command.
root@docker-host:~# docker volume inspect test_volume
The Mountpoint
key value should indicate that test_volume
is located inside the directory where the cloud volume is mounted.
[
{
"CreatedAt": "2020-11-25T02:59:28+01:00",
"Driver": "local",
"Labels": {},
"Mountpoint": "/mnt/docker_data_volume/volumes/test_volume/_data",
"Name": "test_volume",
"Options": {},
"Scope": "local"
}
]
Step 5. Clean up
You can destroy the host by running terraform destroy
. After you respond to the prompt with yes
, Terraform will destroy the Docker host you created.
terraform destroy
module.hcloud-docker-host.hcloud_volume.master: Refreshing state... [id=8257019]
module.hcloud-docker-host.hcloud_ssh_key.default: Refreshing state... [id=2447474]
module.hcloud-docker-host.hcloud_server.server: Refreshing state... [id=8808780]
module.hcloud-docker-host.hcloud_volume_attachment.main: Refreshing state... [id=8257019]
module.hcloud-docker-host.hcloud_volume_attachment.main: Destroying... [id=8257019]
module.hcloud-docker-host.hcloud_ssh_key.default: Destroying... [id=2447474]
# ...
Destroy complete! Resources: 4 destroyed.
Note: Failing to destroy the infrastructure you created during this tutorial could result in charges from Hetzner.
Conclusion
That’s it! You’ve now provisioned a Docker Swarm Host on the Hetzner Cloud Platform. This demonstrates how speedy provisioning of even simple infrastructure can be automated using Terraform.
Part Two will cover deploying the Traefik reverse proxy and a Vault container on your Docker host again using pre-built Terraform modules.
References
* Prices exclude VAT