1. 글을 작성하게 된 계기
최근 프로젝트를 진행하며 테라폼(Terraform)을 도입했는데, 이 과정에서 든 생각을 정리하기 위해 글을 작성하게 되었습니다.
2. 왜 도입했을까?
이전 프로젝트를 할 때, 인프라 자원이 많아지며 이를 관리하기 힘든 이슈가 발생했습니다. 당시 서버 아키텍처는 다음과 같았는데, 개인 프로젝트라 자원이 많지 않음에도 불구하고 꽤 관리가 힘들었습니다.
이 때문에 과금 이 제법 됐는데요, 이번 프로젝트에서 이를 막고, 자원을 한 곳에서 관리해보고 싶어 테라폼 를 도입하게 됐습니다. 선택지는 테라폼, AWS CloudFormation, Puppet 등 몇 가지가 존재했는데, 아티클을 읽다보니 테라폼이 가장 끌렸습니다. 이전에 소개 영상을 봤던 기억도 있고, 무엇보다 테라폼이 이 중 가장 대중적 이어서요.
- Best Infrastructure as Code Tools for Streamlined Management
- Top 8 Infrastructure as Code (IaC) Tools for 2024
- Infrastructure-as-Code (IaC) Tools
AWS를 적극 활용하고 있다 보니, 마지막까지 테라폼과 CloudFormation 둘 중 고민했지만, CloudFormation는 너무 AWS 종속적이라 선택하지 않았습니다. 여튼 테라폼이을 사용하며 어떤 장/단점이 있었는지와 제 생각을 간략히 정리해 보겠습니다.
여담으로 최근 기술을 선택/학습할 때,
대중성도 정말 중요한 것 같습니다. 나만 잘 알고, 잘 사용할 수 있는 기술보다 팀원도 쉽게 접근할 수 있는 기술을 선택하면전체 생산성을 올릴 수 있기 때문입니다. 물론 꼭 필요한 기술이라면 어렵더라도 팀 스터디를 통해 도입하는게 맞고요.
3. 장점
인프라를 코드로 관리하면 크게 자원 파악 과 협업 두 가지 장점을 누릴 수 있습니다.
- 자원 파악
- 협업
3-1. 자원 파악
한 곳에서 코드를 관리하기 때문에 자원 파악 과 관리 가 정말 쉽습니다. 명령어 하나로 어떤 자원이 프로비저닝/디프로비저닝 됐는지, 또 변경됐는지 추적 할 수 있기 때문입니다. 이러면 내가 생성하고 잊은 자원 때문에 과금될 일도 적겠죠? 🤣
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
$ terraform plan
module.s3.aws_s3_bucket.dailyge_client_bucket: Refreshing state... [id=dailyge-client]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.alb.aws_lb.dailyge_alb will be created
+ resource "aws_lb" "dailyge_alb" {
+ arn = (known after apply)
+ arn_suffix = (known after apply)
+ client_keep_alive = 3600
......
+ tags_all = {
+ "Environment" = "Prod"
}
+ vpc_id = (known after apply)
+ xff_header_processing_mode = "append"
+ zone_id = (known after apply)
}
......
}
심지어 수동으로 생성한 자원도 import 가 가능합니다. 한 번 import 되면, 기존 코드에 통합돼서, 지속적인 추적이 가능해집니다.
1
2
3
4
5
6
7
8
9
$ terraform import module.route53.aws_route53_zone.dailyge_route53 ${HOST_ZONE_ID}
module.route53.aws_route53_zone.dailyge_route53: Importing from ID "${HOST_ZONE_ID}"...
module.route53.aws_route53_zone.dailyge_route53: Import prepared!
Prepared aws_route53_zone for import
module.route53.aws_route53_zone.dailyge_route53: Refreshing state... [id=${HOST_ZONE_ID}]
Import successful!
The resources that were imported are shown above. These resources are now in your Terraform state and will henceforth be managed by Terraform.
반대로 코드로 관리 안 해도 되는, 혹은 한 번 생성하면 잘 바뀌지 않는 자원 은 수동으로 설치 후, ID 값만 참조할 수도 있습니다. 대표적으로 ACM이 있는데, 이는 자주 생성되지도 않고, arn만 참조하면 되기 때문입니다. 환경 변수로 값만 잘 보관하면 프로비저닝 시간도 줄일 수 있고, 긴 코드를 작성하지 않아도 됩니다.
1
acm_certificate_arn = "arn:aws:acm:us-east-1:${ACCOUNT_ID}:certificate/${CERTIFICATION_ID}"
무조건 코드로 만들어 추적/관리하려고 하면 생각보다 불편한 부분이 많습니다.
3-2. 협업
협업이 용이해집니다. 자원이 코드로 관리되니, 히스토리를 추적 할 수 있으며, 코드 리뷰 를 도입할 수도 있습니다. 또 린터 를 도입해 코드 컨벤션을 통이할 수도 있고요. 이를 통해 팀 단위로 코드를 지속적으로 유지/보수 하면서 성장시킬 수 있습니다.
이번 프로젝트에서 인프라는 제가 담당했기 때문에 협업 메리트는 느낄 수 없어서 아쉬웠습니다.
4. 단점
느낀 단점은 다음과 같습니다.
- 인프라 이해도
- 자원간 순서 및 의존성
- 러닝커브
4-1. 인프라 이해도
자신이 사용하는 클라우드 서비스의 특성 과 자원, 사용법 에 대한 이해가 필요합니다. 전체 플로우 와 연관 자원 을 알고 있어야 이를 코드로 옮길 수 있기 때문입니다. 예를 들어, 테라폼으로 AWS VPC를 생성할 경우, public/private 서브넷, Route Table, NAT Gateway, LB 등 연관된 다른 자원이 무엇이 있는지, 또 이를 어떻게 구성할 것인지 파악하고 있어야 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
resource "aws_subnet" "dailyge_public_subnets" {
for_each = var.public_subnets
vpc_id = aws_vpc.dailyge_vpc.id
cidr_block = each.value.cidr
availability_zone = each.value.zone
map_public_ip_on_launch = true
tags = {
Name = "${var.name}-public-subnets-${each.key}",
Environment = var.tags["Environment"]
}
}
resource "aws_subnet" "dailyge_private_subnets" {
for_each = var.private_subnets
vpc_id = aws_vpc.dailyge_vpc.id
cidr_block = each.value.cidr
availability_zone = each.value.zone
map_public_ip_on_launch = false
tags = {
Name = "${var.name}-private-${each.key}",
Environment = var.tags["Environment"]
}
}
resource "aws_route_table" "dailyge_public_route_table" {
vpc_id = aws_vpc.dailyge_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.dailyge_igw.id
}
tags = {
Name = "${var.name}-public-route-table",
Environment = var.tags["Environment"]
}
}
......
4-2. 순서 및 의존성
순서 나 의존성 이 있으면 이를 고려해야 합니다. 이 또한 설계를 잘해야 하는데요, 이번 프로젝트에서도 사이클 때문에 꽤 고생했습니다.
1
2
3
4
5
6
│ Error: Cycle: module.cloudfront.var.bucket_id (expand), module.cloudfront.var.tags (expand),
module.cloudfront.var.bucket_name (expand), module.cloudfront.aws_cloudfront_origin_access_
identity.dailyge_client_oai, module.cloudfront.var.domain_name (expand), module.cloudfront
.aws_cloudfront_distribution.dailyge_client_distribution, module.cloudfront.output.cloudfront
_distribution_arn (expand), module.s3.var.cloudfront_arn (expand), module.s3.aws_s3_bucket_policy
.dailyge_client_bucket_policy, module.s3 (close), module.cloudfront (expand)
특히 의존성이 문제였는데, 한 자원이 다른 자원의 구체적 설정을 알 경우, ID 참조 정도만 할 수 있도록 만드는 것이 처음엔 꽤 어려웠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
resource "aws_cloudfront_origin_access_control" "oac" {
name = "OAC-${var.bucket_name}"
description = "Origin Access Control for ${var.bucket_name}"
origin_access_control_origin_type = "s3"
signing_behavior = "always"
signing_protocol = "sigv4"
}
resource "aws_cloudfront_distribution" "s3_distribution" {
origin {
domain_name = var.s3_bucket_regional_domain_name
origin_id = "S3-${var.bucket_id}" # S3 Bucket ID 참조
custom_origin_config {
http_port = 80
https_port = 443
origin_protocol_policy = "https-only"
origin_ssl_protocols = ["TLSv1.2"]
origin_read_timeout = 30
origin_keepalive_timeout = 5
}
}
}
4-3. 러닝커브
마지막으로 러닝 커브입니다. 사람마다 느끼는 정도가 다르겠지만, 개인적으로는 러닝 커브가 조금 있었습니다. 단순히 자원을 프로비저닝하는 정도는 며칠이면 가능했지만, 자원 간 순서 나 의존성 이 있으면 이를 처리하기 꽤 까다로웠기 때문입니다. 위에서 설명한 내용인데, 의존성 문제로 데드락이 발생해 한참을 헤맨 경우도 있었습니다. 변수로 선언하거나 중복 선언을 줄이는 것도요.
1
2
3
4
5
6
│ Error: Cycle: module.cloudfront.var.bucket_id (expand), module.cloudfront.var.tags (expand),
module.cloudfront.var.bucket_name (expand), module.cloudfront.aws_cloudfront_origin_access_
identity.dailyge_client_oai, module.cloudfront.var.domain_name (expand), module.cloudfront
.aws_cloudfront_distribution.dailyge_client_distribution, module.cloudfront.output.cloudfront
_distribution_arn (expand), module.s3.var.cloudfront_arn (expand), module.s3.aws_s3_bucket_policy
.dailyge_client_bucket_policy, module.s3 (close), module.cloudfront (expand)
머리로는 어떻게 해결할지 떠올랐지만, 코드로 작성이 안 돼서 꽤 답답했고, ‘그냥 설치해서 사용해 버릴까?’ 하는 생각도 종종 들었습니다. 여튼 처음 사용할 때 러닝 커브가 있을 수 있다는 점이 제가 느낀 마지막 아쉬운 점이었습니다.
개발자의 숙명이라 새로 배우는 것은 이제는 그러려니 합니다. 💬
5. 정리
코드를 인프라로 관리하는 것은 꽤 매력적인 것 같습니다. 자원을 깔끔하게 관리할 수 있고, 코드를 추적/성장시키는 맛이 있어서요. 물론 이 또한 전문 분야이기 때문에 개발자가 하는 게 맞을까? 라고 물으면 솔직히 아닌 것 같습니다. 그래도 흥미가 간다면, 시간이 남는다면 자원 생성만이라도 꼭 한 번 시도해 봤으면 좋겠습니다. 재미있고 편리합니다.