Add initial Terraform infrastructure for Hetzner test environment
This commit introduces the foundational Infrastructure-as-Code for provisioning a test environment on Hetzner Cloud. It defines server nodes, private networking, comprehensive firewalls, and includes documentation on resource lifecycle and safe configuration practices.
This commit is contained in:
parent
81c38e8d39
commit
2d515f7206
@ -108,6 +108,46 @@ output "test_public_ips" {
|
|||||||
|
|
||||||
Inventory output'u daha sonra `ansible/inventory/generated/test.yml` dosyasina yazilabilir. Inventory dosyasinda secret bulunmayacaksa commit edilebilir; secret veya token icerirse commit edilmeyecek.
|
Inventory output'u daha sonra `ansible/inventory/generated/test.yml` dosyasina yazilabilir. Inventory dosyasinda secret bulunmayacaksa commit edilebilir; secret veya token icerirse commit edilmeyecek.
|
||||||
|
|
||||||
|
## Lifecycle ve Resize Politikasi
|
||||||
|
|
||||||
|
### server_type Degisikligi (Yeniden Boyutlandirma)
|
||||||
|
|
||||||
|
`server_type` degistirmek Terraform destroy+create **tetiklemez**. `hcloud` provider
|
||||||
|
bunu natively destekler: sunucuyu durdurur, Hetzner Resize API'sini cagirir,
|
||||||
|
yeniden baslatir. `terraform.tfvars` icinde degeri guncelle, `terraform apply` calistir.
|
||||||
|
|
||||||
|
Downtime olur (sunucu durur ve baslar) ancak disk, kurulu yazilim ve Docker volumes
|
||||||
|
korunur. `ignore_changes` veya manuel adim gerekmez.
|
||||||
|
|
||||||
|
### Hangi Degisiklikler Sunucuyu Zorla Yeniden Olusturur?
|
||||||
|
|
||||||
|
| Degisen alan | Davranis | Not |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `server_type` | In-place resize (provider native) | `terraform apply` yeterli |
|
||||||
|
| `hcloud_server_network` | Sadece attachment guncellenir | Ayri resource kullanildigi icin |
|
||||||
|
| `hcloud_firewall_attachment` | Sadece attachment guncellenir | Ayri resource kullanildigi icin |
|
||||||
|
| `placement_group_id` | Hetzner API degisime izin vermiyor → destroy+create | Degistirme |
|
||||||
|
| `image` | Disk imaji degisir → destroy+create | Degistirme |
|
||||||
|
| `location` | Baska datacenter'a tasinamaz → destroy+create | Degistirme |
|
||||||
|
|
||||||
|
### Network ve Firewall Attachment Ayrimi
|
||||||
|
|
||||||
|
`network` blogu ve `firewall_ids` `hcloud_server` icine gomulmez. Bunun yerine
|
||||||
|
ayri resource tanimlanir:
|
||||||
|
|
||||||
|
- `hcloud_server_network` — private IP atamasi
|
||||||
|
- `hcloud_firewall_attachment` — firewall iliskisi
|
||||||
|
|
||||||
|
Gomulu tanimlamada bazi provider versiyonlari bu alanlardaki degisiklikleri
|
||||||
|
sunucu recreation olarak yorumlar. Ayri resource kullanildiginda sadece
|
||||||
|
attachment guncellenir, sunucu dokunulmaz.
|
||||||
|
|
||||||
|
### prevent_destroy Korumasi
|
||||||
|
|
||||||
|
Her sunucuya `lifecycle { prevent_destroy = true }` eklenir. Bu blok varken
|
||||||
|
Terraform hicbir kosulda sunucuyu silemez, plan asamasinda hata verir.
|
||||||
|
Kasitli silmek icin once lifecycle blogunu gecici olarak kaldir.
|
||||||
|
|
||||||
## Kabul Kriterleri
|
## Kabul Kriterleri
|
||||||
|
|
||||||
- `terraform plan` sadece test Hetzner Project token'i ile calisir.
|
- `terraform plan` sadece test Hetzner Project token'i ile calisir.
|
||||||
|
|||||||
171
terraform/hetzner/test/firewall.tf
Normal file
171
terraform/hetzner/test/firewall.tf
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# Swarm node firewall — public HTTP/HTTPS + private infra services
|
||||||
|
resource "hcloud_firewall" "swarm" {
|
||||||
|
name = "${local.name_prefix}-firewall-swarm"
|
||||||
|
|
||||||
|
# SSH — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "22"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTP public
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "80"
|
||||||
|
source_ips = ["0.0.0.0/0", "::/0"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS public
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "443"
|
||||||
|
source_ips = ["0.0.0.0/0", "::/0"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm control plane
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "2377"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm node discovery (TCP)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "7946"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm node discovery (UDP)
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "udp"
|
||||||
|
port = "7946"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Docker Swarm VXLAN overlay
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "udp"
|
||||||
|
port = "4789"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Vault API — private only, never public
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "8200"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Redis
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "6379"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ AMQP
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "5672"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ STOMP
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "61613"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ Web STOMP
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "15674"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# RabbitMQ Management — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "15672"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# APISIX Admin API — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "9180"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# Prometheus — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "9090"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grafana — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "3000"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = var.environment
|
||||||
|
role = "swarm"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# DB node firewall — SSH + DB ports from app/swarm subnet only
|
||||||
|
resource "hcloud_firewall" "db" {
|
||||||
|
name = "${local.name_prefix}-firewall-db"
|
||||||
|
|
||||||
|
# SSH — admin CIDRs only
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "22"
|
||||||
|
source_ips = var.admin_allowed_cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
# PostgreSQL from app/swarm subnet
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "5432"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
# MongoDB from app/swarm subnet
|
||||||
|
rule {
|
||||||
|
direction = "in"
|
||||||
|
protocol = "tcp"
|
||||||
|
port = "27017"
|
||||||
|
source_ips = [local.app_subnet_cidr]
|
||||||
|
}
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = var.environment
|
||||||
|
role = "db"
|
||||||
|
}
|
||||||
|
}
|
||||||
10
terraform/hetzner/test/locals.tf
Normal file
10
terraform/hetzner/test/locals.tf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
locals {
|
||||||
|
name_prefix = "iklim-${var.environment}"
|
||||||
|
|
||||||
|
swarm_private_ip = "10.10.10.11"
|
||||||
|
db_private_ip = "10.10.20.11"
|
||||||
|
|
||||||
|
network_cidr = "10.10.0.0/16"
|
||||||
|
app_subnet_cidr = "10.10.10.0/24"
|
||||||
|
db_subnet_cidr = "10.10.20.0/24"
|
||||||
|
}
|
||||||
22
terraform/hetzner/test/network.tf
Normal file
22
terraform/hetzner/test/network.tf
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
resource "hcloud_network" "main" {
|
||||||
|
name = "${local.name_prefix}-net"
|
||||||
|
ip_range = local.network_cidr
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = var.environment
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_network_subnet" "app" {
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
type = "cloud"
|
||||||
|
network_zone = "eu-central"
|
||||||
|
ip_range = local.app_subnet_cidr
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_network_subnet" "db" {
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
type = "cloud"
|
||||||
|
network_zone = "eu-central"
|
||||||
|
ip_range = local.db_subnet_cidr
|
||||||
|
}
|
||||||
46
terraform/hetzner/test/outputs.tf
Normal file
46
terraform/hetzner/test/outputs.tf
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
output "ansible_inventory_yaml" {
|
||||||
|
description = "Ansible inventory in YAML format — write to ansible/inventory/generated/test.yml"
|
||||||
|
sensitive = false
|
||||||
|
value = yamlencode({
|
||||||
|
all = {
|
||||||
|
children = {
|
||||||
|
swarm = {
|
||||||
|
hosts = {
|
||||||
|
(hcloud_server.swarm.name) = {
|
||||||
|
ansible_host = hcloud_server.swarm.ipv4_address
|
||||||
|
private_ip = local.swarm_private_ip
|
||||||
|
ansible_user = "root"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db = {
|
||||||
|
hosts = {
|
||||||
|
(hcloud_server.db.name) = {
|
||||||
|
ansible_host = hcloud_server.db.ipv4_address
|
||||||
|
private_ip = local.db_private_ip
|
||||||
|
ansible_user = "root"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
output "test_private_ips" {
|
||||||
|
description = "Private IPs assigned to test nodes"
|
||||||
|
sensitive = false
|
||||||
|
value = {
|
||||||
|
swarm_01 = local.swarm_private_ip
|
||||||
|
db_01 = local.db_private_ip
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "test_public_ips" {
|
||||||
|
description = "Public IPv4 addresses of test nodes"
|
||||||
|
sensitive = false
|
||||||
|
value = {
|
||||||
|
swarm_01 = hcloud_server.swarm.ipv4_address
|
||||||
|
db_01 = hcloud_server.db.ipv4_address
|
||||||
|
}
|
||||||
|
}
|
||||||
8
terraform/hetzner/test/placement.tf
Normal file
8
terraform/hetzner/test/placement.tf
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
resource "hcloud_placement_group" "test_spread" {
|
||||||
|
name = "${local.name_prefix}-spread"
|
||||||
|
type = "spread"
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = var.environment
|
||||||
|
}
|
||||||
|
}
|
||||||
3
terraform/hetzner/test/providers.tf
Normal file
3
terraform/hetzner/test/providers.tf
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
provider "hcloud" {
|
||||||
|
token = var.hcloud_token
|
||||||
|
}
|
||||||
71
terraform/hetzner/test/servers.tf
Normal file
71
terraform/hetzner/test/servers.tf
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
resource "hcloud_ssh_key" "admin" {
|
||||||
|
name = "${local.name_prefix}-admin-key"
|
||||||
|
public_key = file(var.admin_ssh_public_key_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server" "swarm" {
|
||||||
|
name = "${var.environment}-swarm-01"
|
||||||
|
server_type = var.server_type_swarm
|
||||||
|
image = var.image
|
||||||
|
location = var.location
|
||||||
|
ssh_keys = [hcloud_ssh_key.admin.id]
|
||||||
|
placement_group_id = hcloud_placement_group.test_spread.id
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = var.environment
|
||||||
|
role = "swarm"
|
||||||
|
type = "service"
|
||||||
|
}
|
||||||
|
|
||||||
|
# prevent_destroy: Terraform'un sunucuyu kazara silmesini engeller.
|
||||||
|
# Kasitli silmek icin once bu bloku kaldir.
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server" "db" {
|
||||||
|
name = "${var.environment}-db-01"
|
||||||
|
server_type = var.server_type_db
|
||||||
|
image = var.image
|
||||||
|
location = var.location
|
||||||
|
ssh_keys = [hcloud_ssh_key.admin.id]
|
||||||
|
placement_group_id = hcloud_placement_group.test_spread.id
|
||||||
|
|
||||||
|
labels = {
|
||||||
|
environment = var.environment
|
||||||
|
role = "db"
|
||||||
|
type = "db"
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycle {
|
||||||
|
prevent_destroy = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ayri resource: firewall veya network degistiginde sunucu recreation tetiklenmez.
|
||||||
|
resource "hcloud_server_network" "swarm" {
|
||||||
|
server_id = hcloud_server.swarm.id
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
ip = local.swarm_private_ip
|
||||||
|
|
||||||
|
depends_on = [hcloud_network_subnet.app]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_server_network" "db" {
|
||||||
|
server_id = hcloud_server.db.id
|
||||||
|
network_id = hcloud_network.main.id
|
||||||
|
ip = local.db_private_ip
|
||||||
|
|
||||||
|
depends_on = [hcloud_network_subnet.db]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_firewall_attachment" "swarm" {
|
||||||
|
firewall_id = hcloud_firewall.swarm.id
|
||||||
|
server_ids = [hcloud_server.swarm.id]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "hcloud_firewall_attachment" "db" {
|
||||||
|
firewall_id = hcloud_firewall.db.id
|
||||||
|
server_ids = [hcloud_server.db.id]
|
||||||
|
}
|
||||||
8
terraform/hetzner/test/terraform.tfvars.example
Normal file
8
terraform/hetzner/test/terraform.tfvars.example
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
hcloud_token = "YOUR_HETZNER_TEST_PROJECT_API_TOKEN"
|
||||||
|
environment = "test"
|
||||||
|
location = "fsn1"
|
||||||
|
image = "ubuntu-24.04"
|
||||||
|
server_type_swarm = "cx32"
|
||||||
|
server_type_db = "cx42"
|
||||||
|
admin_ssh_public_key_path = "~/.ssh/id_ed25519.pub"
|
||||||
|
admin_allowed_cidrs = ["X.X.X.X/32"]
|
||||||
46
terraform/hetzner/test/variables.tf
Normal file
46
terraform/hetzner/test/variables.tf
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
variable "hcloud_token" {
|
||||||
|
type = string
|
||||||
|
sensitive = true
|
||||||
|
description = "Hetzner Cloud API token for the test project"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "environment" {
|
||||||
|
type = string
|
||||||
|
default = "test"
|
||||||
|
description = "Environment name prefix for all resources"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "location" {
|
||||||
|
type = string
|
||||||
|
default = "fsn1"
|
||||||
|
description = "Hetzner Cloud datacenter location"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image" {
|
||||||
|
type = string
|
||||||
|
default = "ubuntu-24.04"
|
||||||
|
description = "Server image"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "server_type_swarm" {
|
||||||
|
type = string
|
||||||
|
default = "cx32"
|
||||||
|
description = "Hetzner server type for the Swarm node"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "server_type_db" {
|
||||||
|
type = string
|
||||||
|
default = "cx42"
|
||||||
|
description = "Hetzner server type for the DB node"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin_ssh_public_key_path" {
|
||||||
|
type = string
|
||||||
|
default = "~/.ssh/id_ed25519.pub"
|
||||||
|
description = "Path to the admin SSH public key file"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admin_allowed_cidrs" {
|
||||||
|
type = list(string)
|
||||||
|
description = "CIDR list for admin SSH and management port access"
|
||||||
|
}
|
||||||
10
terraform/hetzner/test/versions.tf
Normal file
10
terraform/hetzner/test/versions.tf
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
terraform {
|
||||||
|
required_version = ">= 1.6"
|
||||||
|
|
||||||
|
required_providers {
|
||||||
|
hcloud = {
|
||||||
|
source = "hetznercloud/hcloud"
|
||||||
|
version = "~> 1.49"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user