构建我的个人网站:从想法到自动化部署(第2部分)
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 */