내 개인 웹사이트 구축: 아이디어부터 자동 배포까지 (파트 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 a new network
create_network = true
name = local.project_slug
ip_range = "10.100.0.0/16"
# Optional labels
labels = local.common_labels
# Define subnets
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:
필요에 따라 추가 서브넷이나 라우트를 자유롭게 추가하세요.
방화벽
terraform-hcloud-firewall 모듈을 사용하면 사용자 정의 인바운드 및 아웃바운드 규칙을 가진 여러 방화벽을 생성할 수 있습니다. 이 예제에서는 다음을 수행합니다:
- HTTP/HTTPS 트래픽을 Cloudflare IP 주소에서만 허용합니다.
- SSH 접근을 개인 홈 IP에서만 허용합니다.
- 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)
}
Note: 이 구성이 올바르게 작동하려면 Cloudflare DNS 설정에서 A 및 AAAA 레코드에 대한 Proxy 토글을 활성화하십시오.
Source:
SSH Key
terraform-hcloud-ssh-key 모듈은 Hetzner Cloud용 SSH 키를 관리합니다. 이 모듈은 다음을 수행할 수 있습니다.
- 새 키 쌍을 자동으로 생성합니다.
- 개인 키를 로컬에 저장합니다(선택 사항).
- 직접 관리하고 싶다면 기존 키를 가져오기합니다.
기본적으로 모듈은 ED25519 키 쌍을 생성하는데, 이는 권장되는 안전한 알고리즘입니다.
사용 예시
module "ssh_key" {
source = "danylomikula/ssh-key/hcloud"
version = "1.0.0"
# Create a new key pair
create_key = true
name = local.project_slug
# Save the private key locally (optional)
save_private_key_locally = true
local_key_directory = path.module
# Apply common labels to the key resource
labels = local.common_labels
}
create_key–true로 설정하면 새 키 쌍을 생성합니다.name– Hetzner Cloud에서 키에 할당할 이름입니다.save_private_key_locally–true로 설정하면 개인 키가local_key_directory에 기록됩니다.local_key_directory– 개인 키 파일이 저장될 디렉터리(기본값은 모듈 디렉터리).labels– 키 리소스에 붙일 선택적 라벨 맵입니다.
Source: …
서버
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– 헷즈너에서 제공하는 가장 저렴한 옵션(헬싱키 기준 월 약 $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
}