AWS 인프라 Terraform + Helm 으로 설정 과정 #2

jude Kim·2022년 2월 5일
0

전제

스타트업에서 초기 환경을 구축하기 위해서 시도하는 과정이라 대규모 시스템을 구축하는 것과는 괴리가 있습니다.

  • 트래픽이 0에서 점진적으로 증가할 것을 가정하고 구축합니다.
  • 비용을 최소화한 구성을 진행합니다. 단, 나중에 상대적으로 쉽게 변경이 가능한 것은 변경할 것을 가정하고 진행합니다.
  • 초기에는 특히 설치 반복을 무지하게 반복할 것같아 디렉토리를 이 상황을 고려해서 추가적으로 분리합니다.

Environment

  • 로컬 환경 local - AWS와 관련없는 환경이지만 k8s 로 추상화되어 유사하게 구현
  • 개발 환경 dev - 로컬 개발환경도 있긴하지만, EKS 환경하에서 개발 및 테스트를 위한 환경이 필요합니다.
  • 스테이징 환경 stage - 실서버와 동일한 환경하에서의 배포전 검증등을 진행하는 환경 다만, 초반에는 optional 로 생각하고 생략할 예정
  • 실서비스 환경 prod - 실서비스 환경 궁극적으로 고립되어 간섭이 없는 환경이어야 합니다. 운영자의 실수가 영향이 되지 않도록 권한까지 분리하면 더욱 더 좋습니다. (바램)

위는 단일 서비스에서의 기본적인 고려사항입니다.
여기에 추가적으로 환경은 아니지만 infra를 관리하기 위한 영역이 필요합니다.

이 환경을 어떻게 isolation 할것인가?를 고민해봤는데, 현실적인 비용을 고려하면서 생각하다보니 생각의 흐름이 이런식이 되었습니다.
비용

  • 개별 EKS Cluster를 둘것인가? 하지만 EKS Cluster는 설정만 해두어도(시간만 흘러도 청구되는) 들어가는 비용이 있습니다. 여기에 필수적인 요소인 NAT Gateway(private subnet에서 외부와 통신하기 위한)나, ALB 등도 고정 비용의 요소입니다.

시간

  • 환경별로 껐다 켰다가 하는 것도 한계가 있습니다. 저 고정비용의 항목들은 그냥 청구되는 것이 아니라 독립적인 Infra가 생성되는 것이라 청구가 되는 것이고, 즉 Dedicated 자원들은 그냥 있는것을 shared 하는 설정의 영역이 아니라서(추측) 생성하는데 시간이 필요합니다.
  • EKS Cluster 하나 생성하는데 최근에 단축되어서 9분이라고 하지만 실제로는 12분 가량 걸렸습니다. 시간을 앞당길 수 있나도 알아봤지만 Scale up을 할 수 있는 옵션은 없어서 시간 단축은 AWS가 힘써주지 않는한 없다고 봐야합니다.
  • NAT Gateway도 오랜 시간은 아니지만 1분 40여초가 걸립니다.
aws_nat_gateway.kts-eks-nat-gateway: Creation complete after 1m38s [id=nat-017806cdee17537ef]

위 두가지를 고려했을때 단일 EKS Cluster로 진행하고자 합니다.

그럼 환경은 어떻게 처리할 것일지 고민했습니다.

k8s의 namespace를 기반으로 하기로 정리하였습니다.
거기에 추가적인 infra(eg. CI/CD 등)를 관리하기 위한 것도 모두 namespace 기반으로 분리할 예정입니다.

Common

base/provider.tf

terraform {
  required_version = ">= 0.12"
  backend "s3" {
    profile = "terraform"
    bucket  = "{{s3-bucket}}"
    key     = "base/terraform.tfstate"
    region  = "ap-northeast-2"
  }
}

provider "aws" {
  profile = var.profile
  region  = var.aws-region-seoul
}

data "aws_availability_zones" "available" {
  all_availability_zones = true
}
  • region : ap-northeast-2 seoul
  • availability zone (az) : seoul 리전은 4개의 az를 가지고 있음
  • terraform state 는 S3 를 사용하기로 결정

    terraform cloudS3를 고민했으나, 또다른 risk point를 추가하는것은 지양하자고 생각해서 S3로 결정

  • aws config 에서 terraform 이라는 profile을 셋팅하고 그 프로파일을 활용

VPC

base/vpc.tf

EKS는 단일 VPC로 구성을 해야 해서 private IP 대역대에서 10.10.0.0/16을 우선 사용하기로 정의하였습니다.

10.10.0.0 ~ 10.10.255.255 즉, 65,536개의 IP를 사용할 수 있다.

# create VPC
resource "aws_vpc" "kts-eks-vpc" {
  cidr_block = "10.10.0.0/16"
  tags = {
    "Name"                                          = "kts-eks-node"
    "kubernetes.io/cluster/${var.eks-cluster-name}" = "shared"
  }
}

subnet 구성

resource "aws_subnet" "kts-eks-public-subnet" {
  count = length(data.aws_availability_zones.available.names)

  availability_zone       = element(data.aws_availability_zones.available.names, count.index)
  cidr_block              = "10.10.${var.subnet-cidr.public-index + (count.index * var.subnet-cidr.public-subnet-step)}.0/${var.subnet-size[var.subnet-cidr.public-subnet-step]}"
  map_public_ip_on_launch = true
  vpc_id                  = aws_vpc.kts-eks-vpc.id

  tags = {
    "Name"                                          = "kts-eks-public-${count.index}"
    "kubernetes.io/cluster/${var.eks-cluster-name}" = "shared"
  }
}

# private subnet
resource "aws_subnet" "kts-eks-private-subnet" {
  count = length(data.aws_availability_zones.available.names)

  availability_zone = element(data.aws_availability_zones.available.names, count.index)
  cidr_block        = "10.10.${var.subnet-cidr.private-index + (count.index * var.subnet-cidr["private-subnet-step"])}.0/${var.subnet-size[var.subnet-cidr["private-subnet-step"]]}"
  vpc_id            = aws_vpc.kts-eks-vpc.id

  tags = {
    "Name"                                          = "kts-eks-private-${count.index}"
    "kubernetes.io/cluster/${var.eks-cluster-name}" = "shared"
  }
}

# db subnet
resource "aws_subnet" "kts-eks-db-subnet" {
  count = length(data.aws_availability_zones.available.names)

  availability_zone = element(data.aws_availability_zones.available.names, count.index)
  cidr_block        = "10.10.${var.subnet-cidr.db-index + (count.index * var.subnet-cidr["db-subnet-step"])}.0/${var.subnet-size[var.subnet-cidr["db-subnet-step"]]}"
  vpc_id            = aws_vpc.kts-eks-vpc.id

  tags = {
    "Name"                                          = "kts-eks-db-${count.index}"
    "kubernetes.io/cluster/${var.eks-cluster-name}" = "shared"
  }
}
base/variables.tf
variable "subnet-size" {
  type    = map
  default = {
    1 = "24"
    2 = "23"
    4 = "22"
    8 = "21"
  }
}

variable "subnet-cidr" {
  type    = map
  default = {
    # 0 부터 시작해서 1단위로 총 4개 - 0 + (1 * 4) = 4
    "public-index"        = 0
    "public-subnet-step"  = 1
    # 4부터 시작해서 4단위로 총 4개 - 4 + (4 * 4) = 20
    "private-index"       = 4
    "private-subnet-step" = 4
    # 20부터 시작해서 1단위로 총 4개 - 20 + (1 * 4) = 24
    "db-index"            = 20
    "db-subnet-step"      = 1
  }
}
  • az 별로 subnet 구성을 진행해야 해서 a, c zone(exclude_names = ["ap-northeast-2b", "ap-northeast-2d"])을 이용해서 public/private 각 2개씩 az으로 나누려 했으나, 단일 EKS Cluster로 구성을 진행할 예정이라 4개의 az을 다 활용하는 구성으로 진행하는게 좋겠다고 생각했습니다.
  • 추후 EKS에 subnet을 추가하는 것은 가능하지만, 가능하지 않은것과 같다고 생각해야 합니다. subnet을 추가하고 terrafrom apply를 실행하면 EKS Cluster를 다시 생성합니다. 즉, 실서비스에서는 일반적인 상황에선 사용할 수 없습니다.
data "aws_availability_zones" "available" {
}
  • all_availability_zones = true 라는 설정도 있지만 이렇게 진행한다면 4개의 az 가 있음에도 0 ~ 4까지의 총 5개의 subnet을 생성하도록 시도하며 우리가 본적이 없는 0번때문에 실패합니다. "ap-northeast-2-wl1-cjj-wlz-1" 와 같은 zone을 설정하려 합니다.

  • EKS의 worker node의 경우 private subnet만을 사용하지만, EKS 클러스터는 public 및 private 을 둘다 사용하도록 설정을 해두어야 public ELB를 통해 내부망 통신으로 내부 worker node 들과 통신하는 형태를 구성할 수 있습니다.

  • db subnet은 RDS를 사용할 것이라 EKS subnet으로 지정하지는 않습니다.

Internet gateway

resource "aws_internet_gateway" "kts-eks-igw" {
  vpc_id = aws_vpc.kts-eks-vpc.id

  tags = {
    "Name" = "kts-eks-igw"
  }
}

route table & association

# create public route table
resource "aws_route_table" "kts-eks-public-route" {
  vpc_id = aws_vpc.kts-eks-vpc.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.kts-eks-igw.id
  }

  tags = {
    "Name" = "kts-eks-public"
  }
}

# create route table association
# subnet 과 route table을 연결
resource "aws_route_table_association" "kts-eks-public-routing" {
  count          = length(aws_subnet.kts-eks-public-subnet.*.id)
  subnet_id      = aws_subnet.kts-eks-public-subnet.*.id[count.index]
  route_table_id = aws_route_table.kts-eks-public-route.id
}
  • route table에서 private 및 db subnet은 별도의 폴더로 분리하였습니다.
  • 이렇게 분리를 진행한 가장 큰 이유는 별도의 비용이 청구되는 NAT Gateway 때문이고, 초기 설정이 확정된 이후에는 굳이 분리하여 관리를 할 필요는 없다고 생각합니다.

On demand VPC

on-demand/vpc.tf

EIP & NAT Gateway

# EIP 정의
resource "aws_eip" "kts-eks-eip" {
  vpc  = true
  tags = {
    "Name" = "kts-eks-public-nat-gw"
  }
}

# NAT Gateway
resource "aws_nat_gateway" "kts-eks-nat-gateway" {
  allocation_id = aws_eip.kts-eks-eip.id
  subnet_id     = data.terraform_remote_state.base.outputs.kts-eks-main-public-subnet-id

  tags = {
    "Name" = "kts-eks-nat-gw"
  }
}
on-demand/data.tf
data "terraform_remote_state" "base" {
  backend = "s3"
  config = {
    bucket = "{{s3-bucket}}"
    key = "base/terraform.tfstate"
    region = "ap-northeast-2"
  }
}
  • NAT gateway를 생성합니다. 특성에 맞추어 public subnet에 설치가 되어야 하고 이를 위해서 data.tf 파일을 통해서 앞서 설정한 terraform.tfstate 파일에 접근하여 subnet id를 가져올 수 있도록 합니다.
  • 이를 위해서 base/output.tf 파일에 다음과 같이 설정해줍니다.
output "kts-eks-main-public-subnet-id" {
  value = aws_subnet.kts-eks-public-subnet[0].id
}
  • 분리된 폴더에서 생성된 자원의 정보를 참조하기 위해선 S3 backend 같은 설정이 필요하고, output.tf 에서 어떤 설정을 어떤 변수이름으로 접근할지에 대해서 선언해주어야 하며, 이렇게 되어 있다면 data.terraform_remote_state.base.outputs.kts-eks-main-public-subnet-id 와 같이 설정을 통해서 접근이 가능하게 됩니다.

  • 추가적으로 private, db의 route table 및 association (route table과 subnet을 연결해주는 설정)을 설정합니다.

이상으로 VPC 기본적인 네트워크 설정을 마쳤습니다.

다음에는 EKS 클러스터 및 Worker Node 설정을 진행해보겠습니다.

profile
씨봉봉이

0개의 댓글