构建我的个人网站:从想法到自动化部署(第2部分)

发布: (2025年12月4日 GMT+8 23:50)
7 min read
原文: Dev.to

Source: Dev.to

请提供您希望翻译的文章正文内容,我将按照要求保留链接、格式和代码块,仅翻译文本部分。

概览

在本系列的第一部分,我介绍了高层架构以及用于构建个人网站的工具选择。本篇文章将更深入地探讨技术实现,首先介绍部署最小化 Hetzner Cloud 基础设施所需的 Terraform 模块。

必需组件

  • Network – 用于内部通信的私有网络
  • Firewall – 限制流量的安全规则
  • SSH Key – 服务器访问的身份验证密钥
  • Server – 计算实例

每个组件都由其各自的 Terraform 模块进行管理。

网络

terraform-hcloud-network 模块提供了 Hetzner Cloud 的全面网络管理功能,包括:

  • 可选创建新网络
  • 支持多个子网
  • 自定义路由
  • 一致的输出

示例用法

module "network" {
  source  = "danylomikula/network/hcloud"
  version = "1.0.0"

  # 创建一个新网络
  create_network = true
  name           = local.project_slug
  ip_range       = "10.100.0.0/16"

  # 可选标签
  labels = local.common_labels

  # 定义子网
  subnets = {
    web = {
      type         = "cloud"
      network_zone = "eu-central"
      ip_range     = "10.100.1.0/24"
    }
  }
}

注意事项

  • 网络区域: 选择 eu-central 是因为其定价优势。
  • CIDR 块:
    • 网络 CIDR:10.100.0.0/16
    • 子网 CIDR:10.100.1.0/24(足以容纳单台服务器)

如有需要,可随时添加更多子网或路由。

Source:

防火墙

terraform-hcloud-firewall 模块允许您创建多个防火墙,并自定义入站和出站规则。在本示例中,我们:

  • 仅允许来自 Cloudflare IP 地址的 HTTP/HTTPS 流量。
  • 仅允许来自个人家庭 IP 的 SSH 访问。
  • 允许来自任何位置的 ICMP(ping)。
module "firewall" {
  source  = "danylomikula/firewall/hcloud"
  version = "1.0.0"

  firewalls = {
    "${local.resource_names.website}" = {
      rules = [
        {
          direction   = "in"
          protocol    = "tcp"
          port        = "22"
          source_ips  = [var.my_homelab_ip]
          description = "allow ssh"
        },
        {
          direction   = "in"
          protocol    = "tcp"
          port        = "80"
          source_ips  = local.cloudflare_all_ips
          description = "allow http from Cloudflare"
        },
        {
          direction   = "in"
          protocol    = "tcp"
          port        = "443"
          source_ips  = local.cloudflare_all_ips
          description = "allow https from Cloudflare"
        },
        {
          direction   = "in"
          protocol    = "icmp"
          source_ips  = ["0.0.0.0/0", "::/0"]
          description = "allow ping"
        },
      ]

      labels = {
        service = "firewall"
      }
    }
  }

  common_labels = local.common_labels
}

动态 Cloudflare IP

Cloudflare 会公开其 IP 地址段。通过 Terraform 的 http 数据源获取这些地址,可让防火墙规则自动保持最新。

data "http" "cloudflare_ips_v4" {
  url = "https://www.cloudflare.com/ips-v4"
}

data "http" "cloudflare_ips_v6" {
  url = "https://www.cloudflare.com/ips-v6"
}

locals {
  cloudflare_ipv4_cidrs = split("\n", trimspace(data.http.cloudflare_ips_v4.response_body))
  cloudflare_ipv6_cidrs = split("\n", trimspace(data.http.cloudflare_ips_v6.response_body))
  cloudflare_all_ips    = concat(local.cloudflare_ipv4_cidrs, local.cloudflare_ipv6_cidrs)
}

注意: 为使此配置正常工作,请在 Cloudflare DNS 设置中为您的 A 记录和 AAAA 记录启用 Proxy 切换。

Source:

SSH 密钥

terraform-hcloud-ssh-key 模块用于管理 Hetzner Cloud 的 SSH 密钥。它可以:

  • 自动生成 新的密钥对。
  • 本地存储 私钥(可选)。
  • 导入 已有密钥,以便自行管理。

默认情况下,模块会创建 ED25519 密钥对,这是推荐的安全算法。

使用示例

module "ssh_key" {
  source  = "danylomikula/ssh-key/hcloud"
  version = "1.0.0"

  # 创建新的密钥对
  create_key = true
  name       = local.project_slug

  # 本地保存私钥(可选)
  save_private_key_locally = true
  local_key_directory      = path.module

  # 为密钥资源添加通用标签
  labels = local.common_labels
}
  • create_key – 设置为 true 以生成新的密钥对。
  • name – 在 Hetzner Cloud 中为密钥指定的名称。
  • save_private_key_locally – 为 true 时,私钥会写入 local_key_directory
  • local_key_directory – 私钥文件保存的目录(默认是模块所在目录)。
  • labels – 可选的标签映射,用于附加到密钥资源上。

服务器

terraform-hcloud-server 模块创建计算实例并将之前定义的资源关联在一起。

module "servers" {
  source  = "danylomikula/server/hcloud"
  version = "1.0.0"

  servers = {
    "${local.resource_names.website}" = {
      server_type  = "cx23"
      location     = "hel1"
      image        = data.hcloud_image.rocky.name
      user_data    = local.cloud_init_config
      ssh_keys     = [module.ssh_key.ssh_key_id]
      firewall_ids = [module.firewall.firewall_ids[local.resource_names.website]]
      networks = [{
        network_id = module.network.network_id
        ip         = "10.100.1.10"
      }]
      labels = {
        service = "website"
      }
    }
  }

  common_labels = local.common_labels
}
  • 实例类型: cx23 – Hetzner 最便宜的套餐(≈ 5 美元/月,位于赫尔辛基),足以运行静态网站。
  • 动态标识符: SSH 密钥 ID、防火墙 ID 和网络 ID 均来自相应模块的输出,避免手动配置,降低错误风险。

完整的 Terraform 配置

以下是完整的 Terraform 配置,汇集了上述所有部分。

locals {
  project_slug = "mikula-dev"

  common_labels = {
    environment = "production"
    project     = local.project_slug
    managed_by  = "terraform"
  }

  resource_names = {
    website = "${local.project_slug}-web"
  }

  cloud_init_config = templatefile(
    "${path.module}/cloud-init.tpl",
    { ansible_ssh_public_key = var.ansible_user_ssh_public_key }
  )

  cloudflare_ipv4_cidrs = split("\n", trimspace(data.http.cloudflare_ips_v4.response_body))
  cloudflare_ipv6_cidrs = split("\n", trimspace(data.http.cloudflare_ips_v6.response_body))
  cloudflare_all_ips    = concat(local.cloudflare_ipv4_cidrs, local.cloudflare_ipv6_cidrs)
}

/* Fetch Cloudflare IP ranges for firewall rules */
data "http" "cloudflare_ips_v4" {
  url = "https://www.cloudflare.com/ips-v4"
}

data "http" "cloudflare_ips_v6" {
  url = "https://www.cloudflare.com/ips-v6"
}

/* Network */
module "network" {
  source  = "danylomikula/network/hcloud"
  version = "1.0.0"

  create_network = true
  name           = local.project_slug
  ip_range       = "10.100.0.0/16"

  labels = local.common_labels

  subnets = {
    web = {
      type         = "cloud"
      network_zone = "eu-central"
      ip_range     = "10.100.1.0/24"
    }
  }
}

/* SSH Key */
module "ssh_key" {
  source  = "danylomikula/ssh-key/hcloud"
  version = "1.0.0"

  create_key = true
  name       = local.project_slug

  save_private_key_locally = true
  local_key_directory      = path.module

  labels = local.common_labels
}

/* Firewall */
module "firewall" {
  source  = "danylomikula/firewall/hcloud"
  version = "1.0.0"

  firewalls = {
    "${local.resource_names.website}" = {
      rules = [
        {
          direction   = "in"
          protocol    = "tcp"
          port        = "22"
          source_ips  = [var.my_homelab_ip]
          description = "allow ssh"
        },
        {
          direction   = "in"
          protocol    = "tcp"
          port        = "80"
          source_ips  = local.cloudflare_all_ips
          description = "allow http from cloudflare"
        },
        {
          direction   = "in"
          protocol    = "tcp"
          port        = "443"
          source_ips  = local.cloudflare_all_ips
          description = "allow https from cloudflare"
        },
        {
          direction   = "in"
          protocol    = "icmp"
          source_ips  = ["0.0.0.0/0", "::/0"]
          description = "allow ping"
        },
      ]
      labels = {
        service = "firewall"
      }
    }
  }

  common_labels = local.common_labels
}

/* Server */
module "servers" {
  source  = "danylomikula/server/hcloud"
  version = "1.0.0"

  servers = {
    "${local.resource_names.website}" = {
      server_type  = "cx23"
      location     = "hel1"
      image        = data.hcloud_image.rocky.name
      user_data    = local.cloud_init_config
      ssh_keys     = [module.ssh_key.ssh_key_id]
      firewall_ids = [module.firewall.firewall_ids[local.resource_names.website]]
      networks = [
        {
          network_id = module.network.network_id
          ip         = "10.100.1.10"
        }
      ]
      labels = {
        service = "website"
      }
    }
  }

  common_labels = local.common_labels
}
Back to Blog

相关文章

阅读更多 »