1. 개요

IaC(Infrastructure-as-Code)는 AWS, Google 및 Microsoft와 같은 퍼블릭 클라우드 제공업체의 인기가 높아짐에 따라 주류가 된 방식입니다. 간단히 말해서 개발자가 애플리케이션 코드를 관리하는 데 사용하는 것과 동일한 접근 방식을 사용하여 리소스 집합(컴퓨팅, 네트워크, 스토리지 등)을 관리하는 것으로 구성됩니다 .

이 사용방법(예제)에서는 DevOps 팀에서 인프라 작업을 자동화하는 데 사용하는 가장 인기 있는 도구 중 하나인 Terraform을 빠르게 둘러볼 것입니다. Terraform의 주요 매력은 인프라가 어떻게 생겼는지 선언 하면 도구가 해당 인프라를 "구현"하기 위해 취해야 할 조치를 결정한다는 것입니다.

2. 간략한 역사

GitHub에 따르면 Terraform의 첫 번째 커밋 날짜는 2014년 5월 21일이었습니다. 작성자는 Hashicorp의 창립자 중 한 명인 Mitchell Hashimoto였으며 "미션 선언문"이라고 부를 수 있는 내용을 설명 하는 README 파일 만 포함 합니다.

Terraform은 인프라를 안전하고 효율적으로 구축하고 변경하기 위한 도구입니다.

이 구절은 그 의도를 잘 설명하고 있습니다. 그 이후로 이 도구는 지원하는 인프라 제공자 측면에서 기능을 꾸준히 향상시켜 왔습니다.

이 글을 쓰는 시점에서 Terraform은 공식적으로 약 130개의 공급자를 지원합니다 . 커뮤니티 지원 제공자 페이지에는 또 다른 160개가 나열되어 있습니다. 이러한 제공자 중 일부는 몇 가지 리소스만 공개하지만 AWS 또는 Azure와 같은 다른 제공자는 수백 개의 리소스를 제공합니다.

이렇게 지원되는 리소스가 많기 때문에 Terraform은 많은 DevOps 엔지니어가 선택하는 도구입니다. 또한 단일 도구를 사용하여 여러 공급업체를 관리할 수 있다는 것도 큰 이점입니다.

3. 안녕하세요, Terraform입니다.

내부 작동에 대해 자세히 알아보기 전에 기본 설정부터 시작하겠습니다. 초기 설정과 빠른 "Hello, World" 스타일 프로젝트입니다.

3.1. 다운로드 및 설치

Terraform 배포는 Hashicorp의 다운로드 페이지 에서 자유롭게 다운로드할 수 있는 단일 바이너리 파일로 구성됩니다 . 의존성은 없으며 운영 체제의 PATH에 있는 일부 폴더에 실행 가능한 바이너리를 복사하여 간단히 실행할 수 있습니다 .

이 단계를 완료하면 간단한 명령으로 올바르게 작동하는지 확인할 수 있습니다.

$ terraform -v
Terraform v0.12.24

그게 다야 — 관리자 권한이 필요하지 않습니다! 인수 없이 Terraform을 실행하여 사용 가능한 명령에 대한 빠른 도움말을 얻을 수 있습니다.

$ terraform
Usage: terraform [-version] [-help] <command> [args]
... help content omitted

3.2. 첫 번째 프로젝트 만들기

Terraform 프로젝트는 리소스 정의를 포함하는 디렉토리에 있는 파일 세트일 뿐입니다 . 규칙에 따라 .tf 로 끝나는 이러한 파일 Terraform의 구성 언어 를 사용하여 생성하려는 리소스를 정의합니다.

"Hello, Terraform" 프로젝트의 경우 리소스는 콘텐츠가 고정된 파일일 뿐입니다. 계속해서 명령 셸을 열고 몇 가지 명령을 입력하여 이것이 어떻게 보이는지 봅시다.

$ cd $HOME
$ mkdir hello-terraform
$ cd hello-terraform
$ cat > main.tf <<EOF
provider "local" {
  version = "~> 1.4"
}
resource "local_file" "hello" {
  content = "Hello, Terraform"
  filename = "hello.txt"
}
EOF

main.tf의 A : 파일은 두 블록이 포함되어 제공자 선언과 자원 정의를. 제공자 선언은 우리가 사용합니다한다고 현지  버전 1.4 호환되는 1에서 제공합니다.

다음으로 local_file 유형의 hello 라는 리소스 정의 가 있습니다 . 리소스 유형은 이름에서 알 수 있듯이 주어진 콘텐츠가 있는 로컬 파일 시스템의 파일일 뿐입니다.

3.3. 초기화, 계획적용

이제 이 프로젝트에서 Terraform을 실행해 보겠습니다. 이 프로젝트를 처음 실행하는 것이므로 init 명령 으로 초기화해야 합니다 .

$ terraform init

Initializing the backend...

Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "local" (hashicorp/local) 1.4.0...

Terraform has been successfully initialized!
... more messages omitted

이 단계에서 Terraform은 프로젝트 파일을 스캔하고 필요한 모든 공급자(이 경우 로컬 공급자)를 다운로드합니다  .

다음으로 plan 명령을 사용하여 Terraform이 리소스를 생성하기 위해 수행할 작업을 확인합니다. 이 단계는 GNU의 make 도구와 같은 다른 빌드 시스템에서 사용할 수 있는 "테스트 실행" 기능과 거의 유사합니다.

$ terraform plan
... messages omitted
Terraform will perform the following actions:

  # local_file.hello will be created
  + resource "local_file" "hello" {
      + content              = "Hello, Terraform"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "hello.txt"
      + id                   = (known after apply)
    }

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

여기에서 Terraform은 아직 존재하지 않기 때문에 예상되는 새 리소스를 생성해야 한다고 말합니다. 또한 우리가 설정한 제공된 값과 한 쌍의 권한 속성을 볼 수 있습니다. 리소스 정의에 제공하지 않았으므로 공급자는 기본값을 가정합니다.

이제 apply  명령을 사용하여 실제 리소스 생성을 진행할 수 있습니다  .

$ terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.hello will be created
  + resource "local_file" "hello" {
      + content              = "Hello, Terraform"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "hello.txt"
      + id                   = (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

local_file.hello: Creating...
local_file.hello: Creation complete after 0s [id=392b5481eae4ab2178340f62b752297f72695d57]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

이제 지정된 내용으로 파일이 생성되었는지 확인할 수 있습니다.

$ cat hello.txt
Hello, Terraform

문제 없다! 이제 적용 명령 을 다시 실행 하면 어떤 일이 발생하는지 보겠습니다.  이번에는  -auto-approve 플래그를 사용하여 Terraform이 확인을 요청하지 않고 바로 실행되도록 합니다.

$ terraform apply -auto-approve
local_file.hello: Refreshing state... [id=392b5481eae4ab2178340f62b752297f72695d57]

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

이번에는 파일이 이미 존재하기 때문에 Terraform은 아무 것도 하지 않았습니다. 하지만 그게 다가 아닙니다. 리소스가 존재하지만 누군가가 속성 중 하나를 변경했을 수 있습니다. 이러한 시나리오를 일반적으로 "구성 드리프트"라고 합니다.  이 시나리오에서 Terraform이 어떻게 작동하는지 봅시다.

$ echo foo > hello.txt
$ terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

local_file.hello: Refreshing state... [id=392b5481eae4ab2178340f62b752297f72695d57]

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # local_file.hello will be created
  + resource "local_file" "hello" {
      + content              = "Hello, Terraform"
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "hello.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.
... more messages omitted

Terraform은 hello.txt 파일 콘텐츠 의 변경 사항을 감지하고  이를 복원할 계획을 생성했습니다. 로컬 공급자는 현재 위치 수정에 대한 지원이 부족 하기 때문에 계획이 파일 재생성이라는 단일 단계로 구성되어 있음을 알 수 있습니다.

이제 다시 적용 을 실행할 있으며 결과적으로 파일의 내용을 의도한 내용으로 복원합니다.

$ terraform apply -auto-approve
... messages omitted
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

$ cat hello.txt
Hello, Terraform

4. 핵심 개념

이제 기본 사항을 다루었으므로 Terraform의 핵심 개념에 대해 알아보겠습니다.

4.1. 공급자

P는 rovider는 운영 체제의 장치 드라이버로 거의 작동합니다. 공통 추상화를 사용하여 리소스 유형 집합을 노출 하므로 사용자에게 거의 투명한 리소스를 생성, 수정 및 파괴하는 방법에 대한 세부 정보를 마스킹합니다 .

Terraform 은 주어진 프로젝트의 리소스를 기반으로 필요에 따라 공개 레지스트리 에서 공급자를 자동으로 다운로드 합니다. 사용자가 수동으로 설치해야 하는 사용자 정의 플러그인을 사용할 수도 있습니다. 마지막으로 일부 기본 제공 공급자는 기본 바이너리의 일부이며 항상 사용할 수 있습니다.

몇 가지 예외를 제외하고 공급자를 사용하려면 일부 매개변수로 구성해야 합니다 . 공급자마다 많이 다르지만 일반적으로 API에 도달하고 요청을 제출할 수 있도록 자격 증명을 제공해야 합니다.

꼭 필요한 것은 아니지만 Terraform 프로젝트에서 사용할 공급자를 명시적으로 선언하고 해당 버전을 알리는 것이 좋습니다. 이를 위해 모든 공급자 선언에 사용할 수 있는 버전 속성을 사용합니다 .

provider "kubernetes" {
  version = "~> 1.10"
}

여기서는 추가 매개변수를 제공하지 않으므로 Terraform은 필요한 매개변수를 다른 곳에서 찾습니다 . 이 경우 공급자의 구현은 kubectl에서 사용하는 것과 동일한 위치를 사용하여 연결 매개변수를 찾습니다 . 다른 일반적인 방법은 키-값 쌍을 포함하는 파일인 환경 변수 및 변수 파일 을 사용 하는 것입니다.

4.2. 자원

Terraform에서, R의 esource는 주어진 제공자의 맥락에서 CRUD 조작의 대상이 될 수있는 일이다. 몇 가지 예는 EC2 인스턴스, Azure MariaDB 또는 DNS 항목입니다.

간단한 리소스 정의를 살펴보겠습니다.

resource "aws_instance" "web" {
  ami = "some-ami-id"
  instance_type = "t2.micro"
}

첫째, 정의를 시작하는 리소스 키워드 가 항상 있습니다  . 다음으로, 우리는 자원 유형이 , 일반적으로 다음과  provider_type의 규칙을. 위의 예에서 aws_instance 는 EC2 인스턴스를 정의하는 데 사용되는 AWS 공급자가 정의한 리소스 유형입니다. 그 다음에 는 동일한 모듈에서 이 리소스 유형에 대해 고유해야 하는 사용자 정의 리소스 이름 이 있습니다 . 자세한 내용은 나중에 모듈에서 설명합니다.

마지막으로 리소스 사양으로 사용되는 일련의 인수를 포함하는 블록이 있습니다. 리소스에 대한 요점은 일단 생성되면 표현식을 사용하여 해당 속성을 쿼리할 수 있다는 것입니다. 또한 마찬가지로 중요한 것은 이러한 속성을 다른 리소스에 대한 인수로 사용할 수 있다는 것 입니다.

작동 방식을 설명하기 위해 기본이 아닌 VPC(Virtual Private Cloud)에서 EC2 인스턴스를 생성하여 이전 예제를 확장해 보겠습니다.

resource "aws_instance" "web" {
  ami = "some-ami-id"
  instance_type = "t2.micro"
  subnet_id = aws_subnet.frontend.id
}
resource "aws_subnet" "frontend" {
  vpc_id = aws_vpc.apps.id
  cidr_block = "10.0.1.0/24"
}
resource "aws_vpc" "apps" {
  cidr_block = "10.0.0.0/16"
}

여기에서는 VPC 리소스 id 속성을 프런트엔드의 vpc_id 인수 값으로 사용합니다 . 다음으로  id 매개변수는 EC2 인스턴스에 대한 인수가 됩니다. 이 특정 구문을 사용하려면 Terraform 버전 0.12 이상이 필요합니다 . 이전 버전 은 여전히 ​​사용 가능하지만 레거시로 간주 되는 더 복잡한 "${expression}" 구문을 사용했습니다.

이 예제는 또한 Terraform의 장점 중 하나를 보여줍니다. 프로젝트에서 리소스를 선언하는 순서에 관계없이 구문 분석할 때 빌드하는 의존성 그래프를 기반으로 리소스를 생성하거나 업데이트해야 하는 올바른 순서를 파악합니다.

4.3. countfor_each 메타 인수

계수의 for_each 메타 인수는 우리가 모든 리소스의 여러 인스턴스를 만들 수 있습니다. 이들 사이의 주요 차이점은 count 는 음수가 아닌 숫자를 예상하는 반면 for_each  는 값 List 또는 맵을 허용한다는 것입니다.

예를 들어 count 를 사용하여 AWS에서 일부 EC2 인스턴스를 생성해 보겠습니다  .

resource "aws_instance" "server" {
  count = var.server_count 
  ami = "ami-xxxxxxx"
  instance_type = "t2.micro"
  tags = {
    Name = "WebServer - ${count.index}"
  }
}

count 를 사용하는 리소스 내 에서 표현식 에서 count 개체를 사용할 수 있습니다  . 이 개체는 하나의 속성을 가진다 :  인덱스 의 인덱스를 가지고, 각각의 인스턴스 (제로로부터).

마찬가지로 for_each 메타 인수를 사용하여 맵을 기반으로 인스턴스를 생성 할 수 있습니다  .

variable "instances" {
  type = map(string)
}
resource "aws_instance" "server" {
  for_each = var.instances 
  ami = each.value
  instance_type = "t2.micro"
  tags = {
    Name = each.key
  }
}

이번에는 레이블에서 AMI(Amazon Machine Image) 이름까지의 맵을 사용하여 서버를 생성했습니다. 리소스 내에서 개체를 사용하여 특정 인스턴스 의 현재 값 액세스할 수 있습니다.

count for_each 에 대한 요점은  표현식을 할당할 수 있지만 Terraform은 리소스 작업을 수행하기 전에 해당 값을 확인할 수 있어야 한다는 것 입니다. 결과적으로 다른 리소스의 출력 속성에 의존하는 표현식을 사용할 수 없습니다.

4.4. 데이터 소스

데이터 소스 는 기존 리소스 에 대한 정보를 얻을 수는 있지만 생성하거나 변경할 수는 없다는 점에서 "읽기 전용" 리소스와 거의 유사 합니다. 일반적으로 다른 리소스를 생성하는 데 필요한 매개변수를 가져오는 데 사용됩니다.

일반적인 예는 기존 AMI에서 속성을 복구하는 데 사용하는 AWS 공급자에서 사용할 수 있는 aws_ami 데이터 원본입니다.

data "aws_ami" "ubuntu" {
  most_recent = true
  filter {
    name   = "name"
    values = ["ubuntu/images/hvm-ssd/ubuntu-trusty-14.04-amd64-server-*"]
  }
  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }
  owners = ["099720109477"] # Canonical
}

이 예에서는 AMI 레지스트리를 쿼리하고 찾은 이미지와 관련된 여러 속성을 반환하는 "ubuntu"라는 데이터 소스 를 정의합니다 . 그런 다음 속성 이름 앞에 데이터 접두사를 추가하여 다른 리소스 정의에서 해당 속성을 사용할 수 있습니다 .

resource "aws_instance" "web" {
  ami = data.aws_ami.ubuntu.id 
  instance_type = "t2.micro"
}

4.5. 상태

Terraform 프로젝트의 상태는 주어진 프로젝트의 컨텍스트에서 생성된 리소스에 대한 모든 세부 정보를 저장하는 파일입니다 . 예를 들어 프로젝트에서 azure_resourcegroup 리소스를 선언하고 Terraform을 실행하면 상태 파일에 해당 식별자가 저장됩니다.

상태 파일의 주요 목적은 이미 존재하는 리소스에 대한 정보를 제공하는 것이므로 리소스 정의를 수정할 때 Terraform은 수행해야 할 작업을 파악할 수 있습니다.

상태 파일에 대한 중요한 점은 민감한 정보가 포함될 수 있다는 것 입니다. 예를 들어 데이터베이스를 만드는 데 사용되는 초기 암호, 개인 키 등이 있습니다.

Terraform은 백엔드 개념을 사용하여  상태 파일을 저장하고 검색합니다. 기본 백엔드는  프로젝트의 루트 폴더에 있는 파일을 저장 위치로 사용하는 로컬 백엔드입니다. 프로젝트의 .tf 파일 중 하나 에 있는 terraform 블록에 선언 하여 대체 원격 백엔드를 구성할 수도 있습니다.

terraform {
  backend "s3" {
    bucket = "some-bucket"
    key = "some-storage-key"
    region = "us-east-1"
  }
}

4.6. 모듈

Terraform 모듈은 여러 프로젝트에서 리소스 정의를 재사용하거나 단일 프로젝트에서 더 나은 조직을 가질 수 있게 해주는 주요 기능입니다 . 이것은 표준 프로그래밍에서 하는 것과 매우 유사합니다. 모든 코드를 포함하는 단일 파일 대신 여러 파일과 패키지에 걸쳐 코드를 구성합니다.

모듈은 하나 이상의 리소스 정의 파일을 포함하는 디렉토리입니다. 사실, 모든 코드를 단일 파일/디렉토리에 넣었을 때에도 여전히 모듈을 사용하고 있습니다. 이 경우에는 하나만 사용합니다. 중요한 점은 하위 디렉토리가 모듈의 일부로 포함되지 않는다는 것입니다. 대신 상위 모듈은 모듈 선언을 사용하여 명시적으로 포함해야 합니다 .

module "networking" {
  source = "./networking"
  create_public_ip = true
}

여기서 우리는 "네트워킹" 하위 디렉토리에 있는 모듈을 참조하고 여기에 단일 매개변수( 이 경우 부울 값) 를 전달합니다 .

현재 버전에서 Terraform은 countfor_each 를 사용하여 모듈의 여러 인스턴스를 생성하는 것을 허용하지 않는다는 점에 유의하는 것이 중요 합니다.

4.7. 입력 변수

최상위 또는 기본 모듈을 포함한 모든 모듈은 변수  블록 정의를 사용하여 여러 입력 변수를 정의할 수 있습니다 .

variable "myvar" {
  type = string
  default = "Some Value"
  description = "MyVar description"
}

변수는이 유형 수 있습니다 문자열 , Map , 또는 세트 다른 사람의 사이를,. 또한  기본값과 설명이 있을 수 있습니다. 최상위 모듈에 정의된 변수의 경우 Terraform은 여러 소스를 사용하여 변수에 실제 값을 할당합니다.

  • -var 명령줄 옵션
  • .tfvar 파일, 명령줄 옵션을 사용하거나 잘 알려진 파일/위치 검색
  • TF_VAR_로 시작하는 환경 변수
  • 변수의 기본값(있는 경우)

중첩 또는 외부 모듈에 정의된 변수의 경우 기본값이 없는 모든 변수는 모듈 참조의 인수를 사용하여 제공해야 합니다  . 입력 변수에 대한 값이 필요한 모듈을 사용하려고 하지만 제공하지 않으면 Terraform에서 오류를 생성합니다.

일단 정의되면 var 접두사를 사용하여 표현식에서 변수를 사용할 수 있습니다 .

resource "xxx_type" "some_name" {
  arg = var.myvar
}

4.8. 출력 값

의도적으로 모듈의 소비자는 모듈 내에서 생성된 리소스에 액세스할 수 없습니다. 그러나 때로는 다른 모듈이나 리소스에 대한 입력으로 사용하기 위해 이러한 속성 중 일부가 필요합니다. 이러한 경우를 해결하기 위해 모듈은 생성된 리소스의 하위 집합을 노출하는 출력  블록을 정의할 수 있습니다 .

output "web_addr" {
  value = aws_instance.web.private_ip
  description = "Web server's private IP address"
}

여기에서 모듈이 생성한 EC2 인스턴스의 IP 주소를 포함하는 "web_addr"이라는 이름의 출력 값을 정의합니다. 이제 모듈을 참조하는 모든 모듈은 표현식에서 이 값을 module.module_name.web_addr 로 사용할 수 있습니다 여기서  module_name 은 해당 모듈 선언 에서 사용한 이름  입니다.

4.9. 지역 변수

지역 변수는 표준 변수처럼 작동하지만 그 범위는 선언된 모듈로 제한됩니다 . 지역 변수를 사용하면 특히 모듈의 출력 값을 처리할 때 코드 반복을 줄이는 경향이 있습니다.

locals {
  vpc_id = module.network.vpc_id
}
module "network" {
  source = "./network"
}
module "service1" {
  source = "./service1"
  vpc_id = local.vpc_id
}
module "service2" {
  source = "./service2"
  vpc_id = local.vpc_id
}

여기서 로컬 변수 vpc_id네트워크 모듈 에서 출력 변수의 값을 받습니다 . 나중에 이 값을 service1service2 모듈 모두에 인수로 전달 합니다.

4.10. 작업 공간

Terraform 작업 공간을 사용하면 동일한 프로젝트에 대해 여러 상태 파일을 유지할 수 있습니다. 프로젝트에서 Terraform을 처음 실행할 때 생성된 상태 파일은 기본 작업 공간 으로 이동합니다 . 나중에 terraform workspace new 명령을 사용하여 새 작업 공간을 만들 수  있으며 선택적으로 기존 상태 파일을 매개변수로 제공할 수 있습니다.

일반 VCS에서 분기를 사용하는 것처럼 작업 공간을 거의 사용할 수 있습니다 . 예를 들어 각 대상 환경(DEV, QA, PROD)에 대해 하나의 작업 공간을 가질 수 있으며 작업 공간을 전환하여 새 리소스를 추가 할 때 변경 사항을 적용 할 수 있습니다 .

작동 방식을 감안할 때 작업 공간은 동일한 구성 집합의 여러 버전(원하는 경우 "육화")을 관리하는 탁월한 선택입니다. 이것은 악명 높은 "내 환경에서 작동" 문제를 처리해야 했던 모든 사람에게 좋은 소식입니다. 모든 환경이 동일하게 보이도록 보장할 수 있기 때문입니다.

일부 시나리오에서는 대상으로 하는 특정 작업 영역을 기반으로 일부 리소스 생성을 비활성화하는 것이 편리할 수 있습니다. 이러한 경우 terraform.workspace 사전 정의 변수를 사용할 수 있습니다  . 이 변수에는 현재 작업 공간의 이름이 포함되어 있으며 표현식에서 다른 변수로 사용할 수 있습니다.

5. 결론

Terraform은 프로젝트에서 코드로서의 인프라(Infrastructure-as-Code) 사례를 채택하는 데 도움이 되는 매우 강력한 도구입니다. 그러나 이 힘에는 도전이 따릅니다. 이 기사에서는 이 도구의 기능과 기본 개념을 더 잘 이해할 수 있도록 이 도구에 대한 간략한 개요를 제공했습니다.

평소와 같이 모든 코드는 GitHub에서 사용할 수 있습니다 .

Cloud footer banner