AWS VPC Network with Terraform

지난 블로그 AWS VPC basic 에서 AWS VPC Network에 대한 기초지식을 살펴 봤었는데 AWS VPC에 대해 공부하게 된 계기 중에 하나가 Terraform이었다.

지금 다니고 있는 회사에서는 Terraform을 이용해서 AWS 리소스를 관리하고 있는데 새롭게 접한 Terraform이 쉽사리 익숙해지지 않아 개인 AWS 계정을 통해 Terraform을 연습해 보고자 시도를 했었다.

그런데 막상 Terraform을 이용해 VPC를 생성하려고 보니 AWS VPC의 각각의 리소스에 대해 명확한 이해가 되어있지 않아 Terraform파일 작성이 쉽지 않았다.

지난 글을 통해서 AWS VPC를 정리하면서 이제 AWS VPC에 대해 어느정도 감을 잡았다고 생각하고 해당 지식을 기반으로 Terraform을 이용해서 VPC를 생성해 보려고 한다.

Terraform으로 VPC를 생성해보자.

먼저 구글에서 Terraform AWS VPC 의 키워드로 검색해보면, 다음과 같은 글들을 쉽게 찾을 수 있다.

Terraform으로 AWS VPC 생성하기 :: Outsider’s Dev Story
Terraform으로 VPC 설정하기
Create AWS VPC with Terraform – InsidePacket
Building VPC with Terraform in Amazon AWS – DevOps
Terraform: AWS VPC with Private and Public Subnets — Nick Charlton

이렇게 참고할 수 있는 자료가 많이 있는데도 불구하고 굳이 블로그에 새로이 정리하는 이유는 몇일 전 AWS VPC basic이라는 글을 쓰고보니 다른 사람이 작성한 글을 읽어 보는 것실제로 내가 실행해보고 정리해서 설명하는 것 은 차원이 다르다. 라는 걸 깨닫게 되었기 때문이다…라는 건 왠지 멋지게 보이려고 써 본 이유고, 실은 블로그 글 10개를 쓰면 스스로에게 산토리 가쿠빈을 선물로 주자! 하고 결심했기 때문에…(이 글을 다쓰면 8번만 더 쓰면 된다! 파이팅!)

그런데 Terraform은 과연 무엇인가?

Terraform
TerraformHashiCorp에서 만든 인프라스트럭처 관리도구다.
홈페이지에는 Write, Plan, and Create Infrastructure as Code 라는 슬로건이 큼지막하게 화면 중앙을 차지하고 있다. 말 그대로 인프라스트럭처를 GUI가 아닌코드를 통해 생성, 수정, 삭제할 수 있다. 이 코드는 일반적으로 개발자들이 작성하는 소스코드처럼 (대부분) VCS를 통해 관리되고, 코드가 변하지 않는 한 언제든 동일한 설정 값(상태)으로 새로운 인프라를 생성할 수 있다.

Terraform은 특정 벤더(프로바이더)에 종속되어 있지 않기 때문에 Providers 페이지를 보면 Alicloud, AWS, DigitalOcean, Google Cloud, OpenStack등의 리소스를 Terraform Configuration코드를 통해 관리할 수 있음을 알 수 있다. 클라우드 리소스뿐만 아니라 DNS, Consul, Github등의 다양한 infrastructure를 코드로 관리할 수 있다.

Terraform이 사용하는 Configuration 파일은 Hashicorp가 만든 HCL – HashiCorp configuration language 포맷의 파일을 .tf 확장자로 생성하거나, JSON 포맷의 파일을 .tf.json 확장자로 생성해서 사용할 수 있다.

Terraform에 대한 좀 더 자세한 설명은 @outsider 님의 Terraform에 대해서… :: Outsider’s Dev StoryTerraform으로 AWS 관리하기 :: Outsider’s Dev Story를 읽어보는 것을 추천한다.

Terraform 사용 준비하기

brew를 이용한 설치

맥을 이용하는 경우, 다음과 같이 brew를 이용해서 간단히 설치할 수 있다.

$ brew install terraform

바이너리 파일을 이용한 설치

Download Terraform 에서 Terraform을 다운받는다.
다운받은 zip파일의 압축을 풀면, terraform 바이너리 파일이 짠! 하고 나온다.
HashiCorp의 대부분의 툴들과 마찬가지로 Terraform도 golang으로 만들어졌기 때문에 별도의 설치 과정없이 실행환경에 맞는 바이너리 파일을 받아 path에 위치시키고 사용하면 된다.

나는 terraform 바이너리 파일의 심볼릭링크를 /usr/local/bin 아래에 생성했다.

$ which terraform
/usr/local/bin/terraform

$ terraform -v
Terraform v0.10.5

Terraform에서 사용할 IAM 생성

AWS Console IAM – Users 메뉴에서 Add user를 선택하고 User를 생성한다.

User 명, access type 선택

add_user1
terraform이라는 이름으로 User를 생성한다. AWS Console에는 사용하지 않고, API 호출만 사용하는 User이므로 Access type은 programmatic access를 선택한다.

Policy 선택

add_user2
Terraform이 사용할 policy를 추가한다.
이번에는 VPC 생성을 할 예정이므로 AmazonVPCFullAccess를 선택했다.
처음부터 AdministratorAccess policy를 줄 수도 있지만 좀 더 안전하게 필요한 policy를 그때 그때 추가하는 방법이 좋다고 생각한다.

Review

add_user3
Review에서 생성할 내용을 확인하고 Create user를 클릭해서 User생성을 완료한다.

Key 확인

add_user4
Terraform에 세팅할 terraform 계정의 Access key IDSecret access key(Show 버튼을 눌러 확인)를 기록해둔다.

Terraform이 사용할 Access key ID, Secret access key 세팅

Terraform에 AWS IAM을 적용할 때는 환경변수에 세팅, provider configuration에 직접 세팅, *.tfvar를 사용한 세팅 등의 방법이 있다.
나는 direnv를 사용하기 때문에 project root 경로에 .envrc 파일을 다음과 같이 작성하고 환경변수를 이용해 IAM을 적용한다.

# terraform personal AWS
export AWS_ACCESS_KEY_ID={유저 생성시 할당받은 Access key ID}
export AWS_SECRET_ACCESS_KEY={유저 생성 시 할당받은 Secret access key}
export AWS_DEFAULT_REGION=ap-northeast-1

direnv는 디렉터리 기반으로 환경변수를 설정할 수 있어 프로젝트 마다 별도의 env를 지정해서 사용해야할 경우 유용하다. 이전에는 direnv없이 어떻게 개발했었나 싶다.

프로젝트 디렉터리 생성

Terraform configuration file(이하 Terraform 파일)을 작성하기 위해서 2dal-infrastrucure라는 디렉터리를 생성하고, 다음과 같이 구조를 잡았다.

$ tree
.
├── README.md
└── terraform
    ├── common # 공용 리소스 정의
    │   ├── iam # iam 정의
    │   └── dev_vpc # dev VPC 정의
    └── project # 프로젝트 별 리소스 정의

Terraform으로 VPC 생성하기

AWS VPC basic 글에서 설계했던 VPC Network를 Terraform을 이용해서 생성해 보자.
AWS VPC3

개발에 사용할 목적의 VPC이므로 VPC 이름을 dev VPC로 생성한다.
두개의 AZ위에 public / private으로 구분한 subnet을 올리고 public subnet에는 외부로 나가는 트래픽을 위해 NAT – IGW를 연결했다. 그림에는 보이지 않지만 각각의 연결단계에서 라우팅테이블로 아웃바운드 룰을 정의한다.

AWS 리소스에 대해서는 Provider: AWS 페이지에 정리되어 있으므로, 이 페이지를 참고해서 각각의 리소스에 해당하는 Terraform 파일을 작성해보자.

./terraform/common/dev_vpc 경로에서 Terraform파일을 작성한다.

$ mkdir -p terraform/common/dev/dev_vpc
$ cd terraform/common/dev_vpc

Provider

  • provider.tf
provider "aws" {
  region = "ap-northeast-1"
}

AWS provider를 정의한다.
나는 도쿄리전을 사용하기 때문에 region 항목에 ap-northeast-1 을 설정했다.

terraform파일을 작성 후에 execution plan을 생성하고, 리뷰(변경사항을 체크)할 수 있는 terraform plan 명령을 수행해보면

$ terraform plan
Plugin reinitialization required. Please run "terraform init".
Reason: Could not satisfy plugin requirements.
...

위와 같이 오류가 발생한다. terraform plan, apply등의 명령을 실행하기 앞서서 provider plugin설치 등의 초기셋업을 위해 terraform init을 실행해야 한다.

terraform init을 실행한다.

$ terraform init
...
Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
...

이제 terraform plan을 실행해보면 변경(적용)할 내용이 없다는 메시지가 정상적으로 출력된다.

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

VPC

  • vpc.tf
resource "aws_vpc" "dev" {
  cidr_block           = "172.16.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  instance_tenancy     = "default"

  tags {
    Name = "dev"
  }
}

dev VPC를 정의한다.
enable_dns_xxxxx 옵션은 EC2 인스턴스에 DNS 호스트 이름을 사용하기위해 true로 세팅한다.
instance_tenancy 옵션은 dedicated로 설정한 경우 Amazon EC2 Dedicated Instances 의 설명처럼 유저 전용의 하드웨어에서 EC2 instance를 생성한다. dev VPC에서는 default로 값을 설정한다

Subnet

AWS VPC3
subnet.tf 파일 작성 전에 생성할 VPC Subnet을 한번 더 확인 해보자.

다음과 같이 2개의 AZ에 public, private subnet을 각각 1개씩 생성한다.

AZ: ap-northeast-1a
    public  subnet: 172.16.1.0/24
    private subnet: 172.16.101.0/24
AZ: ap-northeast-1c
    public  subnet: 172.16.2.0/24
    private subnet: 172.16.102.0/24
  • subnet.tf
resource "aws_subnet" "public_1a" {
  vpc_id            = "${aws_vpc.dev.id}"
  availability_zone = "ap-northeast-1a"
  cidr_block        = "172.16.1.0/24"

  tags {
    Name = "public-1a"
  }
}

resource "aws_subnet" "private_1a" {
  vpc_id            = "${aws_vpc.dev.id}"
  availability_zone = "ap-northeast-1a"
  cidr_block        = "172.16.101.0/24"

  tags {
    Name = "private-1a"
  }
}

resource "aws_subnet" "public_1c" {
  vpc_id            = "${aws_vpc.dev.id}"
  availability_zone = "ap-northeast-1c"
  cidr_block        = "172.16.2.0/24"

  tags {
    Name = "public-1c"
  }
}

resource "aws_subnet" "private_1c" {
  vpc_id            = "${aws_vpc.dev.id}"
  availability_zone = "ap-northeast-1c"
  cidr_block        = "172.16.102.0/24"

  tags {
    Name = "private-1c"
  }
}

${aws_vpc.dev.id} 는 aws_vpc의 dev리소스로부터 id값을 가져와서 세팅한다.
resource name은 {aws_subnet.public_1a.id} 와 같이 작성하기 쉽도록 underscore를 사용했다.

Internet Gateway (IGW)

  • internet_gateway.tf
resource "aws_internet_gateway" "dev" {
  vpc_id = "${aws_vpc.dev.id}"

  tags {
    Name = "dev"
  }
}

dev VPC에서 사용할 IGW를 정의한다. IGW는 AZ에 무관하게 한개의 IGW를 공유해서 사용할 수 있다.

Elastic IP (EIP)

  • eip.tf
resource "aws_eip" "nat_dev_1a" {
  vpc = true
}

resource "aws_eip" "nat_dev_1c" {
  vpc = true
}

각각의 AZ의 NAT에서 사용할 EIP를 정의한다.
vpc = true 항목은 aws_eip 페이지에 별도의 설명은 없지만 EIP 생성 시 EIP의 scope를 VPC로 할지 classic으로 할지 물어봤던 옵션을 의미하는 것으로 추측된다.

NAT Gateway

  • nat_gateway.tf
resource "aws_nat_gateway" "dev_1a" {
  allocation_id = "${aws_eip.nat_dev_1a.id}"
  subnet_id     = "${aws_subnet.public_1a.id}"
}

resource "aws_nat_gateway" "dev_1c" {
  allocation_id = "${aws_eip.nat_dev_1c.id}"
  subnet_id     = "${aws_subnet.public_1c.id}"
}

설정파일을 작성하다보니 NAT도 IGW처럼 한개를 공유해서 사용하는지, 아니면 AZ별로 각각 NAT를 생성해야 하나 의문이 생겼었는데 NAT 게이트웨이 – Amazon Virtual Private Cloud 가이드 문서에 따르면 다음과 같이 가용영역(AZ) 별로 NAT 게이트웨이를 사용해야 복수의 AZ를 사용하는 장점을 같이 가져갈 수 있음을 알 수 있다.

여러 가용 영역에 리소스가 있고 하나의 NAT 게이트웨이를 공유하는 경우 NAT 게이트웨이의 가용 영역이 다운되면 다른 가용 영역의 리소스도 인터넷에 액세스할 수 없습니다. 가용 영역과 독립적인 아키텍처를 만들려면 각 가용 영역에 NAT 게이트웨이를 만들고 리소스가 동일한 가용 영역의 NAT 게이트웨이를 사용하도록 라우팅을 구성합니다.

NAT Gateway는 왠지 tags(Name)를 넣을 수 없었다. (Console에서는 잘 넣어짐)

Route Table

  • route_table.tf
# dev_public
resource "aws_route_table" "dev_public" {
  vpc_id = "${aws_vpc.dev.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.dev.id}"
  }

  tags {
    Name = "dev-public"
  }
}

resource "aws_route_table_association" "dev_public_1a" {
  subnet_id      = "${aws_subnet.public_1a.id}"
  route_table_id = "${aws_route_table.dev_public.id}"
}

resource "aws_route_table_association" "dev_public_1c" {
  subnet_id      = "${aws_subnet.public_1c.id}"
  route_table_id = "${aws_route_table.dev_public.id}"
}

# dev_private_1a
resource "aws_route_table" "dev_private_1a" {
  vpc_id = "${aws_vpc.dev.id}"

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = "${aws_nat_gateway.dev_1a.id}"
  }

  tags {
    Name = "dev-private-1a"
  }
}

resource "aws_route_table_association" "dev_private_1a" {
  subnet_id      = "${aws_subnet.private_1a.id}"
  route_table_id = "${aws_route_table.dev_private_1a.id}"
}

# dev_private_1c
resource "aws_route_table" "dev_private_1c" {
  vpc_id = "${aws_vpc.dev.id}"

  route {
    cidr_block     = "0.0.0.0/0"
    nat_gateway_id = "${aws_nat_gateway.dev_1c.id}"
  }

  tags {
    Name = "dev-private-1c"
  }
}

resource "aws_route_table_association" "dev_private_1c" {
  subnet_id      = "${aws_subnet.private_1c.id}"
  route_table_id = "${aws_route_table.dev_private_1c.id}"
}

public subnet -> IGW
private subnet -> NAT Gateway 로 0.0.0.0/0 outbound를 라우팅하는 route_table을 정의하고 해당 route_table을 적용할 subnet에 route table association을 만들어준다.

만약 여기까지 작성하고 terraform apply를 수행하면

아래와 같이 default Network ACL, default Security Group이 자동 생성된다.

  • dev VPC의 default Network ACL
    • 4개의 subnet과 associated된 (0.0.0.0/0 모든포트로 in/out bound 모두 허용)
  • dev VPC의 default Security Group
    • 동일 security group내에서 모든포트로 inbound 허용
    • 0.0.0.0/0 모든포트로 outbound 허용

보안 – Amazon Virtual Private Cloud 페이지의 설명을 보면
AWS resource의 관점에서 SG를 1차 보안 계층(인스턴스 수준)으로, NACL을 2차 보안 계층(서브넷 수준)으로 나누고 있다.
또한 NACL의 경우 stateless하기 때문에 inbound로 들어온 트래픽의 응답도 별도로 ACL을 열어줘야 전송이 가능하다. SG의 경우 stateful하기 때문에 inbound만 열어주면 해당 요청의 응답이 별도 설정없이 가능해진다.

이대로 자동 생성된 NACL, SG를 사용할 수도 있지만, Terraform으로 리소스를 관리하기로 한 이상 default 리소스에 대해서도 Terraform으로 정의해서 사용하는 것이 좋겠다는 생각이 든다. 가급적이면 Terraform에 정의되지 않은 리소스는 AWS에 존재하지 않도록 하려고 생각하고 있다.
default NACL, SG의 terraform 정의는 @outsider 님의 Terraform으로 AWS VPC 생성하기 :: Outsider’s Dev Story 포스팅을 참고해서 작성했다.

Network ACL (NACL)

  • nacl.tf
resource "aws_default_network_acl" "dev_default" {
  default_network_acl_id = "${aws_vpc.dev.default_network_acl_id}"

  ingress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  egress {
    protocol   = -1
    rule_no    = 100
    action     = "allow"
    cidr_block = "0.0.0.0/0"
    from_port  = 0
    to_port    = 0
  }

  subnet_ids = [
    "${aws_subnet.public_1a.id}",
    "${aws_subnet.public_1c.id}",
    "${aws_subnet.private_1a.id}",
    "${aws_subnet.private_1c.id}",
  ]

  tags {
    Name = "dev-default"
  }
}

Security Group (SG)

  • security_group.tf
resource "aws_default_security_group" "dev_default" {
  vpc_id = "${aws_vpc.dev.id}"

  ingress {
    protocol  = -1
    self      = true
    from_port = 0
    to_port   = 0
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }

  tags {
    Name = "dev-default"
  }
}

Bastion

외부로부터 격리된 private subnet에 ssh로 접근하기 위해서 Bastion instance를 생성한다.
EC2 instance 생성을 위해서 AWS IAM – Users 메뉴에서 Add permissions 버튼을 클릭하고 다음과 같이 EC2 Full Access policy를 추가한다.
ec2 full access

필요에 따라 Bastion instance도 AZ별로 구축할 수 있지만, 이번에는 public-1a subnet에만 구축하도록 하겠다.
Bastion은 SG + EIP + EC2 instance를 하나의 세트로 보고 bastion.tf 파일에 관련 리소스를 모두 정의했다.

  • bastion.tf
resource "aws_security_group" "bastion" {
  name        = "bastion"
  description = "open ssh port for bastion"

  vpc_id = "${aws_vpc.dev.id}"

  ingress {
    from_port   = 22
    to_port     = 22
    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 {
    Name = "bastion"
  }
}

resource "aws_eip" "bastion_1a" {
  instance = "${aws_instance.bastion_1a.id}"
  vpc      = true
}

resource "aws_instance" "bastion_1a" {
  ami               = "${var.amazon_inux}"
  availability_zone = "ap-northeast-1a"
  instance_type     = "t2.nano"
  key_name          = "${var.dev_keyname}"

  vpc_security_group_ids = [
    "${aws_security_group.bastion.id}",
    "${aws_default_security_group.dev_default.id}",
  ]

  subnet_id                   = "${aws_subnet.public_1a.id}"
  associate_public_ip_address = true

  tags {
    Name = "bastion-1a"
  }
}

bastion instance는 SSH를 터널링하는 용도로만 사용하기 때문에 t2.nano로 정의했다. key_name에는 AWS EC2Key Pairs에서 생성한 Key 이름을 입력한다.
vpc_security_group_ids에는 bastion SG와 함께 dev VPC subnet의 다른 인스턴스(private subnet의 인스턴스)에 접속하기 위해 dev VPC default SG를 추가했다.

AMI 버전, dev VPC에서 사용할 key name을 한 곳에서 관리할 수 있도록 다음과 같이 variable로 정의해서 사용한다.

  • variable.tf
variable "amazon_inux" {
  # Amazon Linux AMI 2017.03.1 (HVM), SSD Volume Type - ami-4af5022c
  default = "ami-4af5022c"
}

variable "dev_keyname" {
  default = "2dal-dev"
}

Terraform fmt

terraform fmt 명령으로 tf파일의 포맷팅 표준에 맞게 수정할 수 있다.
명령 수행 후 수정된 파일 명이 화면에 출력된다.

$ terraform fmt
bastion.tf

Terraform plan

terraform plan을 실행해서 변경되는 내용에 대해서 리뷰한다.
포맷이 맞지 않거나, 존재하지 않는 resource type, name등을 사용하면 오류 메시지가 출력된다.

$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_default_network_acl.dev_default
...
Plan: 22 to add, 0 to change, 0 to destroy.

Terraform apply

이제 작성된 Terraform 파일을 terraform apply 명령으로 AWS에 적용한다.

$ terraform apply
...
Apply complete! Resources: 22 added, 0 changed, 0 destroyed.

terraform plan으로 한번 더 확인해 본다.

$ terraform plan
...
No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

No changes가 얼마나 반가운 메시지인지 곧 알게 될 것이다.

접속 테스트

다음과 같이 Bastion을 통해 ubuntu기반의 private instance에 접속해본다.

$ ssh -i ~/.ssh/2dal-dev.pem -A ec2-user@{Bastion IP} -t ssh ubuntu@{Private instance IP}
...
Are you sure you want to continue connecting (yes/no)? yes
...
Welcome to Ubuntu 16.04.2 LTS (GNU/Linux 4.4.0-1022-aws x86_64)

접속이 잘된다!

magic

정리

와! 이제 끝났다! 글쓰기를 시작해서 작성하고, 실패하고, 정리하고 몇 일이 걸린 것 같다.
기다려라 산토리 가쿠빈!

Terraform을 이용해서 Multi A-Z, public/private subnet으로 분리된 기본적인 VPC Network를 생성해봤다. 설명에 사용된 파일은 GitHub – asbubam/2dal-infrastructure 에서 확인할 수 있다.

VPC Network를 어떻게 설계하는가에 따라 큰 그림은 달라지겠지만 세부적인 AWS 리소스, association의 정의는 크게 다르지 않을 것으로 생각된다.

Terraform으로 리소스를 정의하다보면 GUI 콘솔에서는 보이지 않거나 자동으로 생성되서 크게 신경쓰지 않았던 리소스들에 대해서, 그리고 리소스와 리소스가 연결되는 관계에 대해서 명확하게 알게되는 장점이 있다. (이점이 Terraform의 가장 큰 어려움이면서 또한 장점이라고 생각된다.)

앞으로 다양한 리소스를 Terraform을 통해 관리해보고, 그 내용을 정리해 나갈 수 있으면 좋겠다.

참고자료

Provider: AWS – Terraform by HashiCorp
NAT 게이트웨이 – Amazon Virtual Private Cloud
보안 – Amazon Virtual Private Cloud
AWS VPC를 디자인해보자(2) – ACL과 Security Group을 활용한 보안 강화
Terraform으로 AWS VPC 생성하기 :: Outsider’s Dev Story
Terraform으로 VPC 설정하기
Terraform: AWS VPC with Private and Public Subnets — Nick Charlton

AWS VPC basic

최근 그림으로 배우는 클라우드 인프라와 API의 구조라는 책을 읽게 되면서, 클라우드 인프라에 대해 새롭게 알게 된(그동안 모르고 써왔던) 사실이 많이 있었다.

그중에서도 특히 그동안 어? 이렇게 하니까 되네! 하고 감으로 써왔던 AWS VPC(Virtual Private Cloud)에 대해 조금은 더 이해할 수 있게 된 것 같아 새롭게 알게된 내용들을 정리해 본다.

VPC 설명 전에 알고 있으면 좋은 키워드

  • 테넌트 (Tenent)
    클라우드 서비스 이용자가 갖게 되는 자신만의 격리된 환경 을 테넌트라고 한다.
    혹 다른 이용자와, 물리적으로는 하나의 서버를 공유하게 되더라도
    논리적으로 분리된 멀티 테넌트 환경에 의해서 클라우드 리소스를 보호 받을 수 있다.
    AWS에서는 하나의 관리자 계정(AWS 콘솔 루트 사용자)에 의해 관리되는 환경을 테넌트라 할 수 있다.

  • 리전 (Region)
    클라우드 인프라가 위치한 국가나 지역을 식별할 수 있도록 분리한 것을 리전이라고 한다.
    AWS에서는 미국 동/서부, 캐나다, 유럽, 아시아, 남미 중에서 리전을 선택해서 사용할 수 있다.
    (중국, 프랑스, 홍콩, 스웨덴 등이 준비 중이다.)
    2017년 9월 현재 Asia Pacific에는 싱가포르, 시드니, 서울, 도쿄, 뭄바이 리전이 존재한다.
    참고: AWS Region Table

  • AZ (Availability Zone)
    동일 리전안에서 리소스가 운용되는 데이터 센터를 Availability Zone(줄여서 AZ)으로 나눠서 관리한다.
    여러개의 AZ에 동일 리소스/서비스를 분산/배포해서, 특정 데이터 센터(zone)에서 발생하는 장애에 대비할 수 있다.
    동일 region안에서의 AZ간 발생하는 latency 문제는 low-latency links를 통해서 보장해준다고 한다.
    AWS의 컨디션이나 계정에 따라 특정 AZ의 사용이 제한될 수 있다.

    국내 대상 서비스에는 물리적으로 가까운 도쿄, 서울 리전이 주로 사용된다.
    AZ는 리전 코드식별 문자의 조합으로 표시된다.

    • 도쿄 리전은 다음의 3개의 AZ로 구분된다.
      • ap-northeast-1a
      • ap-northeast-1b
      • ap-northeast-1c
    • 서울 리전은 다음의 2개의 AZ로 구분된다.
      • ap-northeast-2a
      • ap-northeast-2c
  • CIDR (Classless Inter-Domain Routing)
    VPC resource는 CIDR로 IP대역을 정의한다.
    일반적으로 VPC는 private IP 대역인 다음 대역 내에서 CIDR을 정의한다.

    * 10.0.0.0 - 10.255.255.255 (10.0.0.0/8)
    * 172.16.0.0 - 172.31.255.255 (172.16.0.0/12)
    * 192.168.0.0 - 192.168.255.255 (192.168.0.0/16)
    

    여기서 / 뒤에 붙는 숫자 8, 12, 16은 IP의 앞에서부터 각각 8비트, 12비트, 16비트가 네트워크 주소로 사용 됨을 의미한다. 32비트로 이뤄진 ipv4 주소에서 네트워크 주소를 제외한 24, 20, 16비트가 호스트를 나타내는 주소로 사용된다.
    예를들면 10.1.1.0/24 의 경우
    2진수로 00001010.00000001.00000001.00000000/24 로 나타낼 수 있고
    여기서 24는 앞에서 24비트인 00001010.00000001.00000001를 고정하고 마지막 8비트를 변경가능한 IP 대역 (10.1.1.0 ~ 10.1.1.255)으로 사용하게된다.

VPC (Virtual Private Cloud)

  • Amazon Virtual Private Cloud 사용 설명서에 따르면, VPC사용자의 AWS 계정 전용 가상 네트워크를 의미한다.
  • VPC는 AWS 클라우드에서 다른 가상 네트워크와 논리적으로 분리되어 있고, Amazon EC2 인스턴스와 같은 AWS 리소스를 VPC에서 실행할 수 있다.
  • VPC는 반드시 하나의 Region에 종속되어 운영되며, 다수의 AZ를 이용하여 설계할 수 있다.
  • VPC에 단일 CIDR(Classless Inter-Domain Routing) 블록을 지정할 수 있다. 허용된 블록 크기는 /16 넷마스크 ~ /28 넷마스크이고, 따라서 VPC는 16 ~ 65,536개의 IP 주소를 포함할 수 있다.

물리 네트워크와 비교해보면…

VPC 메뉴의 각 항목을 설명하기 전에 물리 네트워크 예제를 간단히 이해하고 넘어가면, VPC의 각 항목이 각각 물리 네트워크의 어떤 부분에 매칭이 되는지 그려보면서 이해할 수 있어 도움이 된다.

아래는 Internet ISP에 연결된 공인 IP(210.1.22.33)를 가정용 공유기(Router)스위치를 이용해 사설(가상) 네트워크(192.168.0.0/24)로 공유해서 인터넷을 사용하는 간단한 홈랜 네트워크 구조이다.

homelan

[ 그림1 – 공유기를 사용하는 네트워크 ]

그림1 에서 파란색 점선은 private ip(virtual ip)로 통신하는 부분, 붉은색 점선은 public ip(real ip)로 통신하는 부분을 나타낸다.

가정에서 흔히 사용하는 공유기는 여러가지 기능을 제공하는데 이 중 VPC의 이해를 도와줄 공유기의 주요 기능을 살펴보면 다음과 같다.

  • 라우터 (Router)
    라우터는 서로 다른 네트워크간의 통신을 중계한다.
    여기서 다른 네트워크란 subnet mask가 다른 네트워크로 이해하면 쉽다.

  • 스위치 (Switch), DHCP
    전송 시에 사용하는 OSI 7 Layer에 따라 L2, L3, L4, L7으로 구분한다.
    공유기 내부에서 사용하는 스위치나, 저가에 판매되는 대부분의 스위치는 L2 스위치를 칭한다.
    각 포트에 연결된 MAC address를 기억하고 있다가 스위치로 들어온 요청에서 destination MAC address를 읽어, 해당 MAC address로 연결된 port로 데이터를 전송하는 역할을 한다.

  • NAT (Network Address Translation)
    외부 네트워크와 통신할 때는 반드시 public IP를 통해야 한다.
    그런데, 공유기 내부의 장비(PC)들은 private IP만 할당받았기 때문에, private IP만으로는 외부 네트워크와의 통신이 불가능하다.
    이때 공유기의 NAT기능을 통해서 외부로 나가는 요청은 public IP로 변환(IP Masquerading)되어 전송되고, 요청에 대한 응답이 오면 요청했던 내부 IP로 전달해주는 역할을 한다.

  • DHCP (Dynamic Host Configuration Protocol)
    공유기에 연결된 새로운 장비에 동적으로 IP 주소를 할당한다.

  • 방화벽 (Firewall)
    내부로 들어오는/외부로 나가는 IP/PORT를 특정 규칙으로 통제할(열거나 닫을) 수 있다.

그림1의 공유기를 사용하는 네트워크를 AWS VPC에 매칭해서 그려보면 다음과 같다.

AWS VPC

[ 그림2 – 공유기에 대응하는 AWS VPC 네트워크 ]

앞에서 설명했던 공유기의 주요기능을 다음과 같은 AWS resource가 대신한다고 볼 수 있다.

  • 라우터 (Router)
    Internet Gateway + Route Table이 라우터를 대신한다.
    Internet Gateway는 VPC마다 최대 하나씩 할당하며, VPC의 Internet연결을 위해 사용한다.
    Route Table은 (서브넷의) 네트워크 트래픽을 전달할 위치를 결정하는데 사용한다.

  • 스위치 (Switch), DHCP(Dynamic Host Configuration Protocol)
    AWS는 스위치를 별도로 구분하지 않고, Subnet이 (가상)스위치의 기능을 포함한다.
    가상 스위치는 가상 라우터와 가상 머신 인스턴스의 가상 NIC(Network Interface Card)이 연결되는 접점이된다.
    하나의 가상 스위치에 하나의 서브넷이 할당되는데, 여기서 서브넷은 가상 머신 인스턴스가 사용할 수 있는 사설 IP주소의 범위를 의미한다.
    서브넷에 연결된 인스턴스는 기동 시 DHCP를 통해 IP 주소를 할당받게 된다.

  • NAT (Network Address Translation)
    NAT instance나, NAT Gateway가 NAT를 대신한다.

  • 방화벽 (Firewall)
    NACL(Network ACL)Security Group이 방화벽을 대신한다.

이제 VPC의 세부 메뉴들을 살펴보자.

Virtual Private Cloud

  • Your VPCs
    VPC 목록을 조회할 수 있고, 각 VPC에 설정된 CIDR, DHCP 설정등을 변경할 수 있다.

  • Subnet
    가상 인스턴스가 사용할 수 있는 사설 IP 주소의 범위
    서브넷이 라우팅 테이블을 통해 인터넷 게이트웨이에 연결된 경우, 이를 퍼블릭 서브넷이라고 한다.
    VPC외부에서 바로 접근할 수 없는 서브넷을 프라이빗 서브넷이라 한다. 프라이빗 서브넷의 리소스가 외부에 접촉하기 위해서는 반드시 퍼블릭 서브넷(NAT, bastion, ELB 등)을 통과해야 한다.

  • Route Tables
    subnet에서 outbound로 나가는 트래픽의 destination(ip대역), target(local, IGW, NAT등)을 정의한다.
    VPC를 생성하면 자동으로 기본 라우팅 테이블을 생성한다.

  • Internet Gateways
    VPC가 인터넷에 연결되기 위해서는 Internet Gateway가 반드시 필요하다. 줄여서 IGW로 표현하기도 한다.
    NAT 와 IGW의 차이점에 대한 질문에서
    IGW는 벽에 나있는 랜포트로 생각하고 NAT는 가정용 공유기를 생각하면 된다는 답변이 인상깊었다.

  • Egress Only Internet Gateways
    outbound만 허용하는 IGW

  • DHCP Options Sets
    VPC를 생성하면 자동으로 DHCP 옵션 세트가 생성되어 VPC에 연결된다.
    추가로 설정하는 경우는 아직 경험해 보지 못했다.

  • Elastic IPs
    Elastic IP (public 고정 아이피), 줄여서 EIP로 표현하기도 한다.
    모든 인스턴스 또는 네트워크 인터페이스에 EIP를 부여할 수 있다.
    실행 중인 인스턴스의 경우 1개의 EIP를 무료로 사용할 수 있다.
    실행 중이 아닌 상태로 EIP가 연결된 경우, 혹은 1개의 인스턴스에 2개이상의 EIP를 부여한 경우 1개를 초과하는 추가 EIP에 대해 요금이 부과된다.

  • Endpoints
    VPC endpoint를 사용하면 NAT 디바이스나 VPN 연결 또는 AWS Direct Connect를 통해 인터넷에 액세스하지 않고도 VPC와 다른 AWS 서비스 간에 프라이빗 연결을 생성할 수 있다.
    현재로서는 엔드포인트와 Amazon S3 및 DynamoDB의 연결만 지원된다.

  • NAT Gateways
    기존에는 NAT용 AMI를 사용한 NAT instance를 주로 사용했으나 NAT Gateway가 출시되고
    VPC에서 NAT연결은 NAT Gateway사용을 권장한다.
    프라이빗 서브넷에서 외부(인터넷)에 접근하기 위해서 사용한다.
    NAT인스턴스는 인스턴스의 스펙에 의해 트래픽을 제한 받지만, NAT 게이트웨이를 사용할 경우 10Gbps까지 트래픽을 처리할 수 있다고 한다.

  • Peering Connections
    VPC to VPC의 연결을 관리한다.
    내 계정 또는, 다른 계정이 소유하고 있는 VPC에 연결할 수 있다.

Security

  • Network ACLs
    subnet의 IN/OUT bound를 정의한다.
    허용 규칙만 지원
    instance간 서브넷이 다른 경우 NACL이 적용 됨
    Stateless필터링: 요청 정보를 저장하지 않기 때문에 in/out모두 정의 해야 함

  • Security Groups
    instance의 IN/OUT bound를 정의한다.
    허용, 금지 규칙을 지원
    instance간 서브넷이 같은 경우 Security Group만 적용 됨
    Stateful필터링: 요청 정보를 저장하기 때문에 out을 별도로 정의하지 않아도 됨

VPN Connections

  • Customer Gateways
    VPN 연결을 위해 고객 측에 설치된 물리적 디바이스 또는 소프트웨어 애플리케이션.

  • Virtual Private Gateways
    AWS 내부에서 외부 VPN에 연결하기 위해 사용하는 게이트웨이

  • VPN Connections
    Customer Gateway와 Virtual Private Gateway를 연결하면 하나의 VPN Connection이 생성된다.

VPC 메뉴에서 배웠던 개념을 활용해서 VPC 네트워크를 설계해보자.

AWS VPC2

  • 두개의 AZ위에 public / private subnet으로 구분한 서브넷을 올리고 public subnet에는 외부로 나가는 트래픽을 위해 NAT – IGW를 연결했다. 그림에는 보이지 않지만 각각의 연결단계에서 라우팅테이블로 아웃바운드 룰을 정의한다.

  • 바스티온은 private subnet에 위치한 인스턴스에 SSH등으로 접근하기 위해 사용하는 서버를 칭한다.

  • public subnet에 ELB 혹은 ALB를 셋업해 외부에서 private subnet에 운영 중인 서비스에 접근할 수 있도록 할 수 있다.

다음 편 예고

  • 위에서 배운 개념을 토대로 terraform을 사용해서 코드로 VPC network를 구성해 본다.

참고자료

공식: Amazon VPC란 무엇인가?
그림으로 배우는 클라우드 인프라와 API의 구조
AWS VPC를 디자인해보자 – ㅍㅍㅋㄷ 블로그
Terraform으로 VPC 설정하기 – 송은우의 언어들
Tefraform으로 AWS VPC 생성하기 – outsider님 블로그
Difference between Internet gateway and NAT gateway in VPC?