728x90
반응형
SMALL
SMALL

이번에는 HCL의 for-each문을 활용해서 여러개의 리소스를 만들어보자.

 

count

우선, for-each를 사용하기 전 count를 먼저 사용해보자. 이 count는 HCL에서 예전부터 있던 기능인데 이 기능에 대한 문제점을 보완하고자 for-each가 나왔다고 생각하면 된다. 우선 코드를 바로 보자.

# ---------
# count

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

resource "aws_iam_user" "count" {
  count = 5
  name  = "count-user-${count.index}"
}

output "count_user_arns" {
  value = aws_iam_user.count.*.arn
}

 

이 코드에서 "count = 5"를 보면 5개를 만들어낼 것을 예측할 수 있다. 그리고 이 count는 자체적으로 가지는 변수 index라는 것이 있다. 그래서 aws_iam_user를 만들어낼 때 그 이름을 count.index로 변형을 주어 5명의 사용자를 만들어낸다.

 

그리고 output을 보면 aws_iam_user.count 리소스가 리스트로 만들어지는데 [count-user-0, count-user-2, ..., count-user-9] 이런식으로 말이다. 그러면 그 각각의 유저들의 arn을 output으로 지정한다는 뜻이다.

 

이 코드를 Init - Apply 해보자. Apply 결과는 다음과 같다. 잘 만들어졌지만 다음 사진이 바로 count의 문제점이다. 

인덱스로 각각의 리소스를 나타내기 때문에 그래서 0번이 누구인지, 1번이 누구인지 알아보기가 상당히 까다롭고, 만약 아래처럼 0, 1, 2, 3, 4 총 5명의 유저가 있을 때 2번 유저를 삭제하면 3, 4이 한칸씩 앞으로 옮겨지게된다. 이러면 또 골머리가 아파진다.

 

AWS Console에서 확인해보면 다음과 같이 잘 만들어졌음을 확인할 수 있다.

 

 

For-Each

For-Each는 Set/Map을 지원한다. Set은 리스트인데 유니크한 값만을 가지는 리스트이고, Map은 {"key": "value", ...} 이러한 형식이다. 바로 코드를 보자. 다음은 Set을 사용한 For-Each문이다.

# ForEach (Set)

resource "aws_iam_user" "for_each_set" {
  for_each = toset([
    "for-each-set-user-1",
    "for-each-set-user-2",
    "for-each-set-user-3",
  ])

  name = each.key
}

output "for_each_set_user_arns" {
  value = values(aws_iam_user.for_each_set).*.arn
}

For-Each의 Set을 사용하면 자체적으로 제공하는 key라는 property가 존재한다. 그래서 사용자 이름을 each.key로 지정했다. 

output을 보면 values()를 사용한다. 이는 aws_iam_user.for_each_set 이라는 리소스는 다음과 같이 생겼다.

for-each-set-user-1: {...}
for-each-set-user-2: {...}
for-each-set-user-3: {...}

그래서 각각의 value가 {...} 이 부분인데 이것 전체를 가져온다는 의미가 values(aws_iam_user.for_each_set)이다. 그래서 그 모양은 [{...}, {...}, ..] 이렇게 될 것이다. 그리고 그 각각의 value가 가지는 모든것을 의미하는 '*'에서 arn을 찍어낸다.

 

Apply를 해보자. 결과는 다음과 같다.

 

AWS Console에서도 확인해보자. 잘 만들어졌다.

 

다음은 Set이 아닌 Map으로 ForEach를 사용해보자. 뭐 크게 달라지는 건 없다. Map이니까 데이터 모양새가 좀 다를것이고 더 많은 정보가 들어갈 수 있을 것 같다.

# ForEach (Map)

resource "aws_iam_user" "for_each_map" {
  for_each = {
    "alice" = {
      level   = "low"
      manager = "chyonee"
    }
    "bob" = {
      level   = "mid"
      manager = "chyonee"
    }
    "john" = {
      level   = "high"
      manager = "chyonee"
    }
  }

  name = each.key
  tags = each.value
}

output "for_each_map_user_arns" {
  value = values(aws_iam_user.for_each_map).*.arn
}

 

Apply 해보자. 잘 만들어졌다.

적용한 태그 역시 잘 들어갔다.

 

 

결론

같은 형태의 리소스를 여러번 만들기위해 반복문(For-Each)을 사용해봤다. 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

AWS + Terraform (Loop)  (0) 2024.03.07
AWS + Terraform (Conditions)  (0) 2024.03.07
AWS + Terraform (Module)  (0) 2024.03.06
AWS + Terraform  (2) 2024.03.05
Terraform 소개 및 간단하게 다루어보기  (0) 2024.03.05
728x90
반응형
SMALL
SMALL

 

Variables

이번엔 Variable을 사용해서 코드에서 사용할 수 있도록 해보자.

이전 포스팅에서 사용했던 main.tf 파일에서 다음을 추가한다.

variable "vpc_name" {
		
}

 

이 코드는 vpc_name 이라는 변수를 만든 것이다. 여기에 기본값을 설정하지 않으면 Apply를 할 때 변수 이름을 받도록 되어 있다.

그래서 이 변수를 사용해보기 위해 VPC의 name 값을 var.vpc_name 으로 변경해보자.

module "vpc" {
  source  = "tedilabs/network/aws//modules/vpc"
  version = "0.24.0"

  name       = var.vpc_name
  cidr_block = "10.0.0.0/16"

  internet_gateway_enabled = true

  dns_hostnames_enabled = true
  dns_support_enabled   = true

  tags = {}
}

 

최종 소스 코드는 다음과 같다.

#main.tf

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

variable "vpc_name" {
		
}

module "vpc" {
  source  = "tedilabs/network/aws//modules/vpc"
  version = "0.24.0"

  name       = var.vpc_name
  cidr_block = "10.0.0.0/16"

  internet_gateway_enabled = true

  dns_hostnames_enabled = true
  dns_support_enabled   = true

  tags = {}
}

module "subnet_group__public" {
  source  = "tedilabs/network/aws//modules/subnet-group"
  version = "0.24.0"

  name                    = "${module.vpc.name}-public"
  vpc_id                  = module.vpc.id
  map_public_ip_on_launch = true

  subnets = {
    "${module.vpc.name}-public-001/az1" = {
      cidr_block           = "10.0.0.0/24"
      availability_zone_id = "apne2-az1"
    }
    "${module.vpc.name}-public-002/az2" = {
      cidr_block           = "10.0.1.0/24"
      availability_zone_id = "apne2-az2"
    }
  }

  tags = {}
}

module "subnet_group__private" {
  source  = "tedilabs/network/aws//modules/subnet-group"
  version = "0.24.0"

  name                    = "${module.vpc.name}-private"
  vpc_id                  = module.vpc.id
  map_public_ip_on_launch = false

  subnets = {
    "${module.vpc.name}-private-001/az1" = {
      cidr_block           = "10.0.10.0/24"
      availability_zone_id = "apne2-az1"
    }
    "${module.vpc.name}-public-002/az2" = {
      cidr_block           = "10.0.11.0/24"
      availability_zone_id = "apne2-az2"
    }
  }

  tags = {}
}

 

Apply를 실행해보자. 다음과 같이 변수 값을 받는다. 이는 vpc_name의 기본값을 설정해주지 않았기 때문이다.

 

나는 다음과 같이 변수값을 지정했다.

 

이렇게 하고 Enter를 입력하면 이제 Plan 정보들이 나온다.

그 Plan 정보를 잘 보면 내가 넣은 "tftf"값이 보여진다.

 

이제 생성을 완료해서 AWS Console에서 확인해보자. 잘 만들어졌다.

 

Variables 지정 방식들

이러한 변수를 설정하고 지정하는 방식에는 여러 방법이 있는데 문서를 살펴보면 자세히 나와있다.

 

Input Variables - Configuration Language | Terraform | HashiCorp Developer

Input variables allow you to customize modules without altering their source code. Learn how to declare, define, and reference variables in configurations.

developer.hashicorp.com

 

이 문서에 보면 다음과 같은 코드 예시가 있다. 위에서 우리가 작성한 것과 다른 부분은 type을 지정한다거나 default 값을 지정한다.

variable "image_id" {
  type = string
}

variable "availability_zone_names" {
  type    = list(string)
  default = ["us-west-1a"]
}

variable "docker_ports" {
  type = list(object({
    internal = number
    external = number
    protocol = string
  }))
  default = [
    {
      internal = 8300
      external = 8300
      protocol = "tcp"
    }
  ]
}

 

실행 시 옵션으로 변수 지정

그리고 이 변수에 기본값을 지정했더라도 다음처럼 실행 시 변수값을 변경할 수 있다.

terraform apply -var="image_id=ami-abc123"
terraform apply -var='image_id_list=["ami-abc123","ami-def456"]' -var="instance_type=t2.micro"
terraform apply -var='image_id_map={"us-east-1":"ami-abc123","us-east-2":"ami-def456"}'

 

환경변수로 등록해서 변수 지정

또는 다음과 같이 환경변수로도 설정할 수 있다.

export TF_VAR_image_id=ami-abc123

 

변수 파일(.tfvars / .tfvars.json)로 변수 지정

또는 다음과 같이 여러 변수가 있는 경우 변수만을 위한 파일을 만들어서 그 파일을 가리키게 할 수 있다.

파일명은 ".tfvars" 또는 ".tfvars.json"형식이어야 한다.

 

근데, 기본으로 파일명은 "terraform.tfvars"라는 파일만을 바라보게 되어있다. 그래서 이런 파일명이 아닌 경우 Apply시에 인지를 못하는데 그 때 해결하는 방법은 다음처럼 실행 시 옵션을 주는 것이다.

terraform apply -var-file="testing.tfvars"

이 변수 파일은 main.tf 파일이랑 같은 경로에 있으면 된다. 그리고 아래와 같이 작성해주면 된다.

#terraform.tfvars

vpc_name = "varsfile"

 

Local Variables

이 Local Variable은 main.tf 파일 내에서만 공통으로 사용할 변수를 선언하고 그 변수를 사용하기에 적합한 방식이다. 

기존 main.tf 파일에 다음과 같은 코드를 추가해보자.

locals {
  common_tags = {
	Project = "Network"
	Owner = "cwchoi"
  }
}

 

그리고 이 로컬 변수를 각 모듈의 태그에서 사용해보자.

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

variable "vpc_name" {

}

locals {
  common_tags = {
	Project = "Network"
	Owner = "cwchoi"
  }
}

module "vpc" {
  source  = "tedilabs/network/aws//modules/vpc"
  version = "0.24.0"

  name       = var.vpc_name
  cidr_block = "10.0.0.0/16"

  internet_gateway_enabled = true

  dns_hostnames_enabled = true
  dns_support_enabled   = true

  tags = local.common_tags
}

module "subnet_group__public" {
  source  = "tedilabs/network/aws//modules/subnet-group"
  version = "0.24.0"

  name                    = "${module.vpc.name}-public"
  vpc_id                  = module.vpc.id
  map_public_ip_on_launch = true

  subnets = {
    "${module.vpc.name}-public-001/az1" = {
      cidr_block           = "10.0.0.0/24"
      availability_zone_id = "apne2-az1"
    }
    "${module.vpc.name}-public-002/az2" = {
      cidr_block           = "10.0.1.0/24"
      availability_zone_id = "apne2-az2"
    }
  }

  tags = local.common_tags
}

module "subnet_group__private" {
  source  = "tedilabs/network/aws//modules/subnet-group"
  version = "0.24.0"

  name                    = "${module.vpc.name}-private"
  vpc_id                  = module.vpc.id
  map_public_ip_on_launch = false

  subnets = {
    "${module.vpc.name}-private-001/az1" = {
      cidr_block           = "10.0.10.0/24"
      availability_zone_id = "apne2-az1"
    }
    "${module.vpc.name}-public-002/az2" = {
      cidr_block           = "10.0.11.0/24"
      availability_zone_id = "apne2-az2"
    }
  }

  tags = local.common_tags
}

 

이렇게 해당 파일 내에서 반복적으로 사용되는 변수를 로컬 변수라는 형식으로 지정할 수 있다.

 

 

Outputs

Outputs은 Apply를 진행 후 결과를 저장할 때 사용한다. 예를 들어 다음과 같은 코드를 추가했다고 가정해보자.

output "vpc_name" {
  value = module.vpc.name
}

output "vpc_id" {
  value = module.vpc.id
}

output "public_subnet_group" {
  value = module.subnet_group__public
}

output "subnets" {
  value = {
    public  = module.subnet_group__public
    private = module.subnet_group__private
  }
}

 

지금 3개의 output이 있는것을 확인할 수 있다. 이렇게 3개의 아웃풋이 Apply를 진행 후 만들어지는 데이터라고 생각하면 된다.

이 Output을 다른곳에서 가져다가 사용할 수도 있고 Apply 진행 후 결과를 알고 싶을때도 사용할 수 있다. Apply를 해보자.

 

다음과 같은 Outputs 결과를 볼 수 있다.

public_subnet_group = {
  "arns" = [
    "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-0a3d08dd50460e153",
    "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-00c04ce3dead87b6d",
  ]
  "availability_zone_ids" = tolist([
    "apne2-az1",
    "apne2-az2",
  ])
  "availability_zones" = tolist([
    "ap-northeast-2a",
    "ap-northeast-2b",
  ])
  "cache_subnet_group_id" = null
  "cidr_blocks" = [
    "10.0.0.0/24",
    "10.0.1.0/24",
  ]
  "dax_subnet_group_id" = null
  "db_subnet_group_arn" = null
  "db_subnet_group_id" = null
  "dms_replication_subnet_group_id" = null
  "docdb_subnet_group_arn" = null
  "docdb_subnet_group_id" = null
  "ids" = [
    "subnet-0a3d08dd50460e153",
    "subnet-00c04ce3dead87b6d",
  ]
  "ipv6_cidr_blocks" = tolist([])
  "name" = "varsfile-public"
  "neptune_subnet_group_arn" = null
  "neptune_subnet_group_id" = null
  "redshift_subnet_group_arn" = null
  "redshift_subnet_group_id" = null
  "subnets" = [
    {
      "arn" = "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-0a3d08dd50460e153"
      "assign_ipv6_address_on_creation" = false
      "availability_zone" = "ap-northeast-2a"
      "availability_zone_id" = "apne2-az1"
      "cidr_block" = "10.0.0.0/24"
      "customer_owned_ipv4_pool" = ""
      "enable_dns64" = false
      "enable_lni_at_device_index" = 0
      "enable_resource_name_dns_a_record_on_launch" = false
      "enable_resource_name_dns_aaaa_record_on_launch" = false
      "id" = "subnet-0a3d08dd50460e153"
      "ipv6_cidr_block" = ""
      "ipv6_cidr_block_association_id" = ""
      "ipv6_native" = false
      "map_customer_owned_ip_on_launch" = false
      "map_public_ip_on_launch" = true
      "outpost_arn" = ""
      "owner_id" = "135149110460"
      "private_dns_hostname_type_on_launch" = "ip-name"
      "tags" = tomap({
        "Name" = "varsfile-public-001/az1"
        "Owner" = "cwchoi"
        "Project" = "Network"
        "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
        "module.terraform.io/instance" = "varsfile-public"
        "module.terraform.io/name" = "subnet-group"
        "module.terraform.io/package" = "terraform-aws-network"
        "module.terraform.io/version" = "0.24.0"
      })
      "tags_all" = tomap({
        "Name" = "varsfile-public-001/az1"
        "Owner" = "cwchoi"
        "Project" = "Network"
        "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
        "module.terraform.io/instance" = "varsfile-public"
        "module.terraform.io/name" = "subnet-group"
        "module.terraform.io/package" = "terraform-aws-network"
        "module.terraform.io/version" = "0.24.0"
      })
      "timeouts" = null /* object */
      "vpc_id" = "vpc-01166744a0ba4ca4a"
    },
    {
      "arn" = "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-00c04ce3dead87b6d"
      "assign_ipv6_address_on_creation" = false
      "availability_zone" = "ap-northeast-2b"
      "availability_zone_id" = "apne2-az2"
      "cidr_block" = "10.0.1.0/24"
      "customer_owned_ipv4_pool" = ""
      "enable_dns64" = false
      "enable_lni_at_device_index" = 0
      "enable_resource_name_dns_a_record_on_launch" = false
      "enable_resource_name_dns_aaaa_record_on_launch" = false
      "id" = "subnet-00c04ce3dead87b6d"
      "ipv6_cidr_block" = ""
      "ipv6_cidr_block_association_id" = ""
      "ipv6_native" = false
      "map_customer_owned_ip_on_launch" = false
      "map_public_ip_on_launch" = true
      "outpost_arn" = ""
      "owner_id" = "135149110460"
      "private_dns_hostname_type_on_launch" = "ip-name"
      "tags" = tomap({
        "Name" = "varsfile-public-002/az2"
        "Owner" = "cwchoi"
        "Project" = "Network"
        "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
        "module.terraform.io/instance" = "varsfile-public"
        "module.terraform.io/name" = "subnet-group"
        "module.terraform.io/package" = "terraform-aws-network"
        "module.terraform.io/version" = "0.24.0"
      })
      "tags_all" = tomap({
        "Name" = "varsfile-public-002/az2"
        "Owner" = "cwchoi"
        "Project" = "Network"
        "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
        "module.terraform.io/instance" = "varsfile-public"
        "module.terraform.io/name" = "subnet-group"
        "module.terraform.io/package" = "terraform-aws-network"
        "module.terraform.io/version" = "0.24.0"
      })
      "timeouts" = null /* object */
      "vpc_id" = "vpc-01166744a0ba4ca4a"
    },
  ]
  "vpc_id" = "vpc-01166744a0ba4ca4a"
}
subnets = {
  "private" = {
    "arns" = [
      "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-0396c6b38c61fb88f",
      "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-058b0d0699a85f174",
    ]
    "availability_zone_ids" = tolist([
      "apne2-az1",
      "apne2-az2",
    ])
    "availability_zones" = tolist([
      "ap-northeast-2a",
      "ap-northeast-2b",
    ])
    "cache_subnet_group_id" = null
    "cidr_blocks" = [
      "10.0.10.0/24",
      "10.0.11.0/24",
    ]
    "dax_subnet_group_id" = null
    "db_subnet_group_arn" = null
    "db_subnet_group_id" = null
    "dms_replication_subnet_group_id" = null
    "docdb_subnet_group_arn" = null
    "docdb_subnet_group_id" = null
    "ids" = [
      "subnet-0396c6b38c61fb88f",
      "subnet-058b0d0699a85f174",
    ]
    "ipv6_cidr_blocks" = tolist([])
    "name" = "varsfile-private"
    "neptune_subnet_group_arn" = null
    "neptune_subnet_group_id" = null
    "redshift_subnet_group_arn" = null
    "redshift_subnet_group_id" = null
    "subnets" = [
      {
        "arn" = "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-0396c6b38c61fb88f"
        "assign_ipv6_address_on_creation" = false
        "availability_zone" = "ap-northeast-2a"
        "availability_zone_id" = "apne2-az1"
        "cidr_block" = "10.0.10.0/24"
        "customer_owned_ipv4_pool" = ""
        "enable_dns64" = false
        "enable_lni_at_device_index" = 0
        "enable_resource_name_dns_a_record_on_launch" = false
        "enable_resource_name_dns_aaaa_record_on_launch" = false
        "id" = "subnet-0396c6b38c61fb88f"
        "ipv6_cidr_block" = ""
        "ipv6_cidr_block_association_id" = ""
        "ipv6_native" = false
        "map_customer_owned_ip_on_launch" = false
        "map_public_ip_on_launch" = false
        "outpost_arn" = ""
        "owner_id" = "135149110460"
        "private_dns_hostname_type_on_launch" = "ip-name"
        "tags" = tomap({
          "Name" = "varsfile-private-001/az1"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-private"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "tags_all" = tomap({
          "Name" = "varsfile-private-001/az1"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-private"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "timeouts" = null /* object */
        "vpc_id" = "vpc-01166744a0ba4ca4a"
      },
      {
        "arn" = "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-058b0d0699a85f174"
        "assign_ipv6_address_on_creation" = false
        "availability_zone" = "ap-northeast-2b"
        "availability_zone_id" = "apne2-az2"
        "cidr_block" = "10.0.11.0/24"
        "customer_owned_ipv4_pool" = ""
        "enable_dns64" = false
        "enable_lni_at_device_index" = 0
        "enable_resource_name_dns_a_record_on_launch" = false
        "enable_resource_name_dns_aaaa_record_on_launch" = false
        "id" = "subnet-058b0d0699a85f174"
        "ipv6_cidr_block" = ""
        "ipv6_cidr_block_association_id" = ""
        "ipv6_native" = false
        "map_customer_owned_ip_on_launch" = false
        "map_public_ip_on_launch" = false
        "outpost_arn" = ""
        "owner_id" = "135149110460"
        "private_dns_hostname_type_on_launch" = "ip-name"
        "tags" = tomap({
          "Name" = "varsfile-public-002/az2"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-private"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "tags_all" = tomap({
          "Name" = "varsfile-public-002/az2"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-private"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "timeouts" = null /* object */
        "vpc_id" = "vpc-01166744a0ba4ca4a"
      },
    ]
    "vpc_id" = "vpc-01166744a0ba4ca4a"
  }
  "public" = {
    "arns" = [
      "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-0a3d08dd50460e153",
      "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-00c04ce3dead87b6d",
    ]
    "availability_zone_ids" = tolist([
      "apne2-az1",
      "apne2-az2",
    ])
    "availability_zones" = tolist([
      "ap-northeast-2a",
      "ap-northeast-2b",
    ])
    "cache_subnet_group_id" = null
    "cidr_blocks" = [
      "10.0.0.0/24",
      "10.0.1.0/24",
    ]
    "dax_subnet_group_id" = null
    "db_subnet_group_arn" = null
    "db_subnet_group_id" = null
    "dms_replication_subnet_group_id" = null
    "docdb_subnet_group_arn" = null
    "docdb_subnet_group_id" = null
    "ids" = [
      "subnet-0a3d08dd50460e153",
      "subnet-00c04ce3dead87b6d",
    ]
    "ipv6_cidr_blocks" = tolist([])
    "name" = "varsfile-public"
    "neptune_subnet_group_arn" = null
    "neptune_subnet_group_id" = null
    "redshift_subnet_group_arn" = null
    "redshift_subnet_group_id" = null
    "subnets" = [
      {
        "arn" = "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-0a3d08dd50460e153"
        "assign_ipv6_address_on_creation" = false
        "availability_zone" = "ap-northeast-2a"
        "availability_zone_id" = "apne2-az1"
        "cidr_block" = "10.0.0.0/24"
        "customer_owned_ipv4_pool" = ""
        "enable_dns64" = false
        "enable_lni_at_device_index" = 0
        "enable_resource_name_dns_a_record_on_launch" = false
        "enable_resource_name_dns_aaaa_record_on_launch" = false
        "id" = "subnet-0a3d08dd50460e153"
        "ipv6_cidr_block" = ""
        "ipv6_cidr_block_association_id" = ""
        "ipv6_native" = false
        "map_customer_owned_ip_on_launch" = false
        "map_public_ip_on_launch" = true
        "outpost_arn" = ""
        "owner_id" = "135149110460"
        "private_dns_hostname_type_on_launch" = "ip-name"
        "tags" = tomap({
          "Name" = "varsfile-public-001/az1"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-public"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "tags_all" = tomap({
          "Name" = "varsfile-public-001/az1"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-public"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "timeouts" = null /* object */
        "vpc_id" = "vpc-01166744a0ba4ca4a"
      },
      {
        "arn" = "arn:aws:ec2:ap-northeast-2:135149110460:subnet/subnet-00c04ce3dead87b6d"
        "assign_ipv6_address_on_creation" = false
        "availability_zone" = "ap-northeast-2b"
        "availability_zone_id" = "apne2-az2"
        "cidr_block" = "10.0.1.0/24"
        "customer_owned_ipv4_pool" = ""
        "enable_dns64" = false
        "enable_lni_at_device_index" = 0
        "enable_resource_name_dns_a_record_on_launch" = false
        "enable_resource_name_dns_aaaa_record_on_launch" = false
        "id" = "subnet-00c04ce3dead87b6d"
        "ipv6_cidr_block" = ""
        "ipv6_cidr_block_association_id" = ""
        "ipv6_native" = false
        "map_customer_owned_ip_on_launch" = false
        "map_public_ip_on_launch" = true
        "outpost_arn" = ""
        "owner_id" = "135149110460"
        "private_dns_hostname_type_on_launch" = "ip-name"
        "tags" = tomap({
          "Name" = "varsfile-public-002/az2"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-public"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "tags_all" = tomap({
          "Name" = "varsfile-public-002/az2"
          "Owner" = "cwchoi"
          "Project" = "Network"
          "module.terraform.io/full-name" = "terraform-aws-network/subnet-group"
          "module.terraform.io/instance" = "varsfile-public"
          "module.terraform.io/name" = "subnet-group"
          "module.terraform.io/package" = "terraform-aws-network"
          "module.terraform.io/version" = "0.24.0"
        })
        "timeouts" = null /* object */
        "vpc_id" = "vpc-01166744a0ba4ca4a"
      },
    ]
    "vpc_id" = "vpc-01166744a0ba4ca4a"
  }
}
vpc_id = "vpc-01166744a0ba4ca4a"
vpc_name = "varsfile"

 

 

728x90
반응형
LIST
728x90
반응형
SMALL
SMALL

 

목표

이번에는 모듈을 사용해서 인프라를 구축해보자. VPC, Subnet, Route Tables 등등을 모듈로 정의하고 가져다가 사용해서 인프라를 구축한다. 

 

Module

모듈은 쉽게 말해서, 어떤 특정 컴포넌트(예: VPC)를 만들기 위해 필요한 정보들을 미리 구현해놓은 누군가(또는 나의)의 것을 가져다가 사용한다고 생각하면 된다. 개발할 때 패키지나 모듈을 다운받아서 가져다가 쓰는 원리랑 동일하다.

 

그래서 테라폼 레지스트리에서 올려놓은 여러 모듈 중 하나를 가져다가 사용해서 인프라를 구성해보자.

 

테라폼 레지스트리에 등록된 모듈 중 이 모듈을 사용할 것이다.

 

Terraform Registry

 

registry.terraform.io

 

여기 보면 AWS 네트워크 관련된 모듈들이 있다.

 

Terraform Registry

 

registry.terraform.io

 

 

main.tf

다음과 같이 main.tf 파일을 만든다. 하나하나 뜯어보자.

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

module "vpc" {
	source = "tedilabs/network/aws//modules/vpc"
	version = "0.24.0"

	name = "tf-vpc"
	cidr_block = "10.0.0.0/16"

	internet_gateway_enabled = true

	dns_hostnames_enabled = true
	dns_support_enabled = true

	tags = {}
}

module "subnet_group__public" {
	source = "tedilabs/network/aws//modules/subnet-group"
	version = "0.24.0"

	name = "${module.vpc.name}-public"
	vpc_id = module.vpc.id
	map_public_ip_on_launch = true

	subnets = {
		"${module.vpc.name}-public-001/az1" = {
			cidr_block = "10.0.0.0/24"
			availability_zone_id = "apne2-az1"
		}
		"${module.vpc.name}-public-002/az2" = {
			cidr_block = "10.0.1.0/24"
			availability_zone_id = "apne2-az2"
		}
	}
	
	tags = {}
}

module "subnet_group__private" {
	source = "tedilabs/network/aws//modules/subnet-group"
	version = "0.24.0"

	name = "${module.vpc.name}-private"
	vpc_id = module.vpc.id
	map_public_ip_on_launch = false

	subnets = {
		 "${module.vpc.name}-private-001/az1" = {
       cidr_block = "10.0.10.0/24"
 	     availability_zone_id = "apne2-az1"
      }
      "${module.vpc.name}-public-002/az2" = {
        cidr_block = "10.0.11.0/24"
        availability_zone_id = "apne2-az2"
      }	
	}

	tags = {}
}

 

 

VPC 모듈 

module "vpc" {
	source = "tedilabs/network/aws//modules/vpc"
	version = "0.24.0"

	name = "tf-vpc"
	cidr_block = "10.0.0.0/16"

	internet_gateway_enabled = true

	dns_hostnames_enabled = true
	dns_support_enabled = true

	tags = {}
}

 

우선 모듈을 작성할 때 source와 version을 작성해줘야 하는데, source는 이 모듈이 Local, Terraform Registry, Github 등 어디서 가져오냐에 따라 작성 방식이 살짝 다르다. 나의 경우 Terraform Registry에서 가져왔기 때문에 저렇게 source, version을 명시해줘야 한다. 이 내용은 다음 링크에서 자세히 확인이 가능하다.

 

Module Sources | Terraform | HashiCorp Developer

The source argument tells Terraform where to find child modules's configurations in locations like GitHub, the Terraform Registry, Bitbucket, Git, Mercurial, S3, and GCS.

developer.hashicorp.com

 

그리고 이제 다음 name, cidr_block, internet_gateway_enabled 등등은 이 모듈을 제공하는 제공자가 작성하라고 명시한 값이다. 이는 당연히 해당 모듈에 대한 문서에 나와있어야 하고 그것을 가져다가 사용할 뿐이다.

 

Subnet Group 모듈

module "subnet_group__public" {
	source = "tedilabs/network/aws//modules/subnet-group"
	version = "0.24.0"

	name = "${module.vpc.name}-public"
	vpc_id = module.vpc.id
	map_public_ip_on_launch = true

	subnets = {
		"${module.vpc.name}-public-001/az1" = {
			cidr_block = "10.0.0.0/24"
			availability_zone_id = "apne2-az1"
		}
		"${module.vpc.name}-public-002/az2" = {
			cidr_block = "10.0.1.0/24"
			availability_zone_id = "apne2-az2"
		}
	}
	
	tags = {}
}

module "subnet_group__private" {
	source = "tedilabs/network/aws//modules/subnet-group"
	version = "0.24.0"

	name = "${module.vpc.name}-private"
	vpc_id = module.vpc.id
	map_public_ip_on_launch = false

	subnets = {
		 "${module.vpc.name}-private-001/az1" = {
       cidr_block = "10.0.10.0/24"
 	     availability_zone_id = "apne2-az1"
      }
      "${module.vpc.name}-public-002/az2" = {
        cidr_block = "10.0.11.0/24"
        availability_zone_id = "apne2-az2"
      }	
	}

	tags = {}
}

 

 

이렇게 main.tf 파일을 만들고 이 파일을 init - apply 해보자.

 

이러한 결과가 나온다. 잘 만들어졌는지 AWS Console에서 확인해보자.

 

 

결론

작성한 대로, VPC와 Subnet들이 생성됐음을 확인할 수 있다. 이렇게 모듈을 사용해서 Resources를 하나하나 만들어내는게 아니라 원하

는 모듈 하나를 가져와서 그 모듈이 제공하는 리소스를 가져다가 사용할 수도 있다. 모듈이 주는 장점은 재사용성을 높인다는 것이다. 만약 VPC를 만들어 내려고 할 때마다 VPC Resource를 직접 작성해서 만들 수도 있겠지만, 같은 구성 같은 환경이라면 하나의 모듈을 만들어서 그것을 여러번 사용하는게 좀 더 효율적이지 않을까.

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

AWS + Terraform (Conditions)  (0) 2024.03.07
AWS + Terraform (For-Each)  (0) 2024.03.06
AWS + Terraform  (2) 2024.03.05
Terraform 소개 및 간단하게 다루어보기  (0) 2024.03.05
패커(Packer), Ansible 설치 및 설정  (0) 2024.03.05
728x90
반응형
SMALL
SMALL

이번엔 AWS Provider를 사용해서 Terraform을 이용해보자. 

 

AWS Provider

다음 링크는 AWS Provider 관련 링크이다.

 

Terraform Registry

 

registry.terraform.io

 

이 링크에서 우측 상단 "Documentation" 버튼을 클릭하면 어떻게 provider를 적어 넣을지 보여준다.

 

한번 우리가 기존에 만들었던 main.tf 파일에 다음 코드를 추가해보자.

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

 

그리고 그 다음에 위 예제대로 다음 코드를 추가해보자.

resource "aws_vpc" "vpc" {
  cidr_block = "10.0.0.0/16"
}

 

이 작업을 수행하기 앞서 AWS CLI가 있어야 하고 AWS CLI Configure가 진행된 상태여야 한다.

 

 

이 상태에서 tf apply를 입력하면 다음과 같이 에러가 나온다.

 

이는 무슨 에러냐면, 새로운 Provider인 AWS를 사용하기로 했으면 tf init 명령어를 다시 실행해야 한다는 얘기이다. 그래서 다시 tf init 명령어를 실행해보자. 명령어를 실행하면 AWS Provider와 관련된 plugin이 설치되고 잘 진행된다.

 

이제 plan 명령어를 한번 실행해보자.

위 실행 결과로 VPC 관련 무언가가 생성된다고 나온다. AWS에 VPC를 만들어내는 과정인듯하다. apply 명령어를 실행해보자.

실행이 정상적으로 끝나면 다음과 같이 AWS Console에서 만들어진 VPC를 확인할 수 있다.

 

이 VPC를 만들 때 이것 역시 Output을 설정해서 결과 후 내용을 확인할 수도 있다. 다음 문장을 추가하고 다시 apply 해보자.

output "aws_vpc" {
	value = aws_vpc.vpc
}

 

value = aws_vpc.vpc는 우리가 resource로 정의한 이 부분이다.

resource "aws_vpc" "vpc" {
	cidr_block = "10.0.0.0/16"
}

 

이러고 다시 apply를 하면 이러한 output이 나온다. VPC에 대한 ARN, CIDR Block, Id 등 정보가 보여진다.

 

여기 보면 여러 내용이 나오는 것을 알 수 있는데 이 말은 이렇게 VPC를 만들 때 이러한 데이터들 역시 같이 만들어 질 수 있다는 얘기가 된다. 그 중 하나인 "tags" 정보를 추가해보자.

resource "aws_vpc" "vpc" {
	cidr_block = "10.0.0.0/16"
	tags = {
		"Name" = "Terraform"
	}
}

 

apply를 다시 해보자.

 

이번엔 1개가 변경된다고 보여진다. 그 변경 사항은 "tags"다. 이렇게 어떤게 변경될지 어떻게 변경될지 다 보여준다. AWS Console로 가서 VPC를 확인해보면 다음과 같이 Name Tag가 변경된 모습을 확인할 수 있다.

 

하나 더 수정해보자. 이번엔 CIDR Block을 다음과 같이 10.123.0.0/16으로 변경해보자. 

resource "aws_vpc" "vpc" {
	cidr_block = "10.123.0.0/16"
	tags = {
		"Name" = "Terraform"
	}
}

 

이렇게 변경하고 apply를 하면 어떻게 될까? 그냥 CIDR Block만 변경될까? 다음 결과를 보자.

위 내용을 보면 1개가 추가되고 1개가 삭제된다고 나온다. 변경되는 게 아니라 기존에 있던 VPC를 삭제하고 새로운 VPC를 만들어 낸다는 뜻이다. 왜냐하면 CIDR 값은 AWS에서 변경 가능한 값이 아니다. 그렇기 때문에 변경하고자 한다면 새로 추가를 해야한다. 그리고 이 사실을 Terraform은 알고 있기 때문에 내가 10.0.0.0/16을 10.123.0.0/16으로 변경하면 기존에 있던 VPC를 삭제하고 새롭게 VPC를 만든다. 이 부분을 매우 조심해야한다. 그래서 Plan을 반드시 면밀하게 검토한 후 Apply를 해야한다.

 

 이번엔 AWS Provider에서도 Data Sources를 사용해보자. 문서에서 좌측 사이드바에 VPC (Virtual Private Cloud)를 찾고 거기서 Data Sources의 "aws_vpcs"를 찾아보자.

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpcs

 

Terraform Registry

 

registry.terraform.io

 

이 Data Source는 VPC 목록을 가져오는 Data Source이다. 이를 main.tf 파일에 다음과 같이 추가해보자.

data "aws_vpcs" "vpcs" {}

output "vpcs" {
	value = data.aws_vpcs.vpcs
}

 

추가한 후 Apply 명령어 실행하면 다음과 같이 서울 지역의 VPC 2개를 가져오는 모습을 확인할 수 있다.

 

 

Destroy

마지막으로 지금까지 했던 작업에 대한 제거 명령을 실행해보자.

tf destroy

 

이 명령어를 실행하면 지금까지 작업했던 리소스들을 제거한다고 먼저 알려준다.

 

제거를 하기 위해 "yes"를 입력하면 모든 리소스가 제거되고 AWS Console에서 VPC가 삭제됐는지 확인 가능하다.

 

 

중간 점검

AWS Provider와 Terraform을 이용해서 AWS 리소스를 만들거나 수정하거나 삭제가 가능했다. 이제 문서를 참고하여 리소스나 인프라에 대한 구축을 코드로 진행하는 한 발을 띈 셈이다.

 

EC2 Instance와 Terraform

이번엔 EC2 인스턴스를 Terraform으로 설정해보자.

https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance

 

Terraform Registry

 

registry.terraform.io

위 링크에서 EC2 Instance에 대한 Terraform 코드 작성 예시를 볼 수 있다. 예시를 따라 main.tf 파일에 다음과 같이 작성해보자.

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

resource "aws_instance" "tfec2" {
  ami           = ami-097bf0ec147165215
  instance_type = "t2.micro"

  tags = {
    Name = "terraform-ec2"
  }
}

 

AMI에서 다음 부분은 AMI의 ID이다. 

ami-097bf0ec147165215

 

이 ID는 다음 화면과 같이 알 수 있다.

 

원하는 AMI ID를 가져와서 위 코드처럼 작성한 후 "tf init", "tf apply"를 실행한다.

 

다음과 같은 구성이 새로 추가가 된다는 내용을 보여준다. 확인 후 "yes".

 

참고로, Default VPC가 없는 경우가 있을 수 있다. 나의 경우가 그렇다. 이런 경우에는 본인이 가진 VPC의 서브넷 정보도 넣어줘야 한다. (다음 코드 참고)

 

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

resource "aws_instance" "tfec2" {
  ami           = ami-097bf0ec147165215
  instance_type = "t2.micro"
  subnet_id = "subnet-xxx"

  tags = {
    Name = "terraform-ec2"
  }
}

 

이렇게 작성하고 실행하면 다음과 같이 잘 만들어진다.

 

AWS Console에서 확인해보면 잘 만들어졌다.

 

다음은 이미지를 좀 더 효율적으로 가져오는 방법에 대해서 알아보자.

AMI Image를 Data Source를 통해서 가져오기

문서를 보면 다음과 같은 예시 코드가 있다.

여기에서 data 부분을 보자. ubuntu 이미지를 가져오는 Data Source이다. 

 

다음은 가장 최신 이미지를 가져오겠다는 문장이다.

most_recent = true

 

이름으로 AMI Image를 필터링 하는데, ubuntu-22.04로 된 모든 이미지를 가져온다는 필터 부분이다.

filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
  }

아래와 같은 이미지를 말한다고 보면 된다. 

 

이 부분은 Virtualization Type이 hvm인 이미지들을 가져온다는 필터다.

filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

아래와 같이 Virtualization이 hvm이다.

 

AMI Image의 Owner를 나타낸다. 저 숫자는 Canonical을 말한다. (Ubuntu 만든 회사) 

owners = ["099720109477"] # Canonical

 

 

그래서 그 중 가장 최신 이미지를 가져오는 Data Source인 것. 그래서 이 예시를 기반으로 만든 main.tf 파일은 다음과 같다.

# main.tf

  1 provider "aws" {
  2   region = "ap-northeast-2"
  3 }
  4
  5 data "aws_ami" "ubuntu" {
  6   most_recent = true
  7
  8   filter {
  9     name   = "name"
 10     values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*"]
 11   }
 12
 13   filter {
 14     name   = "virtualization-type"
 15     values = ["hvm"]
 16   }
 17
 18   owners = ["099720109477"] # Canonical
 19 }
 20
 21 resource "aws_instance" "terraform-ec2" {
 22   ami           = data.aws_ami.ubuntu.id
 23   instance_type = "t2.micro"
 24   subnet_id = "subnet-xxx"
 25
 26   tags = {
 27     Name = "terraform-ec2"
 28   }
 29 }

 

이렇게 만든 후 init - apply를 해보자.

우리가 원하는대로 EC2 인스턴스가 잘 만들어진 것을 확인할 수 있다.

 

728x90
반응형
LIST
728x90
반응형
SMALL
SMALL

 

Terraform 소개

Terraform으로 본격적으로 인프라를 코드로 관리하기 앞서 알고 가야할 기본 내용들이 존재한다.

우선 핵심이 되는 흐름인 "Write" - "Plan" - "Apply"에 대해서 알아보자.

 

Write

Write는 리소스를 정의하는 단계이다. 테라폼이 읽을 수 있는 언어로 여러 클라우드 프로바이더 중 내가 원하는 클라우드 프로바이더에 이것 저것 인프라를 구축하기 위해 필요한 리소스를 작성하고 설계하는 단계

Plan

Plan은 Write 단계에서 작성한 리소스에 대한 정의를 가지고 어떻게 만들어질 것인가를 알려주는 단계이다. "무언가가 생겨나거나 지워지거나 등 이렇게 구성될 것이다."에 대한 내용을 보여주는 단계

Apply

Apply는 Plan에서 보여준 내용을 실제로 적용하는 단계

 

이 세 가지 핵심 흐름을 이해하면 된다.

 

그리고 Provider라는 개념과 Module이라는 개념이 존재하는데 Provider는 위에서 말한 것처럼 클라우드 프로바이더를 의미한다. 

Provider는 AWS, GCP, Azure, Kubernetes 등 여러 프로바이더가 있고 이 중 내가 원하는 프로바이더에 대한 인프라를 코드로 구성할 수 있게 된다. Module은 인프라에 대한 리소스 그룹을 말한다. 예를 들어, AWS에서 EC2를 만들어 낼 때 이 인스턴스에 대한 여러 구성이 있을 것이다. Security group, EBS Volume 등. 이 각각의 리소스를 하나의 모듈로 그룹화해서 만들어 놓으면 다음에 같은 구성을 만들 때 이 모듈을 가져다가 사용할 수 있게 된다.

 

다음 링크에서 Provider와 Module을 구경할 수 있다.

 

Terraform Registry

 

registry.terraform.io

 

대표적인 Providers는 다음 사진과 같다.

 

Module도 둘러보면 사람들이 미리 만들어놓은 자주 사용되는 리소스 구성에 대한 그룹(Module)을 볼 수도 있다.

https://registry.terraform.io/browse/modules

 

Terraform Registry

 

registry.terraform.io

 

 

Workspace

Terraform에서 Workspace는 하나의 프로젝트 단위라고 보면 된다. 근데 이제 프로젝트 규모가 커지면 인프라 전체를 하나의 워크스페이스에서 관리하던 것이 분리될 수 있다. 예를 들면 인프라 전체를 다루던 워크스페이스가 Network Workspace, Account Workspace, Service Workspace 등등 분리가 가능해질 수 있다. 

 

이제 기본적인 내용은 다 다루어봤으니 코드를 작성해보자.

 

 

main.tf

일반적으로 이러한 이름의 파일로 시작하곤 한다. 가장 먼저 Provider를 지정해야 하는데 많은 프로바이더가 있지만 간단하게 알아보는 시간이니까 Local Provider를 이용한다. Local Provider는 그냥 현재 본인의 PC라고 생각하면 된다. 하단 링크는 Local Provider에 대한 링크이다.

 

Terraform Registry

 

registry.terraform.io

해당 링크에 들어가서 보면 우측 "USE PROVIDER" 버튼이 있다. 클릭해보면 코드가 나온다.

이 코드를 main.tf 파일에 작성하자.

그리고 그 옆 "Documentation" 버튼을 클릭하면 좌측에 Resources, Data Sources 두 개가 있다.

Resources는 어떤 리소스를 만드는데 필요한 부분, Data Sources는 Resources를 만들 때 필요한 데이터가 있을 수 있다. 그 때 사용하는 부분이다. 쉽게 보면 Data Sources는 데이터를 읽는 부분이고 Resources는 그 데이터를 쓰는 부분이라고 생각하면 되겠다.

 

Provider의 Resources

Resources에 local_file 섹션으로 들어가보자. 이러한 예시가 있다.

resource "local_file" "foo" {
  content  = "foo!"
  filename = "${path.module}/foo.txt"
}

 

 

여기서 path.module은 현재 main.tf 파일이 위치한 경로를 의미한다. 그래서 main.tf 파일이 위치한 경로에 foo.txt 파일이라는 의미가 된다. 그 파일에 "foo!" 라는 텍스트를 넣는것으로 보여진다. 

 

이 내용까지 main.tf 파일에 작성해보자. 

#main.tf

provider "local" {
}

resource "local_file" "foo" {
    content = "Hello World!"
    filename = "${path.module}/foo.txt"
}

 

이렇게 작성한 다음 명령어를 입력.

terraform init

 

다음과 같이 나와야 한다.

 

저 문구가 알려주는대로 이제 다음 명령어를 입력해보자. 

terraform plan

 

근데 terraform 계속 쓰는게 길고 여간 귀찮은 작업이 아니다. 그래서 다음과 같이 alias를 붙일 수 있다.

alias tf=terraform

 

plan 명령어를 수행하면 다음과 같이 어떤 것이 생성되고 변경되고 삭제될지 보여준다.

 

결과를 보면 local_file.foo가 생성될 것이라고 알려주고 있고, content는 "Hello World!"라고 되어 있다. 파일의 권한은 어떻게 만들어지는지 등 여러 정보를 알려주면서 하단 Plan: 1 to add, 0 to change, 0 to destroy. 라고 써져있다. 그래서 이렇게 plan 명령어로 어떤 일이 일어날지 알 수 있다. 

 

이제 알았으니 apply 명령어를 수행해보자.

tf apply

 

apply 명령어를 수행하면 다음과 같이 plan 명령어를 입력했을 때 보여지는 부분이 한번 더 최종적으로 리뷰할 수 있게 보여지고 이 액션을 수행할 것인지를 마지막으로 묻는다. "yes"를 입력하면 이 작업이 수행된다.

 

어떤 파일이 만들어졌는지 확인해보자.

 

foo.txt 파일의 생성은 예측이 가능했다. terraform.tfstate 파일은 무엇일까? 열어보자.

이 파일은 그러니까 상태에 대한 파일이다. main.tf 파일을 수정하고 그 수정한 파일에 대해 apply를 하면서 생겨나는 이력과 상태라고 보면 될 것 같다.

 

Provider의 Data Sources

Data Sources는 위에서 잠깐 언급했지만 데이터를 가져다가 사용할 때 쓰는 것이라고 했다. 문서에 보면 다음과 같은 예시가 있다.

data "local_file" "foo" {
  filename = "${path.module}/foo.bar"
}

resource "aws_s3_object" "shared_zip" {
  bucket  = "my-bucket"
  key     = "my-key"
  content = data.local_file.foo.content
}

 

딱 봐도 foo라는 파일이 가진 내용을 데이터로 받아서 AWS S3에 업로드 하는 코드인 듯하다. 이렇게 Resource는 말 그대로 리소스를 만들어내는 부분 Data Sources는 그 만들 리소스가 필요할 때 사용하는 데이터라고 보면 된다.

 

그래서 이제 우리 main.tf 파일을 다음과 같이 변경해보자.

provider "local" {}

resource "local_file" "foo" {
    content = "Hello World!"
    filename = "${path.module}/foo.txt"
}

data "local_file" "bar" {
	filename = "${path.module}/bar.txt"
}

 

이렇게 만들었으니 이제 main.tf 파일이 있는 경로에서 bar.txt 파일을 만들어야 할 것 같다. 맞다.

#bar.txt

Hello DevOps!

 

이 상태로 apply 명령어를 실행해보자. 잘 실행될 것이다. 파일을 만들어 내는 것은 아니니까 어떠한 파일도 생성되진 않을건데 이 Data Sources의 데이터를 보고 싶으니까 main.tf 파일 하단에 다음과 같이 입력해보자.

output "file_bar" {
	value = data.local_file.bar
}

 

이 상태에서 다시 apply 명령어를 수행해보자. 다음과 같이 Outputs: 이 보여진다.

 

결론

이렇게 간단하게 Resources, Data Sources, Provider, Terraform 언어인 HCL을 다루어 보았다. 다음 포스팅부턴 이제 AWS Provider를 활용해 볼 것이다.

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

AWS + Terraform (Module)  (0) 2024.03.06
AWS + Terraform  (2) 2024.03.05
패커(Packer), Ansible 설치 및 설정  (0) 2024.03.05
Terraform 설치 및 설정하기  (2) 2024.03.05
IaC 개요  (2) 2024.03.05
728x90
반응형
SMALL
SMALL

 

Packer란

Packer는 이미지 빌더 도구이다. 여기서 이미지란 Docker나 AWS EC2 Image와 같은 그 이미지를 말한다. AMI 같은 경우 AWS에서 사용할 수 있지만 이 Packer는 이미지를 여러 플랫폼에서 사용할 수 있게 만들어준다.

 

 

Packer 설치하기

공식 문서를 참조하면 각자의 운영체제에 맞게 설치할 수 있다.

 

Install | Packer | HashiCorp Developer

Explore Packer product documentation, tutorials, and examples.

developer.hashicorp.com

 

나는 macOS 사용중이므로 다음 명령어를 통해 설치한다.

brew tap hashicorp/tap
brew install hashicorp/tap/packer

 

설치가 다 됐으면 버전 확인을 해보자.

 

 

Packer 자동 완성 기능 설정

packer -autocomplete-install

 

명령어 수행 후 본인의 쉘 설정 파일에 들어가서 최하단에 다음 문장 확인.

complete -o nospace -C /usr/local/bin/packer packer

 

쉘 다시 킨 후 "packer"를 입력하고 tab을 눌러 자동완성 기능 적용 확인.

 

 

Ansible 설치하기

다음 명령어를 수행하자.

brew install ansible

 

설치가 됐으면 버전 확인.

 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

AWS + Terraform (Module)  (0) 2024.03.06
AWS + Terraform  (2) 2024.03.05
Terraform 소개 및 간단하게 다루어보기  (0) 2024.03.05
Terraform 설치 및 설정하기  (2) 2024.03.05
IaC 개요  (2) 2024.03.05
728x90
반응형
SMALL
SMALL

 

Terraform 설치

 

Install | Terraform | HashiCorp Developer

Explore Terraform product documentation, tutorials, and examples.

developer.hashicorp.com

해당 문서에서 본인의 운영체제에 맞게 설치를 하면 된다.

설치가 끝나면 다음 명령어로 설치가 정상적으로 됐는지 확인한다.

terraform -version

 

버전이 잘 나오면 설치가 성공적으로 된 것이다.

 

 

Terraform 자동완성 설정

 

Install Terraform | Terraform | HashiCorp Developer

Install Terraform on Mac, Linux, or Windows by downloading the binary or using a package manager (Homebrew or Chocolatey). Then create a Docker container locally by following a quick-start tutorial to check that Terraform installed correctly.

developer.hashicorp.com

다음 링크에서 Terraform 자동완성 설정 기능을 문서를 통해 진행할 수 있다. 

 

나는 macOS이고 zsh을 사용하고 있다. 그런 경우 이런 명령어를 입력하라고 나온다.

terraform -install-autocomplete

 

입력하고 다음 명령어를 통해 zsh 설정 파일을 확인해보자.

vi ~/.zshrc

 

명령어를 잘 수행했다면 최하단에 다음 두 문장이 보일것이다.

autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C /usr/local/bin/terraform terraform

 

이 두 문장이 보이면 자동 완성은 설치된 것이고 이제 이 설정을 반영하기 위해 Shell을 껐다가 다시 키자.

 

창을 끄고 다시 켜도 되는데 그냥 다음 명령어를 입력하자. 그럼 zsh이 다시 켜지는 것과 같다.

zsh

 

이제 "terraform"을 입력한 다음 tab을 눌러보면 자동완성이 잘 된다.

 

 

Terraform CLI 및 캐시 경로 설정

terraform cli 설정을 하려면 이 파일을 작업해야 한다. "~/.terraformrc"

vi ~/.terraformrc

 

이 내용은 역시 공식 문서에 나와 있다.

 

CLI Configuration | Terraform | HashiCorp Developer

Learn to use the CLI configuration file to customize your CLI settings, including credentials, plugin caching, provider installation methods, etc.

developer.hashicorp.com

 

문서에서 테라폼 캐시를 사용하려면 이러한 문장을 넣어야한다고 나와 있다.

plugin_cache_dir   = "$HOME/.terraform.d/plugin-cache"
disable_checkpoint = true

 

설정한 다음 파일을 저장하자. 위 설정된 경로는 당연히 지금은 없을 것이다. 그래서 만들어주자.

mkdir -p ~/.terraform.d/plugin-cache

 

이렇게 작업이 끝나면 캐시를 사용할 수 있게 된다. 이를 하는 이유는 테라폼을 사용하여 여러 워크스페이스를 관리할 때 각 워크스페이스에서 필요로 하는 테라폼 프로바이더나 모듈을 다운로드를 받을 때 이 캐시 설정을 하지 않으면 기본값으로 해당 워크스페이스 경로에 플러그인 캐시가 쌓이는데 워크스페이스가 많아지면 많아질수록 이 용량이 방대해지기 때문에 이 캐시 디렉토리를 지정해서 중앙 집중식으로 관리해서 동일한 플러그인은 가져다가 사용하는 효율적인 방식을 기대하기 때문이다.

 

 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

AWS + Terraform (Module)  (0) 2024.03.06
AWS + Terraform  (2) 2024.03.05
Terraform 소개 및 간단하게 다루어보기  (0) 2024.03.05
패커(Packer), Ansible 설치 및 설정  (0) 2024.03.05
IaC 개요  (2) 2024.03.05
728x90
반응형
SMALL
SMALL

 

IaC (Instrastructure as Code)

네트워크, 로드밸런서, 저장소, 서버 등의 인프라 자원을 사람의 손으로 수동 설정이 아닌 코드를 이용하여 프로비저닝하고 관리하는 것을 IaC라고 한다. 대표적인 IaC 도구로 Terraform, CloudFormation, Pulumi, Azure ARM Template 등이 있다.

 

혼동을 주는 개념의 형상 관리라는 개념이 있다.

형상 관리 (Configuration Management)

서버 운영체제 상에 필요한 소프트웨어를 설치하고 원하는 설정으로 관리하는 것(예: 리눅스 기반 운영체제 위에 Nginx를 설치하고 프록시를 세워 웹 서버를 띄우고 웹 서버를 띄우기 위해 필요한 패키지들을 설치하고 관리하는 과정)을 말하는데 이를 코드로 작업하게 되면 Configuration as Code 라고도 불린다. 대표적인 형상 관리 도구로 Ansible, Puppet, Chef, Salt Stack 등이 있다.

 

이 두개의 차이점과 올바르게 바라보는 시선은..

정확히 이 두 개는 뭐가 다른걸까? IaC 도구도 형상 관리에 기여하고 반대도 마찬가지인데 왜 이런 다른 언어로 표현될까?

간단하게 말하자면, 도구가 어떤것을 해결하기 위해 만들어졌냐에 따라 달라진다. 예를 들어, 형상 관리 도구인 Ansible은 운영체제 상에 필요한 패키지들을 설치하고 필요한 설정들을 관리하기 위해 만들어진 도구이다. IaC 도구인 Terraform은 인프라 관리를 코드로 하기 위해 나온 도구이다. 그러나 시간이 지나면서 Terraform이 지원하는 기능이 점점 많아지고 그 기능이 형상 관리 도구가 가지는 기능까지 포함하게 되면서 혼동이 일어나게 됐다. 반대로 Ansible도 마찬가지이다. 

 

그래서, "이 두개의 다른 도구가 가장 핵심적으로 다루는 도메인이 어떤 것인가?"에 초점을 두면 될 것 같다.

인프라 관리에 가장 최적화 된 도구는 Ansible이 아닌 Terraform이다. 형상 관리에 대한 문제를 가장 잘 해결할 수 있는 도구는 Ansible이다. 그래서 하나의 도구로 모든 문제를 해결하는 All-in-One이 아니라 각 도메인 별로 해결할 수 있는 도구를 잘 선택해서 활용하는 게 중요하다.

 

코드로 관리한다는 것은

사람이 수동으로 처리하는 것을 코드로 작성하여 관리한다는 것이고 이는 휴먼 에러 방지, 재사용성, 일관성을 높인다.

이런 코드로 관리함은 또 하나의 파생적 장점을 가져다 주는데 소프트웨어 개발처럼 Git과 같은 버전 관리 시스템을 활용할 수 있게 된다. 이는 코드 리뷰, 변경 내용 추적, 버전 관리, 협업을 할 수 있게 해준다.

 

이렇게 코드로 관리하고 설정하는 것을 두가지 형태로 나눌 수 있는데 선언형 설정(Declarative Configuration)과 절차형 설정(Imperative Configuration)이다. 선언형 설정은 특정 상태에 어떤 작업을 지정하고 어떤 작업은 수행하지 않는 선언을 하므로써 설정을 하는 것이고 절차형 설정은 순차적으로 명령어를 수행한다는 것과 같은데 예를 들면 리눅스 기반 운영체제 서버에 들어가고 패키지를 업데이트하고 원하는 패키지를 설치하고 등등이 있다.

 

이러한 개념을 토대로 이제 Terraform과 Ansible에 대한 공부를 시작해보자.

 

728x90
반응형
LIST

'IaC(Infrastructure as Code)' 카테고리의 다른 글

AWS + Terraform (Module)  (0) 2024.03.06
AWS + Terraform  (2) 2024.03.05
Terraform 소개 및 간단하게 다루어보기  (0) 2024.03.05
패커(Packer), Ansible 설치 및 설정  (0) 2024.03.05
Terraform 설치 및 설정하기  (2) 2024.03.05
728x90
반응형
SMALL
SMALL

 

Requisites

 

우선 AWS CLI를 사용해야한다. 그러기 위해서 AWS CLI를 먼저 설치를 해야하고 구성도 해야한다. 이 부분은 이전 포스팅에서도 다루기도 했고 공식 문서로도 잘 되어 있으니까 생략한다.

 

boto3

Python과 DynamoDB를 연동하기 위해서 필요한 패키지인 'boto3'를 설치해야한다. 

근데 설치하기 앞서, 나는 Global로 파이썬 패키지를 설치하는것을 싫어한다. 그래서 가상환경을 세팅해줄 것이다.

 

작업하길 원하는 경로에서 다음 명령어를 입력한다. 이는 가상환경을 구성하는 명령어이다.

python3 -m venv ./

 

가상환경을 구성했으면 가상환경을 실행한다.

source ./bin/activate

 

가상환경을 실행하면 자동으로 가상환경 내부로 들어온다. 이 내부에서 다음 명령어를 실행한다.

pip3 install boto3

 

이제 동일 경로에서 파일 하나를 만든다. 나는 다음과 같이 dynamoaccess.py 라는 파일을 만들었다.

# dynamoaccess.py

import boto3

client = boto3.client('dynamodb')

data = client.put_item(TableName='Orders', Item={'OrderID': {'S': '1'}, 'Date': {'S': '20240305'}})

 

따로 어떤 dynamodb를 사용할지 내가 누구인지는 작성할 필요없다. 왜냐하면 aws-cli에서 이미 설정을 했기 때문이다.

이 파일을 실행하면 된다.

python3 dynamoaccess.py

 

실행해서 아무런 반응이 없이 실행이 끝나면 성공한 것. 이제 넣으려고 했던 데이터가 잘 들어갔는지 확인해보면 된다.

 

아래와 같이 정상적으로 데이터가 들어갔다.

 

이제 이렇게 DynamoDB에 데이터를 넣어봤으니 한번 읽기도 해보자.

파일 하나를 더 만들자. 나는 dynamoread.py 라는 파일을 만들었다.

 

# dynamoread.py

import boto3

client = boto3.client('dynamodb')

data = client.get_item(TableName='Orders', Key={'OrderID': {'S': '1'}, 'Date':{'S': '20240305'}})

print(data)

 

이 파일을 실행시켜보자.

python3 dynamoread.py

 

다음과 같이 잘 읽어오는 것을 확인할 수 있다.

 

728x90
반응형
LIST

'AWS' 카테고리의 다른 글

AWS Lambda와 DynamoDB를 연동해보기  (0) 2024.03.04
AWS Lambda와 Step functions  (0) 2024.03.04
AWS Lambda와 Layers  (0) 2024.03.04
AWS Lambda와 API Gateway로 Canary Release 하기  (0) 2024.03.03
AWS Lambda를 API Gateway에 등록하기  (0) 2024.03.03
728x90
반응형
SMALL
SMALL

 

DynamoDB

DynamoDB를 알기 전에 기존의 데이터베이스 형태에 대해서 먼저 알아보자. 기존의 데이터베이스의 형태는 거의 대부분이 관계형 데이터베이스였다. 예를 들면 MySQL, PostgreSQL 등이 있다. 

이런 관계형 데이터베이스의 가장 큰 특징은 데이터에 대한 형식이 아주 견고하게 존재한다는 것이다. 그래서 형식에 맞지 않는 데이터를 넣지 못한다. 그 말은 어떤 데이터를 넣더라도 예측 가능한 데이터가 된다. 그러나 이러한 점이 단점으로 부각되기 시작한다. 그 내용은 기업이 활용할 수 있는 데이터가 다양해지면서 데이터의 형식이 다양해지고 내용이 방대해졌다는 뜻이다. 그 말은 예측이 불가능한 데이터가 더 많아진다는 뜻이다. 

 

그래서 이러한 단점(자유도가 떨어진다)을 극복하기 위해 다른 방식의 데이터베이스가 생겨나기 시작했다. 

즉, 어떤 정제가 되지 않은, 있는 그대로의 데이터를 모두 받아들일 수 있는 AWS S3와 같은 DataLakeNoSQL(Not Only SQL)과 같은 데이터베이스가 점점 두각되는 추세이다. 이 NoSQL의 하나가 DynamoDB이다.

 

NoSQL은 말 그대로 SQL만을 취급하지 않는다는 것이다. 즉, 형식이 지정되어 있기도 하지만 지정되지 않은 상태여도 무방하다라는 뜻이다. 

 

DynamoDB 키 포인트

DynamoDB의 특징은 다음과 같다.

  • NoSQL
  • 수평확장(Scale-Out)이 쉽고 유연
  • Auto-Scaling 지원 / 일정 기간 백업 지원
  • 스키마 지정이 필요 X
  • 트랜잭션, 조인과 같은 복잡한 쿼리 불가능

 

Attribute, Partition Key, Sort Key, Primary Key, GSI

- Attribute: Key/Value쌍으로 된 형식이 고정되지 않은 자유로운 데이터.

- Partition Key: Primary key는 아니지만 그와 비슷한 키를 의미한다. 중복이 될 수 있다.

- Sort Key: 또 하나의 키. 이 또한 중복이 될 수 있다.

- Primary Key: Partition Key + Sort Key. 이는 중복이 될 수 없다. 중복된 데이터가 들어오면 받아주지 않는다.

- GSI(Global Secondary Index): 아이템에 대한 키 역할을 하지는 않지만 인덱스(필터링하기 위한)로서의 역할을 할 수 있는 데이터가 Attribute내에 존재할 때 그 값을 따로 빼어내서 인덱스로 만드는 것.

Partition Key Sort Key GSI Attribute
OrderID Date Region Attribute
1 210701 Seoul {...json...}
2 210701 Busan {...json...}
2 210702 Busan {...json...}
3 210703 Daegu {...json...}

 

Sort Key, GSI와 같은 인덱스는 없어도 된다. 그러나 없을 때 Partition Key가 고유값이 아니면 데이터에 대한 무결성을 장담할 수 없게된다. 이렇기 때문에 Sort Key가 필요해지고 이 Sort key와 Partition key의 결합이 Primary key가 되는 것.

 

 

DynamoDB 생성하기

AWS Console에 'DynamoDB'를 검색해서 나온 서비스를 클릭한다.

 

최초 화면은 다음과 같이 보여질 것이다. 우측 'Create table' 버튼 클릭

 

이제 설정하는 부분인데 이 설정 부분이 꽤나 양이 많다. 우선 다음을 보자.

- Table name: Orders

- Partition key: OrderID

- Sort key: Date

위에서 배운대로 Partition key와 Sort key 두 개를 모두 설정하겠다. 이 두개가 더해진 것이 Primary key가 된다. 다른 말로 이 두개가 같은 Item은 있을 수 없다.

 

- Table settings: Customize settings

- Table class: DynamoDB Standard

- Read/write capacity settings: On-demand

Provisioned를 사용하지 않는 이유는 Provisioned는 미리 할당된 만큼의 데이터 비용을 지불한다는 것인데 나는 실습차원에서 만드는 것도 있고 Provisioned는 일반적으로 예측 가능한 트래픽인 경우에 사용한다. 트래픽의 등락이 크면 On-demand가 유리하다.

 

- Secondary indexes: 여기서 Global Index가 위에서 말한 GSI이다. 이후에 생성해도 되고 지금은 필요도 없기 때문에 넘어간다.

- Encryption at rest: Owned by Amazon DynamoDB

 

이렇게 설정한 후 테이블을 최종적으로 만든다. 테이블을 만들면 다음과 같이 리스트에 만든 테이블 하나가 노출된다.

 

저 안으로 들어가보면 다음과 같이 생겼다. 테이블에 대한 정보가 이렇게 보여지고, 아이템을 추가할 수 있다.

 

우측 상단 Actions > Create item 버튼을 클릭해서 아이템 하나를 만들어보자.

 

이러한 화면에서 원하는 값을 입력하면 된다.

 

이 상태에서 새로운 속성을 더 넣어주고 싶으면 우측 "Add new attribute" 버튼을 클릭해서 자유롭게 추가가 가능하다. 이게 위에서 말한 형식에서의 자유로움이다.

 

그리고 이 상태에서 우측 상단 "JSON view" 버튼을 클릭하면 이 데이터를 JSON으로는 어떻게 넣는지 보여준다.

여기서 "S"가 어떤 의미냐면 "String"의 의미이다. 

 

이 상태에서 "Create item" 버튼을 클릭해보자. 그럼 다음과 같이 리스트에 만든 아이템 하나가 보여진다.

 

그리고 이 화면에서 쿼리도 날릴 수 있다. 내가 원하는 데이터만 추출할 때 유용하다.

 

이런식으로 DynamoDB를 이용할 수 있다. 이제 Lambda를 만들어서 이 DynamoDB와 연동해보자.

 

Lambda function에서 DynamoDB 접근하기

다음과 같이 만들어 준다. 런타임은 Node.js 16.x를 사용해보자.

우선 코드를 수정하기 전 이 람다함수가 DynamoDB에 접근할 수 있도록 권한 설정을 해주어야한다.

다음과 같이 Configuration > Permissions 에 들어가보면 적용된 Role이 보여진다. Role 클릭.

 

다음과 같이 해당 역할 내부로 들어와서 Permissions policies 섹션에 우측 Add permissions > Attach policies 버튼 클릭

 

DynamoDB를 검색해서 나오는 AmazonDynamoDBFullAccess를 선택 후 Add permissions 클릭

 

그럼 이렇게 Permissions policies 섹션에 이제 방금 추가한 정책이 들어가 있다.

 

 

이제 람다의 코드를 수정해보자. 다음과 같이 작성한다.

 

 

우선 "aws-sdk"를 임포트해야 한다. 이 녀석으로부터 AWS에 접근해야 하기 때문이다.

const AWS = require("aws-sdk");

 

그리고 이 녀석을 통해 DynamoDB를 가져온다. 가져올 땐 지역을 설정해줘야 한다. 당연히 내가 만든 DynamoDB에 대한 지역을 설정.

const ddb = new AWS.DynamoDB.DocumentClient({"region": "ap-northeast-2"});

 

그리고 리턴할 handler를 만든다. 이 함수는 비동기 함수로 우선 requestId를 받아 저장한다.

그리고 하단에 만든 createMessage()를 호출하는데 이 함수는 인자로 requestId를 넘겨주고 콜백함수를 호출한다. 이는 리턴 객체를 보내기 위함이다.

exports.handler = async (event, context, callback) => {
  const requestId = context.awsRequestId;
  
  await createMessage(requestId).then(() => {
    callback(null, {
      statusCode: 201,
      body: "",
      headers: {
        "Access-Control-Allow-Origin": "*"
      }
    })
  }).catch((err) => console.error(err))
};

 

하단 createMessage()는 변수로 requestId를 받아서 DynamoDB에 아이템 하나를 추가한다. 그리고 최종적으로 위에서 선언한 DynamoDB 객체에 해당 아이템을 put()한다.

function createMessage(requestId) {
  const params = {
    TableName: "Orders",
    Item: {
      "OrderID": requestId,
      "Date": "20240304"
    }
  }
  
  return ddb.put(params).promise();
}

 

이제 Deploy를 하고 테스트를 진행해보자. 다음과 같이 테스트를 만들고 실행한다.

 

실행하면 다음과 같이 정상 결과가 출력된다.

 

이제 DynamoDB에 가서 정상적으로 아이템이 추가됐는지 확인해보자. 다음과 같이 잘 넣어졌다.

 

 

 

728x90
반응형
LIST

'AWS' 카테고리의 다른 글

AWS DynamoDB를 Python과 연동해보기  (2) 2024.03.05
AWS Lambda와 Step functions  (0) 2024.03.04
AWS Lambda와 Layers  (0) 2024.03.04
AWS Lambda와 API Gateway로 Canary Release 하기  (0) 2024.03.03
AWS Lambda를 API Gateway에 등록하기  (0) 2024.03.03

+ Recent posts