2. Spoon Radio Infra Member
● 노가다를 싫어하는 노땅 엔지니어 2명
○ 최상기(Martin): https://www.linkedin.com/in/sanggi-choi/
○ 백영진(Paul): https://www.linkedin.com/in/youngjin-baek-8a2a74a4/
● 묵묵히 열심히 겁나게 달려주는 엔지니어 1명
○ 박민호(Ash): https://www.linkedin.com/in/park-minho-4896b7117/
3. Agenda
● Spoon Radio 소개
● 요구 사항
● 인프라 구성 도구
● 코드 중복 극복기 (DRY Journey)
○ Phase 1: Terraform
○ Phase 2: Terraform Module
○ Phase 3: Terragrunt
○ 고민
○ 숙제
● 정리
4. SPOON RADIO
● 개요: 개인 오디오 라이브 스트리밍 모바일 플랫폼
● 특징: 10 ~ 20대를 타겟팅 한 모바일 미디어 채널
● 2016년(한국), 2017년(인도네시아/베트남), 2018년(일본/중동 GCC 5개국)
● 글로벌 다운로드 900만 / 글로벌 MAU 170만
5. 6개월 간의 중복 코드 탈출기(ing)
● WET: Write Everything Twice
○ 중복, 반복된 코드
○ 수정 및 재 활용 하는데 많은 비용 발생
● DRY: Don't Repeat Yourself
○ 품질과 무결성 확보
○ 읽기 쉽고
○ 재활용 가능
○ 수정 및 활용 하는데 비용이 낮음.
6. 요구 사항
● 보통 인프라 구조는 거의 유사하지만, 아주 약간 다른 환경 구성 관리를 해야
함.
○ multiple aws accounts & region , environments,
○ aws resource(IAM, VPC, Subnet, RDS, EC2, Aurora, ALB, Lambda, S3, Route53 and so on.)
=> 5개국 서비스 구성시 15개의 유사 인프라 구성과 관리
● 기존 Legacy Infra를 Code 기반으로 자동화
○ AWS Well-Architected & ISMS & ISO27001 대응 인프라 구성
○ 생산성이 높은 인프라 환경 구성
8. Phase 1 - Terraform
● IaC - 인프라 생성, 변경, 설정을 코드 관리
○ 비용의 감소(돈, 인력, 노력 등)
○ 빠른 실행 속도
○ 위험 관리
○ 불확실성에 대한 유연함 - 아는 만큼 보이고, 처음 부터 완벽할 수 없다.
● Terraform
○ Terraform은 HashiCprop에서 만든 오픈 소스 도구이며,
인프라를 코드 형태로 정의하는 간편한 선언형 프로그래밍 언어
■ Multi Provider
■ Large Community
○ 그냥 해 보고 싶었다.
11. Phase 1 - main.tf 중복
KR JP IN VE MENA
+ PRD
- main.tf
+ STG
- main.tf
+ DEV
- main.tf
+ PRD
- main.tf
+ STG
- main.tf
+ DEV
- main.tf
+ PRD
- main.tf
+ STG
- main.tf
+ DEV
- main.tf
+ PRD
- main.tf
+ STG
- main.tf
+ DEV
- main.tf
+ PRD
- main.tf
+ STG
- main.tf
+ DEV
- main.tf
● 5개 국가, 3개의 인프라 환경 => 총 15개의 인프라 환경을 구성 관리해야 함.
○ 국가, 환경은 다르지만 동일한 인프라 구조를 만들기 위한 중복되는 옵션의 resource가 다수
발생.
○ 대략 100 ~ 200 line 수준의 main.tf WET Code 발생.
12. Phase 1 - 결국
● 단순 리소스 나열만으로는 15벌 인프라 전개 불가능
○ 재활용이 불가능
○ 비슷한 리소스의 대량 중복 발생
○ 유지 보수 불가능
○ 이렇게 할 거면 Hands - On과 차이가....
13. Phase 2 - Terraform Module
● Data를 변수 처리한 리소스 구성 파일 묶음.
○ 비슷한 리소스를 반복 정의하는 고통에서 벗어날 수 있다.
○ 사용자 정의 모듈 만들어 활용
○ 외부에서 이미 잘 만들어둔 모듈 활용
■ https://registry.terraform.io/
■ https://github.com/cloudposse
■ https://github.com/terraform-aws-modules
○ 리소스에는 versioning이 안되지만, 모듈에는 버저닝 가능
-> 모듈간 독립성 확보(의존성 제거)
15. vars.tf
Duplicated
main.tf
Duplicated
Phase 2 - Repeat Itself, Again
● module을 사용하면서 resource는 재활용이 가능해
졌지만, root module을 참조하는 sub module과 변수
정의 부분은 각 환경별 중복이 발생
● terraform.tfvars을 제외한 곳에서 WET code 발생
# jpn/terraform.tfvars
name = "prd-jpn-vpc"
cidr = "10.2.0.0/16"
public_subnets = ["10.2.0.0/24", ...]
public_subnets = ["10.2.1.0/24", ...]
asz = ["ap-northeast-2a", ...]
env = "prd-kor
# kor/terraform.tfvars
name = "prd-kor-vpc"
cidr = "10.1.0.0/16"
public_subnets = ["10.1.1.0/24", ...]
public_subnets = ["10.1.2.0/24", ...]
asz = ["ap-northeast-2a", ...]
env = "prd-kor
# kor/main.tf
module "network" {
source = "../modules/network"
name = "${var.name}"
cidr = "${var.cidr}"
azs = "${var.azs}"
public_subnets = "${var.public_subnets}"
env = "${var.env}"
}
# kor/vars.tf
variable name {
type = "string"
default = ""
}
variable cidr {
type = "string"
default = "0.0.0.0/0"
}
# jpn/main.tf
module "network" {
source = "../modules/network"
name = "${var.name}"
cidr = "${var.cidr}"
azs = "${var.azs}"
public_subnets = "${var.public_subnets}"
env = "${var.env}"
}
# jpn/vars.tf
variable name {
type = "string"
default = ""
}
variable cidr {
type = "string"
default = "0.0.0.0/0"
}
16. Phase 2 - 세상에 공짜는 없다.
● multiple env에서 그래도 중복 발생(main.tf, var.tf)
○ 5개 국가, 3개 환경 구성에 대한 중복은 그대로
○ global, local 변수 정의 관리 불편
● 오픈소스 모듈도 결국 내 입맛에 맞게 수정
○ 조금만 바꿔도 삭제 후 재 생성 ㅜㅜ
■ 요구 사항에 맞게 Lifecycle 관리 추가
○ resource 생성을 모듈 하위에서 생성하기에 수정하기가 불가함
○ 결국에 오픈소스 전체 코드 분석 후 수정함
17. Phase 3 - Terragrunt
● gruntworks.io에서 개발하고, Terraform만으로는 부족한 기능 보완
● 단순한 인프라는 Terraform으로도 충분하다.
그러나 인프라 구성이 커질 수록 모듈간의 연관 관계 및 중복 발생
● Orchestration = Terragrunt
○ 모듈 간의 코드 중복을 최소화
○ 모듈 간의 종속 관계 제거
Gruntwork.io
18. Terragrunt 기본 구조
● /terraform.tfvars
○ terragrunt의
환경설정
● /globals.tfvars
○ root module
○ root vars
● locals.tfvars
○ sub module
○ sub vars
● terraform.tfvars
○ 각각의 vars
terragrunt = {
….
terraform {
extra_arguments "-var-file" {
commands = ["${get_terraform_commands_that_need_vars()}"]
optional_var_files = [
"${get_tfvars_dir()}/${find_in_parent_folders("globals.tfvars", "ignore")}",
"${get_tfvars_dir()}/${find_in_parent_folders("locals.tfvars", "ignore")}"
]
}
}
}
namespace = "prod"
# dynamodb & s3
bucket = "prod-terraform-state"
config_region = "ap-northeast-2"
dynamodb_table = "prod-terraform-db"
stage = "kor"
aws_region = "ap-northeast-2"
# remote state key
vpc_remote_state_key = "prod/kor/terraform.tfstate"
terragrunt = {
terraform {
source = "git::ssh/spooncast/terraform-service-repo.git?ref=release/v0.0.1//bastion"
}
include = {
path = "${find_in_parent_folders()}"
}
}
name = "bastion"
instance_type = "t2.micro"
cidr_office = "xxx.xxx.xxx.xxx/32"
19. 코드와 데이터의 분리(Data Driven Design)
# prod/main.tf
terraform {
# The configuration for this backend will be filled in by
Terragrunt
backend "s3" {}
}
provider aws {
region = "${var.region}"
profile = "${var.profile}"
}
module "network" {
source = "terraform-aws-modules/vpc/aws"
name = "${var.name}"
cidr = "${var.cidr}"
azs = "${var.azs}"
public_subnets = "${var.public_subnets}"
env = "${var.country}"
}
# global.tfvars
profile = "prod"
# prod/local.tfvars
region = "ap-northeast-2"
country = "kr"
# prod/terraform.tfvars
terragrunt = {
terraform {
source = "network"
}
include = {
path = "${find_in_parent_folders()}"
}
}
name = "vpc-prd-kor"
cidr = "10.1.0.0/16"
azs = ["ap-northeast-2a", "ap-northeast-2b",
"ap-northeast-2c"]
public_subnets = ["10.1.1.0/24", "10.1.2.0/24,
"10.1.3.0/24]
# prod/local.tfvars
region = "ap-northeast-1"
country = "jp"
# prod/terraform.tfvars
terragrunt = {
terraform {
source = "network"
}
include = {
path = "${find_in_parent_folders()}"
}
}
name = "vpc-prd-jp"
cidr = "10.2.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1b",
"ap-northeast-1c"]
public_subnets = ["10.2.1.0/24", "10.2.2.0/24,
"10.2.3.0/24]
# prod/local.tfvars
region = "ap-south-1"
country = "mena"
# prod/terraform.tfvars
terragrunt = {
terraform {
source = "network"
}
include = {
path = "${find_in_parent_folders()}"
}
}
name = "vpc-prd-mena"
cidr = "10.3.0.0/16"
azs = ["ap-south-1a", "ap-south-1b",
"ap-south-1c"]
public_subnets = ["10.3.1.0/24", "10.3.2.0/24,
"10.3.3.0/24]
21. Dependencies between modules
terragrunt = {
terraform {
source = "../../../modules//services//was"
}
include = {
path = "${find_in_parent_folders()}"
}
dependencies {
paths = ["../network"]
}
}
...
주의 (主義)
Terragrunt up-all 실행시 Dependencies까지 함께
생성됨.
하지만 all로 생성된 모듈을 destroy 을 할 경우
같이 생성 된 dependencies 까지 삭제 함.
24. 고민
● Terraform 통한 인프라 구성이 빠르지 않다
○ Terraform 코드 개발 및 검증
○ Terraform Learning cove
○ 손으로 작업 했던 경험
○ 이상과 현실
● Terragrunt도 사용해야 한다.
○ Terraform Enterprise에는 Terragrunt에서 제공하는 대분분의 기능을 제공한다던데...
손은 (눈)코드보다 빠르다
25. 숙제
● Terraform 모듈 테스트 및 검증
○ CI pipeline 통한 모듈 테스트 도입
● PRD 환경 적용 후 리소스 삭제 방지
○ Terraform Lifecycle 설정
○ plan 통한 생성 및 삭제 리뷰 프로세스 수립
● tfstate 파일 시각화
○ tfstate 변경 이력 관리 (Terraboard 도입)
● Data Security (ssh key, password)
○ Vault, Consul 통한 데이터 보안
26. 정리
● “상용 수준 인프라를 구성하고 운영 하는 것은 매우 어렵다”것 인지 하자.
● (AWS) 기본 디자인을 잘 하자.
● DRY를 실천하기 위해 노력 하자.
○ 한 곳에 너무 많은 리소스를 정의 하지 말자.
○ 중복을 최소화하고, 최대한 작게 서로 독립적으로 단순하게 구성 하자.
● 팀에서의 강력한 의지가 없다면 하지 말자.. (아무리 좋아도 관리 안되면 허사)
● 불확성(외부 요구 사항)에 대한 보다 유연한 대응을 위해 노력해야 합니다.
27. Spoon Radio Infra Member
● 노가다를 싫어하는 노땅 엔지니어 2명
● 묵묵히 열심히 달려주는 엔지니어 1명
● 그리고 함께 경험 하실 분 모십니다.