Infrastruktúra

Production VPC fehér lapról: subnetek, NAT, útválasztás és security groupok Terraformmal

A hálózat az a réteg, amit később a legfájdalmasabb átépíteni. Végigtervezünk egy multi-AZ VPC-t publikus és privát subnetekkel, NAT-tal, útvonaltáblákkal és szigorú security groupokkal — teljes Terraform-kóddal.

Röviden: A hálózat az a réteg, amit később a legfájdalmasabb átépíteni. Végigtervezünk egy multi-AZ VPC-t publikus és privát subnetekkel, NAT-tal, útvonaltáblákkal és szigorú security groupokkal — teljes Terraform-kóddal.

A hálózat az a réteg, amit a legkényelmetlenebb utólag átszabni: ha rosszul méretezed a CIDR-t vagy összekevered a publikus és privát útválasztást, később egy egész környezetet kell újraépíteni. Ezért érdemes a VPC-t az elején, tudatosan, kódban megtervezni. Ebben a cikkben egy klasszikus, két elérhetőségi zónára (AZ) terülő production VPC-t építünk: publikus subnetek a bejövő forgalomnak, privát subnetek az alkalmazásnak, NAT a kimenő forgalomhoz, és szigorúan szabályozott security groupok.

A cél-topológia

Két AZ-ban egy-egy publikus és egy-egy privát subnet. A publikus subnetekben ül az internet gateway felé nyíló load balancer és a NAT gateway. A privát subnetekben futnak az alkalmazás-instance-ok, amelyek csak a NAT-on keresztül érik el az internetet — kifelé igen, befelé közvetlenül nem.

CIDR-tervezés: gondolkodj előre

A /16-os VPC (65 536 cím) bőven elég, és hagy helyet a jövőnek. A subneteket /24-esre szabjuk (256 cím zónánként), és szándékosan hagyunk hézagot a publikus és privát tartomány között, hogy később új subnet-típusokat (pl. adatbázis) is beilleszthess ütközés nélkül.

SubnetCIDRAZHasználható IPCél
public-a10.0.0.0/241a~251ALB, NAT
public-b10.0.1.0/241b~251ALB
private-a10.0.10.0/241a~251app
private-b10.0.11.0/241b~251app
(fenntartva)10.0.20.0/24+DB, cache

A VPC és a subnetek Terraformban

A subneteket for_each-csel generáljuk egy lokális map-ből, hogy ne másoljuk a kódot négyszer. Ez a minta jól skálázódik, ha később bővítesz.

hcl
locals {
  azs = ["eu-central-1a", "eu-central-1b"]

  public_subnets = {
    "public-a" = { cidr = "10.0.0.0/24", az = "eu-central-1a" }
    "public-b" = { cidr = "10.0.1.0/24", az = "eu-central-1b" }
  }
  private_subnets = {
    "private-a" = { cidr = "10.0.10.0/24", az = "eu-central-1a" }
    "private-b" = { cidr = "10.0.11.0/24", az = "eu-central-1b" }
  }
}

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true
  tags                 = merge(local.tags, { Name = "${var.project}-vpc" })
}

resource "aws_subnet" "public" {
  for_each                = local.public_subnets
  vpc_id                  = aws_vpc.main.id
  cidr_block              = each.value.cidr
  availability_zone       = each.value.az
  map_public_ip_on_launch = true
  tags                    = merge(local.tags, { Name = each.key })
}

resource "aws_subnet" "private" {
  for_each          = local.private_subnets
  vpc_id            = aws_vpc.main.id
  cidr_block        = each.value.cidr
  availability_zone = each.value.az
  tags              = merge(local.tags, { Name = each.key })
}

Internet gateway, NAT és útvonaltáblák

Az internet gateway a VPC ajtaja kifelé. A publikus subnetek útvonaltáblája 0.0.0.0/0-t az IGW felé küldi. A privát subnetek viszont a NAT gatewayre mutatnak — így az app-instance-ok le tudnak tölteni csomagokat és hívhatnak külső API-t, de kívülről nem címezhetők közvetlenül.

hcl
resource "aws_internet_gateway" "igw" {
  vpc_id = aws_vpc.main.id
  tags   = merge(local.tags, { Name = "${var.project}-igw" })
}

resource "aws_eip" "nat" {
  domain = "vpc"
  tags   = local.tags
}

resource "aws_nat_gateway" "nat" {
  allocation_id = aws_eip.nat.id
  subnet_id     = aws_subnet.public["public-a"].id
  tags          = merge(local.tags, { Name = "${var.project}-nat" })
  depends_on    = [aws_internet_gateway.igw]
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.igw.id
  }
  tags = merge(local.tags, { Name = "${var.project}-public-rt" })
}

resource "aws_route_table" "private" {
  vpc_id = aws_vpc.main.id
  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = aws_nat_gateway.nat.id
  }
  tags = merge(local.tags, { Name = "${var.project}-private-rt" })
}

resource "aws_route_table_association" "public" {
  for_each       = aws_subnet.public
  subnet_id      = each.value.id
  route_table_id = aws_route_table.public.id
}

resource "aws_route_table_association" "private" {
  for_each       = aws_subnet.private
  subnet_id      = each.value.id
  route_table_id = aws_route_table.private.id
}

Security groupok: a valódi tűzfal

A security group állapotkövető (stateful): ha beengedsz egy kérést, a válasz automatikusan kimehet. A jó minta rétegez: a load balancer SG a 443-at engedi az internetről, az app SG pedig CSAK a load balancer SG-jétől fogad forgalmat — nem az egész internettől. Ez a hivatkozás-SG-re minta a legfontosabb hálózati védelem.

hcl
resource "aws_security_group" "alb" {
  name   = "${var.project}-alb"
  vpc_id = aws_vpc.main.id

  ingress {
    description = "HTTPS az internetről"
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = local.tags
}

resource "aws_security_group" "app" {
  name   = "${var.project}-app"
  vpc_id = aws_vpc.main.id

  ingress {
    description     = "Csak az ALB-től"
    from_port       = 8080
    to_port         = 8080
    protocol        = "tcp"
    security_groups = [aws_security_group.alb.id]
  }
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  tags = local.tags
}

Forgalomszabályozás: ki beszélhet kivel?

A rétegzett SG-k így egy tiszta forgalmi láncot adnak: az internet csak az ALB-vel beszél, az ALB csak az app-pal, az app pedig a NAT-on át ér ki. Semmi nem címezhető közvetlenül kívülről a privát rétegben.

NACL vagy security group?

Két hálózati kontroll létezik, és gyakran összekeverik őket. A security group instance-szintű és stateful; a network ACL subnet-szintű és stateless. A legtöbb esetben a security group elég és kényelmesebb; a NACL akkor jön jól, ha egész subnetre akarsz durva, IP-alapú tiltást (pl. egy támadó tartomány kizárása).

TulajdonságSecurity GroupNetwork ACL
Szintinstance / ENIsubnet
Állapotstatefulstateless
Szabályokcsak allowallow és deny
Kiértékelésmindsorrendben, szám szerint
Tipikus használatalapvédelemdurva subnet-szintű tiltás

Mit jelent ez neked?

Ez a VPC-alap — két AZ, publikus és privát subnetek, NAT, rétegzett security groupok — a legtöbb production workload biztonságos kiindulása. A kulcsdöntések láthatók és verziózottak: a CIDR-terv hagy helyet a jövőnek, a privát subnetek nem címezhetők kívülről, és az SG-hivatkozás minta valódi szegmentációt ad. Ha ezt egyszer jól felépíted modulként, a következő környezet (staging, prod) már csak egy terraform apply más változókkal. A hálózatot nem szabad kézzel kattintgatni — pont ez az a réteg, ahol a kód-alapú, reprodukálható megközelítés a legtöbbet éri.