构建我的个人网站:从想法到自动化部署(第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是因为其定价优势。 - CIDR 块:
- 网络 CIDR:
10.100.0.0/16 - 子网 CIDR:
10.100.1.0/24(足以容纳单台服务器)
- 网络 CIDR:
如有需要,可随时添加更多子网或路由。
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
}