Study

[스터디] Terraform의 개념부터 간단한 Terraform Registry 사용까지

mokpolar 2022. 10. 30. 16:01
반응형

멋진 CloudNeta 팀의 스터디에 이번에도 합류하게 되었다. 

이번 주제는 Terraform 이다. 이번 스터디는 "테라폼 업앤러닝" 이라는 도서를 가지고 진행한다. 

 

Terraform 이라는건 IaC(Infra as Code)를 구현하는 도구이다. 

 

왜 IaC인가?

IaC, 그러니까 코드형 인프라는 왜 배우고 왜 써야 하는걸까?

Infra As Code 라는 것인데, 선언적인 언어를 사용하여 인프라를 코드로 정의한다는 뜻이다. 

 

그럼 운영환경을 완전하게 이식해서 코드로 관리할 수 있는걸까? 실수는 생기지 않을까?

이런 의문이 들법 하지만 일단 책에서 강조하는 IaC의 장점을 보면 아래와 같은 내용을 설명하고 있다. 

 

  • 자급식 배포 : 소수의 관리자가 점유하고 있는 인프라가 아니라 개발자가 필요할 때마다 배포를 진행할 수 있음
  • 속도와 안정성 : 배포 프로세스를 자동화하기 때문에 훨씬 빠르고, 일관성있고 반복 가능하고 오류가 덜나는 배포 가능
  • 문서화 : 누구나 읽을 수 있는 문서파일로 인프라의 상택를 관리 가능
  • 버전 관리 : 인프라 변경 내역을 코드로 관리 가능하기 때문에 디버깅도 쉬워짐
  • 유효성 검증 : 코드로 되어있기 때문에 코드가 변경될 때마다 검증을 수행하고 자동화된 테스트 수행 가능
  • 재사용성 : 재사용 가능한 모듈로 패키징이 가능하기 때문에 다른 배포시에도 이미 검증된 모듈로 배포 가능

그럴듯하게 들린다. 그리고 여기에 장점 하나를 더 서술하고 있다. 

 

  • 행복 : 반복적이고 지루한 인프라 관리에 대한 대안이 된다. 

다른 장점들과 결이 다르고 인상적이고 가장 납득이 가는 이유라 따로 뽑아보았다. 

 

 

Terraform 이란?

다시 말하자면, Terraform은 위에서 설명한 IaC를 구현하는 도구이다. 

 

코드를 작성하여 인프라를 생성하고, 수정하고 배포를 할 수 있는데, 

특히 이번 주제인 Terraform이나, AWS의 Cloudformation 등의 프로비저닝 도구들은 서버 등의 리소스 자체를 생성하는 식으로 동작한다. 

 

아래 일부 코드 조각을 예로 들어보면 AWS의 VPC 를 생성하기 위해서 

provider를 AWS로 설정하고 ap-northeast-2 리젼을 선택한 뒤 cidr_block은 어떻게 하겠다 등의 정보들이 담겨있는 모습을 볼 수 있다. 

provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "mokpolar-vpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "t101-study"
  }
}

resource "aws_subnet" "mokpolar-subnet1" {
  vpc_id     = aws_vpc.mokpolar-vpc.id
  cidr_block = "10.10.1.0/24"

  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "t101-subnet1"
  }
}
...

위에서 처럼 AWS, GCP, Azure 등 클라우드 공급자들이 제공하는 API 서버를 활용해서 그들의 리소스를 생성할 수 있다.

물론 각각의 클라우드 공급자들의 기능이 일치하지 않기 때문에 서로간 완전한 이식은 불가능하다. 

 

 

 

Terraform의 설치

Terraform의 설치방법은 공식 홈페이지에서 친절하게 안내하고 있다.

 

MacOS기준으로 brew를 이용하는 방법은 아래와 같다. 

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

 

Terraform로 AWS EC2 간단하게 배포해보기

테라폼의 코드는 확장자가 .tf이고, HashiCorp 구성언어 (HashiCorp Configuration Language, HCL)로 작성한다. 

EC2를 한번 Terraform 으로 배포해보자. 

AWS 에서 ami란 아마존에서 만든 VM 머신 이미지이다. 

instance_type은 EC2 인스턴스의 유형이다. 

비용이 나오지 않는 t2.micro로 테스트 해보자. 

cat <<EOT > main.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "example" {
  ami           = "ami-0c76973fbe0ee100c"
  instance_type = "t2.micro"
}
EOT

 

위와 같이  HCL 로 구성되어 있는 코드의 각각의 내용을 뜯어보면 아래와 같이 구성되어 있다.

resource “<PROVIDER>_<TYPE>” “<NAME>” { [CONFIG ...] }

PROVIDER : ‘aws’ 같은 공급자의 이름

TYPE : ‘security_group’ 같은 리소스의 유형

NAME : 리소스의 이름

CONFIG : 한개 이상 arguments

그리고 실행한다. 

순서대로 명령어는 아래와 같다. 

# 초기화
terraform init

# plan 확인
terraform plan

# apply 실행
terraform apply

실행하면 아래와 같은 메시지들을 볼 수 있다.

terraform init을 실행하면 Terraform에서 코드를 스캔하고, 공급자를 확인하고, 그 공급자에 필요한 코드를 다운로드 받는 과정을 거친다. 

그런 공급자 코드는 실행한 위치의 .terraform 폴더에 다운로드 된다. 

$ terraform init

Initializing the backend...
...
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이다.

plan이라는 단어에서 볼 수 있듯이, 우리의 Terraform이 실제로 어떤 일을 수행하게 될지 미리 볼 수 있다. 

그리고 코드에 잘못된 부분이 있다면 이 단계에서 확인할 수 있다. 

커맨드라인에서 git 명령어를 자주 써왔다면 익숙한 형태이다. 

더해지고 + 빠지는게 -  있으며 ~ 표시는 수정된다는 의미이다. 

$ terraform plan

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:

  # aws_instance.example will be created
  + resource "aws_instance" "example" {
      + ami                                  = "ami-0c76973fbe0ee100c"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
 ...
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id                 = (known after apply)
              + capacity_reservation_resource_group_arn = (known after apply)
            }
        }

      + ebs_block_device {
          + delete_on_termination = (known after apply)
    ...


Plan: 1 to add, 0 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if
you run "terraform apply" now.

마지막으로 terraform apply 이다. 

plan에서 확인한 내용을 실제로 실행하는 것이다. 

 

$ terraform apply


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:

  # aws_instance.example will be created
  + resource "aws_instance" "example" {
      + ami                                  = "ami-0c76973fbe0ee100c"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
 ...
          + kms_key_id            = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)
        }
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_instance.example: Creating...

이후 AWS의 EC2화면으로 가면 아래와 같이 실제로 생성된 EC2 인스턴스를 볼 수 있다.

 

내용을 확인했으니 생성했던 리소스는 이제 지우도록 하자. 

$ terraform destroy -auto-approve

 

Terraform Registry 

간단하게 EC2를 배포하는 방법을 확인해봤지만 사실 이 정도로는 좀 아쉽다. 

EC2 정도를 배포를 이렇게 하는 것보다는 아마 AWS 콘솔창에서 클릭이 더 빠를 것이다. 

아쉬운 사람들을 위해 Terraform Registry 가 존재한다. 

 

https://registry.terraform.io/providers/hashicorp/aws/latest

 

사실 AWS를 많이 쓰면서도, Solutions Architect 자격증 같은걸 갖고 있다고 해도, 

어떤 리소스를 생성할 때 AWS에서 연관된 어떤 리소스들을 생성하는 지를 구체적으로 알기 어렵다. 

 

Terraform을 쓰면 AWS 에서 리소스 생성시 얼마나 많은 유관 리소스들이 생겨나는지를 일목요연하게 알 수 있어서 

AWS 리소스들의 구조를 파악하기가 쉽다는 장점이 있다. 

 

그 중 흥미로운 EKS Managed Nodegroup의 내용을 한번 살펴보겠다.

 

https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest/examples/eks_managed_node_group

 

$ git clone https://github.com/terraform-aws-modules/terraform-aws-eks
$ cd terraform-aws-eks/examples/eks_managed_node_group

init을 했을 때 나오는 내용이 좀 더 길다는 사실을 알 수 있다. 

관련된 AWS provider의 코드들을 더 많이 다운 받는 것이다. 

$ terraform init
Initializing modules...
- eks in ../..
Downloading registry.terraform.io/terraform-aws-modules/vpc/aws 3.18.1 for vpc...
- vpc in .terraform/modules/vpc
Downloading registry.terraform.io/terraform-aws-modules/iam/aws 4.24.1 for vpc_cni_irsa...
- vpc_cni_irsa in .terraform/modules/vpc_cni_irsa/modules/iam-role-for-service-accounts-eks

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/kubernetes versions matching ">= 2.10.0"...
- Finding hashicorp/aws versions matching ">= 3.0.0, >= 3.72.0, >= 3.73.0"...
- Finding hashicorp/tls versions matching ">= 3.0.0"...
- Installing hashicorp/aws v4.39.0...
- Installed hashicorp/aws v4.39.0 (signed by HashiCorp)
- Installing hashicorp/tls v4.0.4...
- Installed hashicorp/tls v4.0.4 (signed by HashiCorp)
- Installing hashicorp/kubernetes v2.15.0...
- Installed hashicorp/kubernetes v2.15.0 (signed by HashiCorp)

 

이제 plan이다.

내용을 보면 AWS 에서 Managed Kubernetes Cluster 하나를 만든다고 할 때 이렇게 많은 것들이 생성된다는 사실을 알 수 있다.

좀 길지만 흥미로우니 한번 살펴보는 것도 나쁘지 않을 것 같다. 

$ terraform plan

...

Changes to Outputs:
  + aws_auth_configmap_yaml                          = (known after apply)
  + cloudwatch_log_group_arn                         = (known after apply)
  + cloudwatch_log_group_name                        = "/aws/eks/ex-eks-managed-node-group/cluster"
  + cluster_addons                                   = {
      + coredns    = {
          + addon_name               = "coredns"
          + addon_version            = (known after apply)
          + arn                      = (known after apply)
          + cluster_name             = "ex-eks-managed-node-group"
          + created_at               = (known after apply)
          + id                       = (known after apply)
          + modified_at              = (known after apply)
          + preserve                 = null
          + resolve_conflicts        = "OVERWRITE"
          + service_account_role_arn = null
          + tags                     = {
              + "Example"    = "ex-eks-managed-node-group"
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + tags_all                 = {
              + "Example"    = "ex-eks-managed-node-group"
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + timeouts                 = null
        }
      + kube-proxy = {
          + addon_name               = "kube-proxy"
          + addon_version            = (known after apply)
          + arn                      = (known after apply)
          + cluster_name             = "ex-eks-managed-node-group"
          + created_at               = (known after apply)
          + id                       = (known after apply)
          + modified_at              = (known after apply)
          + preserve                 = null
          + resolve_conflicts        = null
          + service_account_role_arn = null
          + tags                     = {
              + "Example"    = "ex-eks-managed-node-group"
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + tags_all                 = {
              + "Example"    = "ex-eks-managed-node-group"
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + timeouts                 = null
        }
      + vpc-cni    = {
          + addon_name               = "vpc-cni"
          + addon_version            = (known after apply)
          + arn                      = (known after apply)
          + cluster_name             = "ex-eks-managed-node-group"
          + created_at               = (known after apply)
          + id                       = (known after apply)
          + modified_at              = (known after apply)
          + preserve                 = null
          + resolve_conflicts        = "OVERWRITE"
          + service_account_role_arn = (known after apply)
          + tags                     = {
              + "Example"    = "ex-eks-managed-node-group"
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + tags_all                 = {
              + "Example"    = "ex-eks-managed-node-group"
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + timeouts                 = null
        }
    }
  + cluster_arn                                      = (known after apply)
  + cluster_certificate_authority_data               = (known after apply)
  + cluster_endpoint                                 = (known after apply)
  + cluster_iam_role_arn                             = (known after apply)
  + cluster_iam_role_name                            = (known after apply)
  + cluster_iam_role_unique_id                       = (known after apply)
  + cluster_id                                       = (known after apply)
  + cluster_identity_providers                       = {}
  + cluster_oidc_issuer_url                          = (known after apply)
  + cluster_platform_version                         = (known after apply)
  + cluster_primary_security_group_id                = (known after apply)
  + cluster_security_group_arn                       = (known after apply)
  + cluster_security_group_id                        = (known after apply)
  + cluster_status                                   = (known after apply)
  + cluster_tls_certificate_sha1_fingerprint         = (known after apply)
  + eks_managed_node_groups                          = {
      + bottlerocket_add     = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = (known after apply)
          + launch_template_id                 = (known after apply)
          + launch_template_latest_version     = (known after apply)
          + launch_template_name               = (known after apply)
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + bottlerocket_custom  = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = (known after apply)
          + launch_template_id                 = (known after apply)
          + launch_template_latest_version     = (known after apply)
          + launch_template_name               = (known after apply)
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + bottlerocket_default = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = ""
          + launch_template_id                 = ""
          + launch_template_latest_version     = ""
          + launch_template_name               = ""
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + complete             = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = "eks-managed-node-group-complete-example"
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = (known after apply)
          + launch_template_id                 = (known after apply)
          + launch_template_latest_version     = (known after apply)
          + launch_template_name               = (known after apply)
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = {
              + "GithubOrg"  = "terraform-aws-modules"
              + "GithubRepo" = "terraform-aws-eks"
            }
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = [
              + {
                  + effect = "NO_SCHEDULE"
                  + key    = "dedicated"
                  + value  = "gpuGroup"
                },
            ]
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + containerd           = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = (known after apply)
          + launch_template_id                 = (known after apply)
          + launch_template_latest_version     = (known after apply)
          + launch_template_name               = (known after apply)
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + custom_ami           = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = (known after apply)
          + launch_template_id                 = (known after apply)
          + launch_template_latest_version     = (known after apply)
          + launch_template_name               = (known after apply)
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + default_node_group   = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = ""
          + launch_template_id                 = ""
          + launch_template_latest_version     = ""
          + launch_template_name               = ""
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
      + external_lt          = {
          + iam_role_arn                       = (known after apply)
          + iam_role_name                      = (known after apply)
          + iam_role_unique_id                 = (known after apply)
          + launch_template_arn                = ""
          + launch_template_id                 = ""
          + launch_template_latest_version     = ""
          + launch_template_name               = ""
          + node_group_arn                     = (known after apply)
          + node_group_autoscaling_group_names = (known after apply)
          + node_group_id                      = (known after apply)
          + node_group_labels                  = null
          + node_group_resources               = (known after apply)
          + node_group_status                  = (known after apply)
          + node_group_taints                  = []
          + security_group_arn                 = (known after apply)
          + security_group_id                  = (known after apply)
        }
    }
  + eks_managed_node_groups_autoscaling_group_names  = (known after apply)
  + fargate_profiles                                 = {}
  + node_security_group_arn                          = (known after apply)
  + node_security_group_id                           = (known after apply)
  + oidc_provider                                    = (known after apply)
  + oidc_provider_arn                                = (known after apply)
  + self_managed_node_groups                         = {}
  + self_managed_node_groups_autoscaling_group_names = []

그리고 그 전과 같이 terraform apply를 해주면 

 

클러스터가 생성되는 모습을 볼 수 있다.

 

 

Reference

 

 

반응형