테라폼/T101[3기]

T101 - 2주차

시스템 엔지니어 2023. 9. 9. 22:23

목표

  • 테라폼 기본 사용법에 대해 알아본다.
  • 데이터소스, 변수, 로컬, 출력, 반복문에 대해서 알아 본다.

데이터 소스

데이터 소스는 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용한다

  • 식별자는 동일한 유형에서 중복될 수 없다.
  • 이름 뒤에는 데이터 소스 유형에 대한 구성 인수들은 { } 안에 선언한다. 
    인수가 필요하지 않은 유형도 있지만, 그때에도 { } 는 입력한다
  • 데이터 소스를 정의할 때 사용 가능한 메타인수는 다음과 같다.
    • depends_on : 종속성을 선언하며, 선언된 구성요소와의 생성 시점에 대해 정의
    • count : 선언된 개수에 따라 여러 리소스를 생성
    • for_each : map 또는 set 타입의 데이터 배열의 값을 기준으로 여러 리소스를 생성
    • lifecycle : 리소스의 수명주기 관리

 

실습 확인을 위해서 abc.txt 파일 생성
$ cat abc.txt
t101 study - 2week
테라폼 코드 적용
$ terraform init && terraform plan && terraform apply -auto-approve
data.local_file.abc: Reading...

 

 

데이터소스 생성 확인
$ terraform state list
data.local_file.abc
$ echo "data.local_file.abc" | terraform console

 

[도전과제1] 위 리전 내에서 사용 가능한 가용영역 목록 가져오기를 사용한 VPC 리소스 생성 실습 진행, 또는 관련된 데이터 소스를 사용한 실습한 내용을 글로 작성.

# Declare the data source
data "aws_availability_zones" "available" {
  state = "available"
}
resource "aws_subnet" "primary" {
  availability_zone = data.aws_availability_zones.available.names[0]
  # e.g. ap-northeast-2a
}
resource "aws_subnet" "secondary" {
  availability_zone = data.aws_availability_zones.available.names[1]
  # e.g. ap-northeast-2b
}

 

 

aws_vpc 리소스 생성
vpc_id 연결 필요 확인
data "aws_availability_zones" "available" {
  state = "available"
}

# aws_vpc 생성
resource "aws_vpc" "test01-vpc" {
    cidr_block = "10.10.0.0/16"

    tags = {
        Name = "test-vpc"
    }
}

resource "aws_subnet" "primary" {
  # vpc_id 추가
  vpc_id = aws_vpc.test01-vpc.id
  availability_zone = data.aws_availability_zones.available.names[0]
}

resource "aws_subnet" "secondary" {
  # vpc_id 추가
  vpc_id = aws_vpc.test01-vpc.id
  availability_zone = data.aws_availability_zones.available.names[1]
}

 

terraform plan에서는 문제가 없었지만,
terraform apply시 에러가 발생되면서, 서브넷이 생성되지 않았다.
aws_subnet 리소스에서 추가적인 에러가 발견 되었다.
cidrBlock인데 이것도 명시해줘야 하는것 같았다.

 

 

cidr 인수를 추가해줬다.
data "aws_availability_zones" "available" {
  state = "available"
}

# aws_vpc 생성
resource "aws_vpc" "test01-vpc" {
    cidr_block = "10.10.0.0/16"

    tags = {
        Name = "test-vpc"
    }
}

resource "aws_subnet" "primary" {
  # vpc_id 추가
  vpc_id = aws_vpc.test01-vpc.id
  # cidr_block 추가
  cidr_block = "10.10.0.0/24"
  availability_zone = data.aws_availability_zones.available.names[0]
}

resource "aws_subnet" "secondary" {
  # vpc_id 추가
  vpc_id = aws_vpc.test01-vpc.id
  # cidr_block 추가
  cidr_block        = "10.10.1.0/24"
  availability_zone = data.aws_availability_zones.available.names[1]
}

 

 

 

의존관계도 확인 했다.

 

 

변수

입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적이 있다.

테라폼에서는 이것을 입력 변수 Input Variables 로 정의한다.

  • 변수 정의 시 사용 가능한 메타인수
    • default : 변수 값을 지정하지 않으면 기본값이 전달됨, 기본값이 없으면 대화식으로 사용자에게 변수에 대한 정보를 물어봄
    • type : 변수에 허용되는 값 유형 정의, 유형을 지정하지 않으면 any 유형으로 간주
      (string, number, bool, list, map, set, object, tuple)
    • description : 입력 변수의 설명
    • validation : 변수 선언의 제약조건을 추가해 유효성 검사 규칙을 정의 - 링크
    • sensitive : 민감한 변수 값임을 알리고 테라폼의 출력문에서 값 노출을 제한 (암호 등 민감 데이터의 경우) - 링크
      • sensitive 처리하시더라도 state 파일에 평문으로 남게 됨 - 링크
      • 이러한 부분을 처리하기 위해 TFC/TFE를 통해 안전하게 저장하거나 S3 암호화 기능등을 통해서 보안을 강화 필요
    • nullable : 변수에 값이 없어도 됨을 지정
  • 기본 유형
    • string : 글자 유형
    • number : 숫자 유형
    • bool : true 또는 false
    • any : 명시적으로 모든 유형이 허용됨을 표시
  • 집합 유형
    • list (<유형>): 인덱스 기반 집합
    • map (<유형>): 값 = 속성 기반 집합이며 키값 기준 정렬
    • set (<유형>): 값 기반 집합이며 정렬 키값 기준 정렬
    • object ({<인수 이름>=<유형>, …})
    • tuple ([<유형>, …])
  • 유효성 검사 : 입력되는 변수 타입 지정 이외, 사용자 지정 유효성 검사가 가능
    • 변수 블록 내에 validation 블록에서 조건인 condition에 지정되는 규칙이 true 또는 false를 반환해야 하며, error_message는 condition 값의 결과가 false 인 경우 출력되는 메시지를 정의한다.
    • regex 함수는 대상의 문자열에 정규식을 적용하고 일치하는 문자열을 반환하는데, 여기에 can 함수를 함께 사용하면 정규식에 일치하지 않는 경우의 오류를 검출한다.
    • validation 블록은 중복으로 선언할 수 있다.
variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."

  validation {
    condition     = length(var.image_id) > 4
    error_message = "The image_id value must exceed 4."
  }

  validation {
    # regex(...) fails if it cannot find a match
    condition     = can(regex("^ami-", var.image_id))
    error_message = "The image_id value must starting with \"ami-\"."
  }
}

 

default 값이 없어 value 프롬프트가 발생했고,
4글자 미만으로 에러 메시지가 발생했다.

 

  • 변수 참조 : variable은 코드 내에서 var.<이름>으로 참조된다.
variable "my_password" {}

resource "local_file" "abc" {
  content  = var.my_password
  filename = "${path.module}/abc.txt"
}

 

변수를 참조할 수 있다.

  • 변수 입력 방식과 우선순위
    • variable의 목적은 코드 내용을 수정하지 않고 테라폼의 모듈적 특성을 통해 입력되는 변수로 재사용성을 높이는 데 있다.
    • 특히 입력 변수라는 명칭에 맞게 사용자는 프로비저닝 실행 시에 원하는 값으로 변수에 정의할 수 있다.
    • 선언되는 방식에 따라 변수의 우선순위가 있으므로, 이를 적절히 사용해 로컬 환경과 빌드 서버 환경에서의 정의를 다르게 하거나, 프로비저닝 파이프라인을 구성하는 경우 외부 값을 변수에 지정할 수 있다.

 

  • 민감한 변수 취급 : 입력 변수의 민감 여부 선언 가능
    • 기본값 추가로 입력 항목은 발생하지 않지만, 출력에서 참조되는 변수 값이(sensitive)로 감춰지는 것을 확인 할 수 있다
variable "my_password" {
  default   = "password"
  sensitive = true
}

resource "local_file" "abc" {
  content  = var.my_password
  filename = "${path.module}/abc.txt"
}

 

sensitive 설정 된 것을 확인 할 수 있다.

  • 변수 입력 방식과 우선순위
    • variable의 목적은 코드 내용을 수정하지 않고 테라폼의 모듈적 특성을 통해 입력되는 변수로 재사용성을 높이는 데 있다.
    • 특히 입력 변수라는 명칭에 맞게 사용자는 프로비저닝 실행 시에 원하는 값으로 변수에 정의할 수 있다.
    • 선언되는 방식에 따라 변수의 우선순위가 있으므로, 이를 적절히 사용해 로컬 환경과 빌드 서버 환경에서의 정의를 다르게 하거나, 프로비저닝 파이프라인을 구성하는 경우 외부 값을 변수에 지정할 수 있다.
    • https://spacelift.io/blog/terraform-tfvars

도전과제2: 위 3개 코드 파일 내용에 리소스의 이름(myvpc, mysubnet1 등)을 반드시! 꼭! 자신의 닉네임으로 변경해서 배포 실습해보세요!

vpc.tf

data "aws_availability_zones" "available" {
  state = "available"
}

# aws_vpc 생성
resource "aws_vpc" "thumbup-vpc" {
    cidr_block = "10.10.0.0/16"

    tags = {
        Name = "thumbup-vpc"
    }
}

resource "aws_subnet" "thumbup-subnet1" {
  # vpc_id 추가
  vpc_id = aws_vpc.thumbup-vpc.id
  cidr_block = "10.10.0.0/24"
  availability_zone = data.aws_availability_zones.available.names[0]
}

resource "aws_internet_gateway" "myigw" {
  vpc_id = aws_vpc.thumbup-vpc.id
}

sg.tf

resource "aws_security_group" "mysg" {
  vpc_id      = aws_vpc.thumbup-vpc.id
  name        = "T101 SG"
  description = "T101 Study SG"
}

resource "aws_security_group_rule" "mysginbound" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

resource "aws_security_group_rule" "mysgoutbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

ec2.tf

resource "aws_security_group" "mysg" {
  vpc_id      = aws_vpc.thumbup-vpc.id
  name        = "T101 SG"
  description = "T101 Study SG"
}

resource "aws_security_group_rule" "mysginbound" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

resource "aws_security_group_rule" "mysgoutbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

 

 

local 지역 값

코드 내에서 사용자가 지정한 값 또는 속성 값을 가공해 참조 가능한 local (지역 값)은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값을 선언한다.

  • `local`은 입력 변수와 달리 선언된 모듈 내에서만 접근 가능하고, 변수처럼 실행 시에 입력받을 수 없다.
  • `local`은 사용자가 테라폼 코드를 구현할 때 이나 표현식을 반복적으로 사용할 수 있는 편의를 제공한다.
  • 하지만 빈번하게 여러 곳에서 사용되는 경우 실제 값에 대한 추적이 어려워져 유지 관리 측면에서 부담이 발생할 수 있으므로 주의해야 한다.
  • lcoals에 선언한 로컬 변수 이름은 전체 루트 모듈 내에서 유일해야 한다.
  • 정의되는 속성 값은 지정된 값의 형태에 따라 다양한 유형으로 정의할 수 있다.

출력 output

출력 값은 주로 테라폼 코드의 프로비저닝 수행 후의 결과 속성 값을 확인하는 용도로 사용된다.

또한 프로그래밍 언어에서 코드 내 요소 간에 제한된 노출을 지원하듯, 테라폼 모듈 간, 워크스페이스 간 데이터 접근 요소로도 활용할 수 있다.

  • 루트 모듈에서 사용자가 확인하고자 하는 특정 속성 출력
  • 자식 모듈의 특정 값을 정의하고 루트 모듈에서 결과를 참조
  • 서로 다른 루트 모듈의 결과를 원격으로 읽기 위한 접근 요소
resource "local_file" "abc" {
  content  = "abc123"
  filename = "${path.module}/abc.txt"
}

output "file_id" {
  value = local_file.abc.id
}

output "file_abspath" {
  value = abspath(local_file.abc.filename)
}

 

결과값
Outputs:

file_abspath = "/home/shin/workspaces/3.8/abc.txt"
file_id = "6367c48dd193d56ea7b0baad25b19455e529f5ee"

 

반복문

list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 동일한 내용에 대해 테라폼 구성 정의를 반복적으로 하지 않고 관리할 수 있다.

 

  • count : 반복문, 정수 값만큼 리소스나 모듈을 생성
resource "local_file" "abc" {
  count    = 5
  content  = "abc${count.index}"
  filename = "${path.module}/abc${count.index}.txt"
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}

output "filecontent" {
  value = local_file.abc.*.content
}

 

 

  • for_each : 반복문, 선언된 key 값 개수만큼 리소스를 생성
    • 리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map 또는 set이면, 선언된 key 값 개수만큼 리소스를 생성하게 된다
    • 특징: key 값은 count의 index와는 달리 고유하므로 중간에 값을 삭제한 후 다시 적용해도 삭제한 값에 대해서만 리소스를 삭제한다.
variable "names" {
  default = {
    a = "content a"
    b = "content b"
    c = "content c"
  }
}

resource "local_file" "abc" {
  for_each = var.names
  content  = each.value
  filename = "${path.module}/abc-${each.key}.txt"
}

resource "local_file" "def" {
  for_each = local_file.abc
  content  = each.value.content
  filename = "${path.module}/def-${each.key}.txt"
}

  • for : 복합 형식 값의 형태를 변환하는 데 사용 ← for_each와 다름
    • for 구문을 사용하는 몇 가지 규칙은 다음과 같다
      • list 유형의 경우 반환 받는 값이 하나로 되어 있으면 을, 두 개의 경우 앞의 인수가 인덱스를 반환하고 뒤의 인수가 을 반환
        • 관용적으로 인덱스는 i, 값은 v로 표현
      • map 유형의 경우 반환 받는 값이 하나로 되어 있으면 를, 두 개의 경우 앞의 인수가 를 반환하고 뒤의 인수가 을 반환
        • 관용적으로 키는 k, 값은 v로 표현
      • 결과 값은 for 문을 묶는 기호가 **[ ]**인 경우 tuple로 반환되고 **{ }**인 경우 object 형태로 반환
      • object 형태의 경우 에 대한 쌍은 기호로 구분
      • { } 형식을 사용해 object 형태로 결과를 반환하는 경우 키 값은 고유해야 하므로 값 뒤에 그룹화 모드 심볼(…)를 붙여서 키의 중복을 방지(SQL의 group by 문 또는 Java의 MultiValueMap과 같은 개념)
      • if 구문을 추가해 조건 부여 가능
variable "names" {
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  content  = jsonencode([for s in var.names : upper(s)]) # 결과 : ["A", "B", "C"]
  filename = "${path.module}/abc.txt"
}
  • dynamic : 리소스 내부 속성 블록 동적인 블록으로 생성
    • 리소스 내의 블록 속성(Attributes as Blocks)은 리소스 자체의 반복 선언이 아닌 내부 속성 요소 중 블록으로 표현되는 부분에 대해서만 반복 구문을 사용해야 하므로, 이때 dynamic 블록을 사용해 동적인 블록을 생성 할 수 있다.
    • dynamic 블록을 작성하려면, 기존 블록의 속성 이름을 dynamic 블록의 이름으로 선언하고 기존 블록 속성에 정의되는 내용을 content 블록에 작성한다.
    • 반복 선언에 사용되는 반복문 구문은 for_each를 사용한다. 기존 for_each 적용 시 each 속성에 key, value가 적용되었다면 dynamic에서는 dynamic에 지정한 이름에 대해 속성이 부여된다.
    • dynamic 블록 활용 예

 

느낀점

기본이 있어야 예상치 못했던 이슈를 줄일 수 있는데,
스터디에서 기본을 학습하면서 서비스에서 사용시
조심해야 할 부분들 알 수 있었던 것 같습니다.