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

发布: (2025年12月4日 GMT+8 23:50)
5 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 网络区域,因为它的定价更优惠。该配置创建了一个 /16 CIDR 块(10.100.0.0/16),并在其中划分了一个 /24 子网(10.100.1.0/24),足以容纳单台服务器。

防火墙

terraform-hcloud-firewall 模块允许创建多个防火墙并自定义入站和出站规则。在本示例中,我仅允许来自 Cloudflare IP 地址的 HTTP/HTTPS 流量以及来自我家庭 IP 的 SSH 访问。

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 开关。

SSH 密钥

terraform-hcloud-ssh-key 模块负责 SSH 密钥管理,支持自动生成密钥、本地存储以及导入已有密钥。

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
}

默认会生成一对 ED25519 密钥并本地保存,这是一种安全且推荐的认证方式。

服务器

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 ra */
Back to Blog

相关文章

阅读更多 »

第12天:AWS Terraform 函数

Terraform 中的高级函数 基于第 11 天所介绍的基础函数,今天我们将探索更多能够增强 Terraform 的专用函数……

Terraform 项目:简单 EC2 + 安全组

项目结构 terraform-project/ │── main.tf │── variables.tf │── outputs.tf │── providers.tf │── terraform.tfvars │── modules/ │ └── ec2/ │ ├── main.tf │ …

在 S3 中保存 Terraform 状态

配置 S3 作为 Terraform 后端 Terraform 可以将其状态存储在 S3 存储桶中。以下是一个最小的配置示例,用于设置 S3 后端:hcl terrafor...