Skip to content

Commit

Permalink
feat(hetzner): build servers
Browse files Browse the repository at this point in the history
  • Loading branch information
mrsimonemms committed Aug 3, 2024
1 parent af6e022 commit 94d354b
Show file tree
Hide file tree
Showing 9 changed files with 397 additions and 13 deletions.
24 changes: 22 additions & 2 deletions modules/hetzner/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,43 @@ No modules.
| Name | Type |
|------|------|
| [hcloud_firewall.firewall](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/firewall) | resource |
| [hcloud_load_balancer.k3s_manager](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/load_balancer) | resource |
| [hcloud_load_balancer_network.k3s_manager](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/load_balancer_network) | resource |
| [hcloud_load_balancer_service.k3s_manager](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/load_balancer_service) | resource |
| [hcloud_load_balancer_target.k3s_manager](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/load_balancer_target) | resource |
| [hcloud_network.network](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/network) | resource |
| [hcloud_network_subnet.subnet](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/network_subnet) | resource |
| [hcloud_placement_group.managers](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/placement_group) | resource |
| [hcloud_placement_group.workers](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/placement_group) | resource |
| [hcloud_server.manager](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/server) | resource |
| [hcloud_server.workers](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/server) | resource |
| [hcloud_ssh_key.server](https://registry.terraform.io/providers/hetznercloud/hcloud/latest/docs/resources/ssh_key) | resource |

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_firewall_allow_api_access"></a> [firewall\_allow\_api\_access](#input\_firewall\_allow\_api\_access) | CIDR range to allow access to the Kubernetes API | `list(string)` | <pre>[<br> "0.0.0.0/0",<br> "::/0"<br>]</pre> | no |
| <a name="input_firewall_allow_ssh_access"></a> [firewall\_allow\_ssh\_access](#input\_firewall\_allow\_ssh\_access) | CIDR range to allow access to the servers via SSH | `list(string)` | <pre>[<br> "0.0.0.0/0",<br> "::/0"<br>]</pre> | no |
| <a name="input_name"></a> [name](#input\_name) | Name of project | `string` | `"infrastructure"` | no |
| <a name="input_k3s_manager_load_balancer_algorithm"></a> [k3s\_manager\_load\_balancer\_algorithm](#input\_k3s\_manager\_load\_balancer\_algorithm) | Algorithm to use for the k3s manager load balancer | `string` | `"round_robin"` | no |
| <a name="input_k3s_manager_load_balancer_type"></a> [k3s\_manager\_load\_balancer\_type](#input\_k3s\_manager\_load\_balancer\_type) | Load balancer type for the k3s manager nodes | `string` | `"lb11"` | no |
| <a name="input_k3s_manager_pool"></a> [k3s\_manager\_pool](#input\_k3s\_manager\_pool) | Manager pool configuration | <pre>object({<br> name = optional(string, "manager")<br> server_type = optional(string, "cx22")<br> count = optional(number, 1)<br> image = optional(string, "ubuntu-24.04")<br> })</pre> | `{}` | no |
| <a name="input_k3s_worker_pools"></a> [k3s\_worker\_pools](#input\_k3s\_worker\_pools) | Worker pools configuration | <pre>list(object({<br> name = string<br> server_type = optional(string, "cx22")<br> count = optional(number, 1)<br> image = optional(string, "ubuntu-24.04")<br> location = optional(string) # Defaults to var.location if not set<br> }))</pre> | `[]` | no |
| <a name="input_location"></a> [location](#input\_location) | Location to use. This is a single datacentre. | `string` | `"nbg1"` | no |
| <a name="input_name"></a> [name](#input\_name) | Name of project | `string` | `"k3s"` | no |
| <a name="input_network_subnet"></a> [network\_subnet](#input\_network\_subnet) | Subnet of the main network | `string` | `"10.0.0.0/16"` | no |
| <a name="input_network_type"></a> [network\_type](#input\_network\_type) | Type of network to use | `string` | `"cloud"` | no |
| <a name="input_region"></a> [region](#input\_region) | Region to use. This covers multiple datacentres. | `string` | `"eu-central"` | no |
| <a name="input_ssh_key_public"></a> [ssh\_key\_public](#input\_ssh\_key\_public) | Path to the public SSH key | `string` | `"~/.ssh/id_ed25519.pub"` | no |
| <a name="input_ssh_port"></a> [ssh\_port](#input\_ssh\_port) | Port to use for SSH access | `number` | `22` | no |
| <a name="input_workspace"></a> [workspace](#input\_workspace) | Terraform workspace name | `string` | `"default"` | no |

## Outputs

No outputs.
| Name | Description |
|------|-------------|
| <a name="output_kube_api_server"></a> [kube\_api\_server](#output\_kube\_api\_server) | Kubernetes API server address |
| <a name="output_pools"></a> [pools](#output\_pools) | Servers created |
| <a name="output_ssh_port"></a> [ssh\_port](#output\_ssh\_port) | SSH port for server |
| <a name="output_ssh_user"></a> [ssh\_user](#output\_ssh\_user) | SSH user for server |
<!-- END_TF_DOCS -->
32 changes: 32 additions & 0 deletions modules/hetzner/files/cloud-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#cloud-config

package_reboot_if_required: true
package_update: true
package_upgrade: true
packages:
- curl
- yq
runcmd:
- [service, sshd, restart]
- [rm, -f, /root/.ssh/authorized_keys]
- chown ${user}:${user} "/home/${user}"
timezone: UTC
users:
- default
- name: "${user}"
gecos: "${user}"
sudo: ALL=(ALL) NOPASSWD:ALL
lock_passwd: true
shell: /bin/bash
ssh_authorized_keys:
- "${chomp(publicKey)}"
write_files:
- path: /etc/ssh/sshd_config.d/ssh.conf
content: |
PasswordAuthentication no
PermitRootLogin no
Port ${sshPort}
- path: /etc/environment
content: |
KUBECONFIG="/etc/rancher/k3s/k3s.yaml"
append: true
60 changes: 60 additions & 0 deletions modules/hetzner/load_balancer.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Copyright 2024 Simon Emms <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

resource "hcloud_load_balancer" "k3s_manager" {
count = var.k3s_manager_pool.count > 1 ? 1 : 0

name = format(local.name_format, "load_balancer")
load_balancer_type = var.k3s_manager_load_balancer_type
network_zone = var.region

algorithm {
type = var.k3s_manager_load_balancer_algorithm
}

labels = merge(local.labels, {})
}

resource "hcloud_load_balancer_network" "k3s_manager" {
count = var.k3s_manager_pool.count > 1 ? 1 : 0

load_balancer_id = hcloud_load_balancer.k3s_manager[count.index].id
network_id = hcloud_network.network.id

depends_on = [
hcloud_network_subnet.subnet
]
}

resource "hcloud_load_balancer_service" "k3s_manager" {
count = var.k3s_manager_pool.count > 1 ? 1 : 0

load_balancer_id = hcloud_load_balancer.k3s_manager[count.index].id
protocol = "tcp"
listen_port = local.kubernetes_api_port
destination_port = local.kubernetes_api_port
}

resource "hcloud_load_balancer_target" "k3s_manager" {
count = var.k3s_manager_pool.count > 1 ? 1 : 0

load_balancer_id = hcloud_load_balancer.k3s_manager[count.index].id
type = "label_selector"
label_selector = join(",", [for key, value in local.k3s_manager_labels : "${key}=${value}"])
use_private_ip = true

depends_on = [
hcloud_load_balancer_network.k3s_manager
]
}
32 changes: 29 additions & 3 deletions modules/hetzner/locals.tf
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,43 @@
locals {
global_ipv4_cidr = "0.0.0.0/0"
global_ipv6_cidr = "::/0"
k3s_manager_labels = merge(local.labels, {
format(local.label_namespace, "type") = "manager"
})
k3s_worker_labels = merge(local.labels, {
format(local.label_namespace, "type") = "worker"
})
# Convert pools into individual servers
k3s_worker_pools = flatten([
for w in var.k3s_worker_pools : [
for n in range(w.count) :
merge(
w,
{
location = w.location != null ? w.location : var.location
name = "${w.name}-${n}"
pool = w.name
}
)
]
])
kubernetes_api_port = 6443
labels = {
format(local.label_namespace, "project") = var.name
format(local.label_namespace, "workspace") = local.workspace_name
}
kubernetes_api_port = 6443
label_namespace = "simonemms.com/%s"
label_namespace = "simonemms.com/%s"
name_format = join("-", [
"hetzner",
var.name,
"%s", # resource name
local.workspace_name
]) # use `format(local.name_format, "<name>")` to use this
]) # use `format(local.name_format, "<name>")` to use this
ssh_user = "k3smanager"
user_data = templatefile("${path.module}/files/cloud-config.yaml", {
sshPort = var.ssh_port
publicKey = hcloud_ssh_key.server.public_key
user = local.ssh_user
})
workspace_name = replace(var.workspace, "/[\\W]/", "") # alphanumeric workspace name
}
16 changes: 14 additions & 2 deletions modules/hetzner/networks.tf
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,21 @@ resource "hcloud_firewall" "firewall" {
name = format(local.name_format, "firewall")

dynamic "rule" {
for_each = [
for_each = [for each in [
{
description = "SSH port"
port = var.ssh_port
source_ips = var.firewall_allow_ssh_access
},
{
description = "Allow ICMP (ping)"
source_ips = [
local.global_ipv4_cidr,
local.global_ipv6_cidr,
]
protocol = "icmp"
port = null
},
{
description = "Allow all TCP traffic on private network"
source_ips = [
Expand All @@ -49,12 +58,15 @@ resource "hcloud_firewall" "firewall" {
]
protocol = "udp"
},
# Direct public access only allowed if single manager node
{
description = "Allow access to Kubernetes API"
port = local.kubernetes_api_port
source_ips = var.firewall_allow_api_access
disabled = var.k3s_manager_pool.count > 1
}
]
] : each if lookup(each, "disabled", false) != true]

content {
description = lookup(rule.value, "description", "")
destination_ips = lookup(rule.value, "destination_ips", [])
Expand Down
50 changes: 50 additions & 0 deletions modules/hetzner/output.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2024 Simon Emms <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

output "kube_api_server" {
description = "Kubernetes API server address"
value = var.k3s_manager_pool.count > 1 ? hcloud_load_balancer.k3s_manager[0].ipv4 : hcloud_server.manager[0].ipv4_address
}

output "pools" {
description = "Servers created"
value = merge(
{
managers : [
for m in hcloud_server.manager : {
name = m.name
ipv4_address = m.ipv4_address
ipv6_address = m.ipv6_address
}
]
},
{
for w in local.k3s_worker_pools : w.pool => {
name = hcloud_server.workers[w.name].name
ipv4_address = hcloud_server.workers[w.name].ipv4_address
ipv6_address = hcloud_server.workers[w.name].ipv6_address
}...
}
)
}

output "ssh_port" {
description = "SSH port for server"
value = var.ssh_port
}

output "ssh_user" {
description = "SSH user for server"
value = local.ssh_user
}
Loading

0 comments on commit 94d354b

Please sign in to comment.