Home IAC 도입과 사용하며 느낀점
Post
Cancel

IAC 도입과 사용하며 느낀점

1. 글을 작성하게 된 계기


최근 프로젝트를 진행하며 테라폼(Terraform)을 도입했는데, 이 과정에서 든 생각을 정리하기 위해 글을 작성하게 되었습니다.

HashiCorp Terraform is an infrastructure as code tool that lets you define both cloud and on-prem resources in human-readable configuration files that you can version, reuse, and share.





2. 왜 도입했을까?


이전 프로젝트를 할 때, 인프라 자원이 많아지며 이를 관리하기 힘든 이슈가 발생했습니다. 당시 서버 아키텍처는 다음과 같았는데, 개인 프로젝트라 자원이 많지 않음에도 불구하고 꽤 관리가 힘들었습니다.

image






이 때문에 과금 이 제법 됐는데요, 이번 프로젝트에서 이를 막고, 자원을 한 곳에서 관리해보고 싶어 테라폼 를 도입하게 됐습니다. 선택지는 테라폼, AWS CloudFormation, Puppet 등 몇 가지가 존재했는데, 아티클을 읽다보니 테라폼이 가장 끌렸습니다. 이전에 소개 영상을 봤던 기억도 있고, 무엇보다 테라폼이 이 중 가장 대중적 이어서요.

  1. Best Infrastructure as Code Tools for Streamlined Management
  2. Top 8 Infrastructure as Code (IaC) Tools for 2024
  3. Infrastructure-as-Code (IaC) Tools





AWS를 적극 활용하고 있다 보니, 마지막까지 테라폼과 CloudFormation 둘 중 고민했지만, CloudFormation는 너무 AWS 종속적이라 선택하지 않았습니다. 여튼 테라폼이을 사용하며 어떤 장/단점이 있었는지와 제 생각을 간략히 정리해 보겠습니다.

여담으로 최근 기술을 선택/학습할 때, 대중성 도 정말 중요한 것 같습니다. 나만 잘 알고, 잘 사용할 수 있는 기술보다 팀원도 쉽게 접근할 수 있는 기술을 선택하면 전체 생산성 을 올릴 수 있기 때문입니다. 물론 꼭 필요한 기술이라면 어렵더라도 팀 스터디를 통해 도입하는게 맞고요.







3. 장점


인프라를 코드로 관리하면 크게 자원 파악협업 두 가지 장점을 누릴 수 있습니다.

  1. 자원 파악
  2. 협업




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. 협업

협업이 용이해집니다. 자원이 코드로 관리되니, 히스토리를 추적 할 수 있으며, 코드 리뷰 를 도입할 수도 있습니다. 또 린터 를 도입해 코드 컨벤션을 통이할 수도 있고요. 이를 통해 팀 단위로 코드를 지속적으로 유지/보수 하면서 성장시킬 수 있습니다.

image

이번 프로젝트에서 인프라는 제가 담당했기 때문에 협업 메리트는 느낄 수 없어서 아쉬웠습니다.







4. 단점


느낀 단점은 다음과 같습니다.

  1. 인프라 이해도
  2. 자원간 순서 및 의존성
  3. 러닝커브





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. 정리


코드를 인프라로 관리하는 것은 꽤 매력적인 것 같습니다. 자원을 깔끔하게 관리할 수 있고, 코드를 추적/성장시키는 맛이 있어서요. 물론 이 또한 전문 분야이기 때문에 개발자가 하는 게 맞을까? 라고 물으면 솔직히 아닌 것 같습니다. 그래도 흥미가 간다면, 시간이 남는다면 자원 생성만이라도 꼭 한 번 시도해 봤으면 좋겠습니다. 재미있고 편리합니다.


This post is licensed under CC BY 4.0 by the author.

빌더 패턴의 불완전성 없애기

헬스체크 유예 기간으로 겪은 배포 이슈