Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.

기술 뉴스 #82 : 17-07-15

웹개발 관련

  • GraphQL vs. REST : GraphQL과 REST의 유사한 점과 차이점을 비교한 글이다. 리소스 정의를 각각이 어떻게 하는지, URL 라우팅과 GraphQL의 스키마가 어떻게 다른지, Rest의 핸들러와 GraphQL의 리졸버는 어떻게 구현하고 차이점이 무엇인지를 설명하고 있어서 간략히 GraphQL의 특징을 이해하기 좋다.(영어)
  • IntersectionObserver를 이용한 이미지 동적 로딩 기능 개선 : 이미지가 많은 문서의 경우 이미지가 화면에 보일 때 동적으로 로딩하는 방식을 많이 사용하는데 제대로 구현하기가 쉽지 않은 게 사실이다. 크롬 51부터 추가된 IntersectionObserver API를 이용해서 특정 DOM이 화면에 표시되는 지 여부를 구현하는 방법을 설명하고 있다. 예제도 포함되어 있어서 화면에 보이는 요소를 얼마나 쉽게 검사할 수 있는지 잘 설명되어 있다.(한국어)
  • Getting Into HNPWA with Next.js (HNPWA 앱개발 Next.js 로 시작하기) : HNPWA는 해커뉴스를 PWA로 만들어서 예제로 공유하는 사이트인데 Next.js를 이용해서 해커뉴스 앱을 PWA로 만드는 과정부터 Ligthhouse 등으로 성능을 개선하는 부분까지 PWA 앱을 만드는 전체 과정이 잘 설명되어 있다. 전체 예시 코드도 제공되고 있으므로 PWA나 Next.js에 대한 예제 코드로 활용하기 좋다.(한국어)
  • React, Relay and GraphQL: Under the Hood of the Times Website Redesign : NY Times에서 새롭게 웹사이트를 개편하면서 다양한 언어와 서비스로 구성된 웹사이트를 GraphQL, React, Relay로 어떻게 구현했는지 설명한 글이다. 기술적으로는 간단히 GraphQL, Relay를 소개하는 느낌에 가깝고 Relay Classic에서 새로 나온 Modern으로 업그레이드하는 과정에서 나온 kyt라는 프로젝트 관리 도구도 함께 소개하고 있다.(영어)
  • Standard ECMA-262 ECMAScript® 2017 Language Specification 8th edition : ES2017(ES8)의 명세가 확정되었다.(영어)
  • MDN’s new design is in Beta : 웹 기술 관련 레퍼런스 문서로 가장 좋다고 생각하는 MDN이 새로운 문서 디자인을 베타로 적용해서 테스트하고 있다. MDN에 로그인 후 프로필에서 베타테스터로 설정하면 이용할 수 있다.(영어)

그 밖의 프로그래밍 관련

  • Diff Monster를 소개합니다 : GitHub의 Pull Request를 리뷰할 때 부족한 GitHub의 기능을 보충하기 위해서 파일 리스트를 쉽게 보고 파일별로 리뷰 완료를 확인하는 등 코드 리뷰를 편하게 할 수 있게 도와주는 웹 프로젝트.(한국어)
  • Introducing npx: an npm package runner : npm v5.2.0부터 포함된 npx를 설명한 글이다. 요즘은 npm 모듈을 글로벌로 설치하지 않고 로컬에 설치하고 사용하는게 일반적인데 이때 실행파일을 실행하려면 npm scripts에 추가해서 사용하는데 npx는 package.json를 수정하지 않고 바로 실행할 수 있다. 여기에 추가적으로 설치되어 있지 않은 npm 모듈도 실행하면 알아서 찾아서 임시로 설치해서 실행해 주므로 제너레이터 등을 사용할 때 좋다.(영어)
  • Universal Now: Now, on Every Cloud : Zeit에서 바로 서버에 배포할 수 있는 도구로 만든 now가 이제 유니버설 인터페이스를 제공해서 AWS, GCP, Azure에 배포할 수 있게 되었다. 각 클라우드의 Serverless 기능을 이용하는데 AWS의 경우 Lambda와 API Gateway를 이용한다. 서버리스 도구에서 강력한 도구가 새로 나온 느낌이다.(영어)
  • Go Reliability and Durability at Dropbox, Tammy Butow : Dropbox가 현재 golang을 어떻게 사용하고 있고 어떻게 도입했는지를 설명한 글이다. golang을 쓰기 시작하면서 많은 부분을 golang으로 바꾸어서 15개 팀 이상이 golang을 쓰고 있고 130만 라인 이상의 코드를 작성했다. 현재 아주 성공적으로 도입해서 사용하고 있고 새로운 사람이 golang에 적응할 수 있도록 스타일가이드를 알려주고 코드리뷰를 진행하면서 애플리케이션을 작성해보도록 한다고 한다.(영어)
  • Instagram Makes a Smooth Move to Python 3 : Instagramp이 10개월의 작업 끝에 Python 2에서 3으로 완전히 넘어갔는데 그 과정에 대해 인스타그램 개발자 2명과 인터뷰한 내용이다. 3~4달 동안 사용하지 않는 코드와 3가 지원하지 않는 패키지를 제거하고 2달 동안 유닛테스트를 작성한 뒤 프로덕션 코드를 4달 동안 바꿔서 현재는 완전히 Python 3으로 바꿨다고 한다. 성능에 대한 기대는 하지 않았는데 CPU, 메모리 모두 줄어드는 효과를 보았다고 한다.(영어)
  • adidas APIs : Adidas에서 RESTful API를 구축하면서 각 팀과 협의하고 가이드라인을 만들 과정을 설명한 글이다. 이 과정에서 만들어진 API 가이드라인을 공개했는데 정리가 잘 되어 있어서 API를 설계할 때 참고하기 좋다.(영어)

볼만한 링크

  • 모든 개발자들이 쉬는 시간에도 개발을 하는 것은 아니다 : 모든 개발자가 쉬는 시간이나 여가에 개발하고 트랜드를 공부하는 것은 아니라는 자신에게 맞게 시간을 보내면서 스트레스받지 말자는 자신의 얘기를 적은 글이다. 나는 쉬는 시간에 개발하는 쪽이긴 하지만 더 즐거운 일을 하자는 부분에는 동의한다. 나는 그게 더 즐거울 뿐...(한국어)
  • 데이터를 얻으려는 노오오력 : Bapul의 김영재 님이 교육 서비스에서 데이터를 억기 위해서 수년간 한 작업과 그 결과가 정리된 발표자료이다. 단순히 하나의 내부 프로젝트가 아니라 수년간 서비스 내에서 사용자에게 좋은 데이터를 수집하고 그 인사이트로 서비스를 개선하고 다시 개선하는 과정이 다 나와 있다. 데이터를 수집하기 위해서 내부 알고리즘과 UX 등을 수개월에 걸쳐서라도 개선하고 이런 데이터를 수집하고 분석하기 위해서 취해야 하는 관점 등이 나와 있는데 실제 경험을 기반으로 한 내용이라서 공감도 되고 발표 흐름이 잘 정리되어 있어서 이해하기도 좋다.(한국어)
  • 그로스 해킹 – 이보다 더 과학적일 수 없다 : Qubit이라는 마케팅 분석 플랫폼 스타트업에서 수천 개의 그로스 해킹 실험 결과를 발표한 내용을 정리 요약한 글이다. 영어고 글이 길어서 자세히 보기 어려운데 각 실험에서 어느 정도의 효과가 있는지 이 글을 통해서 알 수 있다.(한국어)

IT 업계 뉴스

프로젝트

  • bundlesize : npm 패키지로 빌드 후 특정 파일의 사이즈를 검사한 지 GitHub의 훅으로 알려주는 라이브러리. Travis CI, CircleCI에서 동작한다.
  • Babylon.js : WebGL 자바스크립트 프레임워크로 3.0부터 WebGL 2를 지원한다.
  • Uppy : 웹브라우저용 파일 업로더
  • Highlight : 키노트 등에 Syntax Highlight를 자동으로 해주는 애플리케이션.
  • Bash-Snippets : 다양한 Bash 스크립트를 모아놓은 저장소.
  • API Security Checklist : API 보안을 확인해 볼 수 있는 체크리스트로 한글로도 제공한다.

버전 업데이트

2017/07/15 22:15 2017/07/15 22:15

Terraform으로 AWS VPC 생성하기

회사에서 Terraform을 계속 사용하고 있어서 연습 겸 개인 AWS 인프라도 모두 Terraform으로 최근에 갈아탔다. 어차피 새로 구성하는 것이므로 큰 부담 없이 테스트로 사용했다.

VPC

VPC는 Virtual Private Cloud를 의미한다. AWS에서 EC2 서버나 다른 리소스를 사용하려면 먼저 VPC를 생성해야 한다. 이 VPC로 AWS 내에서 사용할 내부 CIDR 대역을 지정하고 내부 네트워크를 구성할 수 있다. 이렇게 만든 VPC 내에서 외부에 점근 등을 제어할 수 있다. 이 글은 VPC의 개념까지 설명하기에는 어려우므로 AWS 문서AWS VPC를 디자인해보자를 읽어보자. 나 같은 경우에는 후자의 글이 꽤 많은 도움이 되었다.

Terraform으로 구성할 VPC

여기서는 Terraform으로 VPC를 구성하는 방법을 설명한다. Terraform으로 AWS 등의 리소스를 구성하는 것은 문서를 찾아가면서 손수 정의해야 하므로 꽤 고통스러운 작업이지만 Terraform으로 구성을 해보면 AWS 리소스에 대한 이해도가 아주 높아지는 것을 느낄 수 있다. API 등을 이용할 때도 비슷하겠지만 AWS 웹 콘솔에서 VPC를 만들면 그 개념을 잘 몰라도 GUI가 안내를 해주거나 빠진 부분을 설명해 주무로 대충 만지다 보면 구성할 수 있다.

Terraform으로 구성하려면 웹 콘솔보다 훨씬 세부적인 리소스로 나누어지고 각 리소스 간의 관계를 직접 지정해 주어야 하므로 서로 어떤 관계를 갖는지 이해도가 없으면 만들기가 어렵다. 그래서 Terraform으로 직접 리소스를 정의하다 보면 각 리소스에 대한 용도와 관계를 어쩔 수 없이 공부하게 되고 자연히 이해도가 높아진다. 이전 Terraform으로 AWS 관리하기에서 간단한 VPC를 만들어서 예시로 사용했지만, 실제 서비스하는 VPC를 만들려면 상당히 많은 리소스가 필요하다. VPC를 어떻게 구성하는가는 다양한 접근 방법이 가능하지만 가장 많이 사용하는 시나리오가 AWS 문서에 잘 나와 있다.

VPC는 직접 구성하는 것이므로 필요에 따라 원하는 대로 만들 수 있다. 여기서 만들 VPC는 AWS 문서에 나와 있는 퍼블릭 서브넷과 프라이빗 서브넷이 있는 VPC(NAT)의 구조와 가장 가까운 형태이고 특별한 요구사항이 없다면 가장 일반적인 구조라고 생각한다.

VPC 구성도

정확한 형태를 그리면 위와 같은 형태가 된다. 이 그림에도 많은 개념이 들어가 있는데 이글에서 모두 설명하기는 쉽지 않다. Terraform으로 구성하기 전에 VPC에 대해 어느 정도 이해를 하고 있어야 하므로 간단히 특징 부분만 설명한다.

  • Region 안에는 Availability Zone(AZ)이 여러 개 있다. 여기서는 2개를 사용하는데 보통 이중화를 할 때 다른 AZ에 같은 서버 및 구성을 두어 한쪽 AZ에 장애가 나더라도 문제없게 한다. 그래서 이 그림에서도 양쪽에 두 개의 AZ가 있다.
  • VPC를 생성하고 모든 자원은 이 VPC 안에 만든다.
  • VPC안에 퍼블릭 서브넷과 프라이빗 서브넷을 2개씩 만든다. 2개인 이유는 AZ마다 하나씩 만들기 때문이다.

    • 퍼블릭 서브넷은 외부에서 접근할 수 있고 내부에서도 VPC 밖의 인터넷으로 접근할 수 있는 서브넷이다. 퍼블릭 IP로 접근해야 하는 서버 등은 여기에 띄워야 한다.
    • 프라이빗 서브넷은 외부에서는 접근할 수 없고 VPC 내에서만 접근할 수 있다. 대부분의 서비스 서버는 여기에 둔다.
  • 퍼블릭 서브넷

    • 서브넷앞에 Network ACL을 둔다. Network ACL로 오가는 트래픽을 모두 제어할 수 있다.
    • Network ACL 앞에 Route Table을 둔다. 이는 Subnet 내의 트래픽을 어디로 갈지 정하는데 VPC의 CIDR은 모두 내부를 보도록 하고 외부로 나가는 트래픽은 Internet Gateway로 보내도록 한다.
    • Internet Gateway를 통해서 퍼블릭 서브넷의 아웃바운드 트래픽이 외부 인터넷으로 연결된다.
  • 프라이빗 서브넷

    • 이 서브넷에 RDS나 EC2 인스턴스를 둔다. 서버 자체는 외부에서 아예 접근이 안 되고 서비스는 ELB를 통해서 공개하므로 여기에 두는 것이 좋다.
    • 똑같이 서브넷 앞에 Network ACL과 Route Table을 둔다.
    • 프라이빗 서브넷은 외부에서 접근할 수 있지만 서브넷 내에서 외부에 접근은 가능해야 한다. 패키지를 설치하거나 소스를 가져오거나... 기본적으로 프라이빗 서브넷은 막혀 있으므로 Route Table로 아웃바운드 트래픽을 NAT Gateway로 연결한다.
    • NAT Gateway도 AWS에서 서비스로 제공하는데 이를 퍼블릭 서브넷 안에 만들어 두고 프라이빗 서브넷의 아웃바운드 트래픽은 이 NAT Gateway를 통해서 외부 인터넷으로 나가게 된다.
  • Bastion host

    • 바스티온 호스트는 네트워크에 접근하기 위한 서버를 의미한다.
    • VPC 자체를 네트워크단에서 접근을 제어하고 있으므로 퍼블릭 서브넷에 바스티온 호스트를 만들어두고 외부에서 SSH 등으로 접근할 수 있는 서버는 이 서버가 유일하다.
    • 프라이빗 서브넷이나 VPC 내의 자원에 접근하려면 바스티온 호스트에 접속한 뒤에 다시 접속하는 방식으로 사용한다.
    • Bastion Host도 이중화해서 AZ마다 한대씩 만들어 둘 수 있다.

Terraform으로 VPC 구성하기

Terrafrom으로 VPC를 구성해 보자.

resource "aws_vpc" "side_effect" {
  cidr_block  = "10.10.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support = true
  instance_tenancy = "default"

  tags {
    "Name" = "side effect"
  }
}

aws_vpc 리소스로 VPC를 정의한다. 여기서 side_effect라는 이름은 임의로 준 이름이다.(내 개인 프로젝트용 도메인이이라서...) 리소스 이름은 맘대로 사용할 수 있는데 나중에 aws_vpc.side_effect.id, aws_subnet,side_effect.id 같은 식으로 참조해서 사용하므로 같은 리소스 내에서만 이름이 충돌하지 않으면 된다. 그래서 여러 이름이 필요하지 않으면 연관된 리소스는 같은 이름을 쓰거나 접두사 형식으로 사용한다. 그리고 Terraform의 리소스 명이 모두 스네이크케이스(_로 단어를 이어붙이는..)를 사용하기 때문에 이름도 같은 방식을 사용하고 있다.

aws_vpc는 VPC를 하나 만든 것이다. 원하는 설정을 넣고 CIDR 대역을 10.10.0.0/16로 지정했다.

resource "aws_default_route_table" "side_effect" {
  default_route_table_id = "${aws_vpc.side_effect.default_route_table_id}"

  tags {
    Name = "default"
  }
}

aws_default_route_table는 좀 특수한 리소스이다. AWS에서 VPC를 생성하면 자동으로 route table이 하나 생긴다. 이는 Terraform으로 직접 생성하는 것이 아니므로 aws_default_route_table는 route table을 만들지 않고 VPC가 만든 기본 route table을 가져와서 Terraform이 관리할 수 있게 한다. 이 Route Table에 이름을 지정하고 관리하게 두기 위해서 Terraform으로 가져왔고 이 테이블과의 연결은 VPC에서 사용하는 다른 속성을 사용할 예정이다. 이는 뒤에서 좀 더 설명한다.

// public subnets
resource "aws_subnet" "side_effect_public_subnet1" {
  vpc_id = "${aws_vpc.side_effect.id}"
  cidr_block = "10.10.1.0/24"
  map_public_ip_on_launch = false
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  tags = {
    Name = "public-az-1"
  }
}

resource "aws_subnet" "side_effect_public_subnet2" {
  vpc_id = "${aws_vpc.side_effect.id}"
  cidr_block = "10.10.2.0/24"
  map_public_ip_on_launch = true
  availability_zone = "${data.aws_availability_zones.available.names[1]}"
  tags = {
    Name = "public-az-2"
  }
}

서브넷은 aws_subnet으로 만드는데 앞에서 말한 대로 퍼블릭 서브넷을 2개 만들고 각각 CIDR 대역을 지정했다. 그리고 퍼블릭 서브넷이므로 서버 등을 띄울 때 자동으로 퍼블릭 IP가 할당되도록 map_public_ip_on_launch를 지정했다.

availability_zone으로 두 서브넷이 다른 AZ에 생성하도록 했다.

> data.aws_availability_zones.available.names
[
  ap-northeast-1a,
  ap-northeast-1c
]

data "aws_availability_zones" "available" {}와 같은 데이터를 지정하면 아래와 같이 해당 리전의 AZ 이름을 가져올 수 있다. 여기서는 이 배열의 첫 번째와 두 번째를 각각 지정해서 이름에서 오타가 발생하거나 이름을 기억할 필요가 없게 한 것이다.

// private subnets
resource "aws_subnet" "side_effect_private_subnet1" {
  vpc_id = "${aws_vpc.side_effect.id}"
  cidr_block = "10.10.10.0/24"
  availability_zone = "${data.aws_availability_zones.available.names[0]}"
  tags = {
    Name = "private-az-1"
  }
}

resource "aws_subnet" "side_effect_private_subnet2" {
  vpc_id = "${aws_vpc.side_effect.id}"
  cidr_block = "10.10.11.0/24"
  availability_zone = "${data.aws_availability_zones.available.names[1]}"
  tags = {
    Name = "private-az-2"
  }
}

퍼블릭 서브넷과 똑같이 프라이빗 서브넷을 2개 만들었다.

resource "aws_internet_gateway" "side_effect_igw" {
  vpc_id = "${aws_vpc.side_effect.id}"
  tags {
    Name = "internet-gateway"
  }
}

aws_internet_gateway로 VPC에서 외부 인터넷에 접근하기 위한 인터넷 게이트웨이를 만들었다. 인터넷 게이트웨이를 AWS에서 제공하므로 이를 VPC 안에 만들면 된다.

// route to internet
resource "aws_route" "side_effect_internet_access" {
  route_table_id = "${aws_vpc.side_effect.main_route_table_id}"
  destination_cidr_block = "0.0.0.0/0"
  gateway_id = "${aws_internet_gateway.side_effect_igw.id}"
}

aws_route는 Route Table에 라우팅 규칙을 추가하는 리소스이다. 이 테이블을 aws_vpc.side_effect.main_route_table_id로 Route Table에 추가했다. main_route_table_id는 VPC의 기본 Route Table을 의미하고 이는 앞에서 살펴본 aws_default_route_table와 같은 테이블이다. aws_default_route_table.side_effect.id를 사용해도 같은 값이지만 의미가 더 명확해 보이는 main_route_table_id를 사용했다.

// eip for NAT
resource "aws_eip" "side_effect_nat_eip" {
  vpc = true
  depends_on = ["aws_internet_gateway.side_effect_igw"]
}

// NAT gateway
resource "aws_nat_gateway" "side_effect_nat" {
  allocation_id = "${aws_eip.side_effect_nat_eip.id}"
  subnet_id = "${aws_subnet.side_effect_public_subnet1.id}"
  depends_on = ["aws_internet_gateway.side_effect_igw"]
}

프라이빗 서브넷에서 외부 인터넷으로 요청을 내보낼 수 있도록 하는 NAT 게이트웨이다. NAT 게이트웨이에서 사용할 elastic IP를 하나 만들고 aws_nat_gateway에서 이 IP를 연결하고 퍼블릭 서브넷에 만들어지도록 했다. 둘다 인터넷 게이트웨이가 만들어진 뒤에 구성하려고 aws_nat_gateway에 의존성을 지정했다.

// private route table
resource "aws_route_table" "side_effect_private_route_table" {
  vpc_id = "${aws_vpc.side_effect.id}"
  tags {
    Name = "private"
  }
}

resource "aws_route" "private_route" {
  route_table_id = "${aws_route_table.side_effect_private_route_table.id}"
  destination_cidr_block = "0.0.0.0/0"
  nat_gateway_id = "${aws_nat_gateway.side_effect_nat.id}"
}

프라이빗 서브넷에서 사용할 Route Table을 만들고 여기서 0.0.0.0/0으로 나가는 요청이 모두 NAT 게이트웨이로 가도록 설정했다.

// associate subnets to route tables
resource "aws_route_table_association" "side_effect_public_subnet1_association" {
  subnet_id = "${aws_subnet.side_effect_public_subnet1.id}"
  route_table_id = "${aws_vpc.side_effect.main_route_table_id}"
}

resource "aws_route_table_association" "side_effect_public_subnet2_association" {
  subnet_id = "${aws_subnet.side_effect_public_subnet2.id}"
  route_table_id = "${aws_vpc.side_effect.main_route_table_id}"
}

resource "aws_route_table_association" "side_effect_private_subnet1_association" {
  subnet_id = "${aws_subnet.side_effect_private_subnet1.id}"
  route_table_id = "${aws_route_table.side_effect_private_route_table.id}"
}

resource "aws_route_table_association" "side_effect_private_subnet2_association" {
  subnet_id = "${aws_subnet.side_effect_private_subnet2.id}"
  route_table_id = "${aws_route_table.side_effect_private_route_table.id}"
}

위에서 Route Table을 2개 만들었는데 이를 각각 퍼블릭/프라이빗 서브넷에 연결하는 과정이다. 퍼블릭 서브넷에는 메인 Route Table을 연결하고 Private 용으로 만들 Route Table은 프라이빗 서브넷에 연결했다.

// default security group
resource "aws_default_security_group" "side_effect_default" {
  vpc_id = "${aws_vpc.side_effect.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 = "default"
  }
}

aws_default_security_group은 VPC를 만들면 자동으로 만들어지는 기본 시큐리티 그룹을 Terraform에서 관리할 수 있도록 지정한 것이다. 이 리소스는 Terraform이 생성하지 않고 정보만 가져와서 연결한다.

resource "aws_default_network_acl" "side_effect_default" {
  default_network_acl_id = "${aws_vpc.side_effect.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
  }

  tags {
    Name = "default"
  }
}

aws_default_network_acl도 VPC를 만들 때 기본으로 만들어지는 네트워크 ACL이다. 이름을 지정하고 관리하기 위해서 가져왔지만, 이 ACL은 어떻게 설정해서 다뤄야 할지 애매해서 가져온 채로만 두고 그대로 놔두었다. 각 서브넷에서 사용할 네트워크 ACL을 추가로 만들어서 사용했다.

// network acl for public subnets
resource "aws_network_acl" "side_effect_public" {
  vpc_id = "${aws_vpc.side_effect.id}"
  subnet_ids = [
    "${aws_subnet.side_effect_public_subnet1.id}",
    "${aws_subnet.side_effect_public_subnet2.id}",
  ]

  tags {
    Name = "public"
  }
}

퍼블릭 서브넷에서 사용할 네트워크 ACL을 생성해서 퍼블릭 서브넷에 연결했다.

resource "aws_network_acl_rule" "side_effect_public_ingress80" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 100
  rule_action = "allow"
  egress = false
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 80
  to_port = 80
}

resource "aws_network_acl_rule" "side_effect_public_egress80" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 100
  rule_action = "allow"
  egress = true
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 80
  to_port = 80
}

resource "aws_network_acl_rule" "side_effect_public_ingress443" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 110
  rule_action = "allow"
  egress = false
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 443
  to_port = 443
}

resource "aws_network_acl_rule" "side_effect_public_egress443" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 110
  rule_action = "allow"
  egress = true
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 443
  to_port = 443
}

resource "aws_network_acl_rule" "side_effect_public_ingress22" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 120
  rule_action = "allow"
  egress = false
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 22
  to_port = 22
}

resource "aws_network_acl_rule" "side_effect_public_egress22" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 120
  rule_action = "allow"
  egress = true
  protocol = "tcp"
  cidr_block = "${aws_vpc.side_effect.cidr_block}"
  from_port = 22
  to_port = 22
}

resource "aws_network_acl_rule" "side_effect_public_ingress_ephemeral" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 140
  rule_action = "allow"
  egress = false
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 1024
  to_port = 65535
}

resource "aws_network_acl_rule" "side_effect_public_egress_ephemeral" {
  network_acl_id = "${aws_network_acl.side_effect_public.id}"
  rule_number = 140
  rule_action = "allow"
  egress = true
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 1024
  to_port = 65535
}

퍼블릭 서브넷용 네트워크 ACL에 규칙을 추가한 부분이다. 네트워크 ACL을 기본적으로는 모든 포트가 막혀있으므로 필요한 부분을 열어야 하므로 80, 443, 22, ephemeral 포트를 인바운드/아웃바운드로 열어서 규칙을 추가했다.

// network acl for private subnets
resource "aws_network_acl" "side_effect_private" {
  vpc_id = "${aws_vpc.side_effect.id}"
  subnet_ids = [
    "${aws_subnet.side_effect_private_subnet1.id}",
    "${aws_subnet.side_effect_private_subnet2.id}"
  ]

  tags {
    Name = "private"
  }
}

프라이빗 서브넷에서 사용할 네트워크 ACL을 만들어서 서브넷에 연결했다.

resource "aws_network_acl_rule" "side_effect_private_ingress_vpc" {
  network_acl_id = "${aws_network_acl.side_effect_private.id}"
  rule_number = 100
  rule_action = "allow"
  egress = false
  protocol = -1
  cidr_block = "${aws_vpc.side_effect.cidr_block}"
  from_port = 0
  to_port = 0
}

resource "aws_network_acl_rule" "side_effect_private_egress_vpc" {
  network_acl_id = "${aws_network_acl.side_effect_private.id}"
  rule_number = 100
  rule_action = "allow"
  egress = true
  protocol = -1
  cidr_block = "${aws_vpc.side_effect.cidr_block}"
  from_port = 0
  to_port = 0
}

resource "aws_network_acl_rule" "side_effect_private_ingress_nat" {
  network_acl_id = "${aws_network_acl.side_effect_private.id}"
  rule_number = 110
  rule_action = "allow"
  egress = false
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 1024
  to_port = 65535
}

resource "aws_network_acl_rule" "side_effect_private_egress80" {
  network_acl_id = "${aws_network_acl.side_effect_private.id}"
  rule_number = 120
  rule_action = "allow"
  egress = true
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 80
  to_port = 80
}

resource "aws_network_acl_rule" "side_effect_private_egress443" {
  network_acl_id = "${aws_network_acl.side_effect_private.id}"
  rule_number = 130
  rule_action = "allow"
  egress = true
  protocol = "tcp"
  cidr_block = "0.0.0.0/0"
  from_port = 443
  to_port = 443
}

프라이빗 서브넷용 네트워크 ACL을 위한 규칙으로 VPC 내에서는 모든 포트를 열고(더 엄격히 갈 수도 있지만, 개인용이라 좀 편하게 설정했다.) NAT으로 들어오는 요청과 80, 443으로 나가는 요청을 규칙으로 추가해서 열어주었다.

// Basiton Host
resource "aws_security_group" "side_effect_bastion" {
  name = "bastion"
  description = "Security group for bastion instance"
  vpc_id = "${aws_vpc.side_effect.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"
  }
}

바스티온 호스트에서 사용할 시큐리티 그룹을 하나 만들었다. 바스티온 호스트에 접속 가능한 IP 대역을 지정할 수도 있지만 나는 어디서 접근할지 알 수 없으므로 전체로 22 포트를 여는 시큐리티 그룹을 만들었다.

resource "aws_instance" "side_effect_bastion" {
  ami = "${data.aws_ami.ubuntu.id}"
  availability_zone = "${aws_subnet.side_effect_public_subnet1.availability_zone}"
  instance_type = "t2.nano"
  key_name = "YOUR-KEY-PAIR-NAME"
  vpc_security_group_ids = [
    "${aws_default_security_group.side_effect_default.id}",
    "${aws_security_group.side_effect_bastion.id}"
  ]
  subnet_id = "${aws_subnet.side_effect_public_subnet1.id}"
  associate_public_ip_address = true

  tags {
    Name = "bastion"
  }
}

resource "aws_eip" "side_effect_bastion" {
  vpc = true
  instance = "${aws_instance.side_effect_bastion.id}"
  depends_on = ["aws_internet_gateway.side_effect_igw"]
}

바스티온 호스트를 EC2 인스턴스로 띄웠다. 바스티온 호스트는 중계 역할 밖에 안 하므로 간단히 t2.nano로 퍼블릭 서브넷에 EC2 인스턴스를 띄웠다. 이 인스턴스는 data.aws_ami.ubuntu.id로 우분투 AMI의 ID를 가져와서 사용했는데 원하는 AMI ID를 지정해서 사용해도 된다. 바스티온 호스트의 IP가 서버 바꿀 때마다 바뀌면 피곤하므로 Elastic IP를 하나 만들어서 바스티온 호스트에 접속했다.

이 구성을 다 만드는데 꽤 많은 삽질이 필요했지만 한번 만들고 나니까 변경하면서 관리하기는 꽤 쉬운 상태가 되었다. 필요하다면 이를 복사해서 다른 VPC를 만들거나 다른 리전에 VPC를 생성하는 것도 어렵지 않게 됐다. 이 VPC 구성의 전체 파일은 GitHub 저장소에 올려두었다. 이후 관리하면서 수정이 생길 수 있으므로 이 글과 완전히 일치하지 않을 수 있다.

2017/07/14 23:50 2017/07/14 23:50