Terraform 101

Terraform

Terraform

Terraform은 Infrastructure as code를 모토로 하는 Hashicorp의 오픈소스 도구입니다.

인프라 구성을 코드를 통해 효과적이고 안전하게 만들고, 변경하고, 버저닝할 수 있습니다.

SVN, Git과 같은 버전 제어 시스템과 함께 사용한다면 다른 사람과 함께 구성을 변경할 수 있으며,
변경 이력을 투명하게 살펴볼 수 있고, 코드로 작성되므로 자동화가 가능합니다.

코드로 표현된 인프라

다음은 Terraform 공식 홈페이지에서 소개하고 있는 한 예제입니다.
예제를 살펴보면 코드로 인프라를 표현한다는 것이 무엇을 의미하는지 바로 알 수 있습니다.

위 Terraform 코드는 인프라 내에 두개의 리소스를 정의합니다.

하나는 DigitalOcean의 Droplet을 정의하는 리소스이고,
다른 하나는 DNSimple의 레코드를 정의하는 리소스입니다.

여기서 DNSimple의 레코드를 정의한 리소스를 보면 흥미로운 부분이 있는데,
위 DigitalOcean Droplet의 Public IP를 참조하고 있다는 것입니다.

즉, 다른 리소스의 정보를 참조하여 정말 유연하게 인프라를 구성할 수 있죠!

비슷한 소프트웨어와 무엇이 다른가?

AWS의 CloudFormation, Heat은 코드로 인프라를 구성한다는 점에서 Terraform과 비슷하지만,
다음과 같은 차이가 있습니다.

  1. Terraform은 다양한 Vendor를 지원하므로, 유연한 인프라 구성이 가능하다.
    • e.g.) DNS 서비스는 DNSimple을 사용하고, CDN은 Akamai로 구성
  2. 오픈소스 생태계
    • 공통된 Terraform 설정은 모듈로 패키징되어 재사용할 수 있다.
  3. 적용 전 변경사항 미리보기 가능 (Plan)
    • Terraform은 계획과 적용 단계가 분리되어 있어 변경사항 적용 전 무엇이 변경되는지 검토가 가능
  4. 리소스간 의존성을 그래프로 확인할 수 있음 (Graph)
    • dot-formatted 이므로 Graphviz로 의존성 그래프를 시각화 할수도 있음

Terraform은 실행 계획이 캡쳐되면, 실행 단계는 캡쳐된 실행 계획에서 필요로 하는 부분만 반영하도록 제한합니다.

다른 도구들은 계획과 실행 단계가 분리되어 있지 않고 합쳐져있어 오퍼레이터들이 인프라 구성 변경으로 무엇이 변경되는지 추론하도록 강요받지만,
Terraform은 변경사항 적용 이전에 무슨 일이 벌어지는지 정확하게 알 수 있게 제공함으로써, 오퍼레이터들이 변경사항을 적용하는 것을 신뢰할 수 있게 합니다.

Terraform 설치

Terraform은 단일 실행파일로 배포되고 있으며, 이 덕분에 설치가 아주 간단합니다.

공식 홈페이지의 Downloads 페이지에서 Terraform을 얻을 수 있습니다.

이 글을 작성하는 시점에서는 0.8.1 버전이 최신 버전이고, macOS 기준 Terraform을 설치하는 방법은 다음과 같습니다:

$ # echo $PATH 를 통해 실행 경로를 미리 확인하세요.
$ # 저는 /usr/local/bin 을 즐겨쓰기에 해당 경로를 기준으로 했습니다.
$ wget https://releases.hashicorp.com/terraform/0.8.1/terraform_0.8.1_darwin_amd64.zip
$ unzip terraform_0.8.1_darwin_amd64.zip
$ cp terraform /usr/local/bin/ # root 권한이 필요한 경우 sudo를 사용해야 할 수 있습니다.

참 쉽죠?!

설치를 확인하려면 터미널에서 terraform을 그냥 실행해보면 됩니다.

$ terraform

Hello, Terraform!

Terraform으로 인프라 만들기

Terraform을 설치했으니, Terraform으로 새로운 인프라를 구성해보겠습니다.
이 섹션에서는 몇가지 AWS 서비스들을 Terraform으로 구성하는 방법을 소개하므로,
미리 IAM 에서 Access Key를 발급받아 두시길 바랍니다!

첫 Terraform 설정 파일 만들기

우리는 이번 섹션에서 새로운 EC2 인스턴스를 만들기 위해 새로운 Terraform 설정 파일을 만들어보겠습니다.

Terraform 설정 파일은 Hashicorp Configuration Language (HCL) 이라는 문법을 사용하고, .tf 확장자를 사용합니다.
HCL은 JSON과 완벽히 호환되고, Terraform 설정 파일은 JSON으로도 작성하여 사용할 수 있습니다 (이 경우에는 .tf.json 을 사용합니다.)

Terraform 설정 파일에 대한 자세한 내용은 이곳을 참고하시고,
일단 새로운 Terraform 설정 파일을 만들어보겠습니다.

먼저, terraform-101 이라는 디렉토리를 새로 만든 후, 해당 디렉토리에 아래 내용을 example.tf으로 저장하세요.
(기본적으로 Terraform은 working directory의 모든 .tf 파일을 불러오기 때문에, 만일을 대비해 새로운 디렉터리를 만드는 것입니다.)

지금은 AWS의 Access Key와 Secret Key를 직접 기입했지만,
나중에는 변수에서 두 값을 추출해서 사용하는 방법을 알아볼 것이니 하드코딩된 값을 너무 신경쓰진 마세요!

위 설정은 바로 적용해 볼 수 있는 (완전한) 예제입니다.

기본적인 구조를 설명하면 다음과 같습니다:

provider block은 알려진 Provider를 구성하기 위해 사용됩니다.
위 예제에서는 EC2 리소스를 정의하기 위해 "aws"를 사용했습니다.
Provider는 리소스를 생성하고 관리하는 책임 (역할)을 가집니다.
여러 서비스를 관리하기 위해 복수의 Provider를 정의할 수도 있습니다 (e.g. Akamai + AWS + DNSimple)

resource block은 인프라 내에 존재하는 리소스를 정의하기 위해 사용됩니다.
하나의 리소스는 EC2 인스턴스나 Heroku 애플리케이션과 같은 물리적인 컴포넌트를 의미합니다.

Resource block은 block이 시작되기 전 두개의 문자열을 가지고 있습니다.
바로 리소스의 타입리소스의 이름입니다.
위 예제를 기준으로 하면 aws_instance가 리소스의 타입이고, example이 리소스의 이름이 됩니다.

Terraform에서는 리소스의 타입에 붙은 접두사(Prefix)를 통해 Provider에 매핑합니다.
aws_instance의 경우, 접두사는 aws이고, 이는 aws provider에 의해 관리되는 리소스임을 의미합니다.

Resource block 안에는 해당 리소스와 관련한 속성들이 들어갑니다.
위 예제의 경우 Ubuntu 16.04 LTS AMI를 사용하고, t2.micro 인스턴스를 정의했습니다.

실행 계획 (Execution Plan)

이제 Terraform이 위 환경을 적용할 때 무엇을 할지 살펴보기로 합니다.

example.tf 가 있는 디렉토리에서 terraform plan 명령을 실행해보세요.

아래와 비슷한 내용이 출력될 것입니다:

terraform plan

terraform plan 명령은 현재 환경에서 Terraform이 어떠한 변경사항을 만들어내는지 보여줍니다.
출력 내용은 Git과 비슷한 형식으로 어떤 항목들이 변경되는지 알려줍니다.

위의 경우 aws_instance.example 리소스가 새롭게 생성될 것이고,
총 1개의 생성 / 0개의 수정 / 0개의 삭제가 발생한다는 것을 알 수 있죠.

<computed> 라고 표기된 값은 리소스가 생성되기 전까지는 알 수 없습니다.
당연하겠지만 (?) EC2 인스턴스를 생성하기 전에 미리 Private IP를 알아낼 수는 없으니까요.
그렇지만, Terraform에서는 인프라를 구성할 때 다른 리소스에서 <computed>로 표기된 해당 속성의 값을 참조할 수 있습니다. (이따 자세히 알아보겠습니다!)

적용 (Apply)

terraform plan 명령으로 확인한 변경사항이 적절하다고 판단되면, 이제 실제로 리소스를 만들 시간입니다. (위 예제에서 EC2 인스턴스를 만드니까요)

적용은 terraform apply 명령을 사용하면 됩니다.

terraform apply

와우! 인스턴스를 새롭게 만들고, 해당 인스턴스가 준비될 때 까지 기다리는걸 확인할 수 있습니다!

EC2 대시보드에서 확인해보니, 정말 잘 만들어졌네요.

생성된 인스턴스를 EC2 Dashboard에서도 확인할 수 있습니다.

또한 기본적으로 Terraform은 몇몇 상태 정보를 terraform.tfstate 파일에 저장합니다.

이 상태 파일은 아주 중요한데, Terraform이 무엇을 관리하고 있는지 알 수 있도록 여러 리소스의 메타데이터를 실제 리소스 ID로 매핑한 정보를 담고 있기 때문입니다.
그리고 이 상태 파일은 Terraform을 사용하려는 유저가 있다면 (e.g. 다른 팀원), 반드시 환경설정 파일과 함께 저장되어야하고 함께 배포되어야만 합니다.
다만 이 상태 파일에는 잠재적인 비밀 정보를 포함할 수 있으므로, Terraform에서는 원격에서 상태를 저장할 수 있도록 설정을 구성하는 것을 권장하고 있습니다.

원격 상태 관리에 대해서는 다음 링크에서 자세히 살펴보시기 바랍니다:
https://www.terraform.io/docs/state/remote/index.html

현재 인프라 구성 상태를 확인하고 싶은 경우, terraform show 명령을 사용하면 됩니다.

terraform show

아까 Plan / Apply 명령을 내렸을 때와는 다르게, 생성한 리소스에 대해 자세한 정보를 얻을 수 있는 것을 확인하실 수 있습니다.

Terraform 으로 인프라 변경하기

우리는 방금 전 Terraform으로 하나의 EC2 인스턴스를 생성하는 첫 인프라를 만들었습니다.
이번에는 Terraform으로 위 인스턴스를 변경해보도록 하겠습니다.
Terraform이 어떻게 변경사항을 다루는지 보세요!

Terraform으로 인프라를 변경할 때, Terraform은 변경될 상태에 필요한 부분만 변경하는 실행 계획을 만듭니다.

설정 변경

방금 만든 EC2 인스턴스는 Ubuntu 16.04 LTS AMI를 사용하고 있는데,
이를 Amazon Linux AMI로 변경해보도록 하겠습니다.

설정을 변경하는 방법은 아주 간단합니다.
우리는 AMI 이미지를 교체하려고 하므로, resource 블럭 내의 ami 속성의 값을 ami-983ce8f6 으로 변경하기만 하면 됩니다.

요렇게요.

변경사항을 반영하는것은 이전 단계에서 배웠습니다.
terraform plan 으로 실행 계획을 체크하고, 문제가 없다면 terraform apply 으로 반영하면 됩니다.

설정 변경 후의 실행 계획 (Execution Plan)

설정을 변경하고 나서 terraform plan 명령을 실행하면 다음과 같은 화면이 나올 것입니다:

설정 변경 후 terraform plan

변경사항 앞에 붙은 -/+ 접두사는 Terraform이 해당 리소스를 제거하고 재생성한다는 의미입니다.
반대로, 변경하는 일부 속성이 리소스의 제거 없이 즉시 업데이트가 가능한 경우 Terraform은 ~ 접두사로 변경사항을 표시하게 됩니다.
(~ 접두사가 붙은 것은 차차 확인하기로 하고, 일단 넘어갑시다!)

EC2 인스턴스의 AMI를 변경하는 것은 새 인스턴스를 생성하는 것을 요구하기 때문에, 기존 리소스를 제거하고 새롭게 생성해야하죠.

Terraform은 우리를 위해 이러한 디테일도 잘 다루어줍니다. (세심해라…)

그래서 실행 계획 (Execution Plan) 에서 Terraform이 무엇을 할지 명확하게 알 수 있죠.

덧붙여서, 실행 계획의 결과를 보면 AMI에 대한 변경사항이 리소스를 삭제하고 재생성하도록 요구했다는 것을 알아챌 수 있습니다.
이 정보를 적절히 사용한다면, 특정 시점에 리소스를 제거하거나 생성하는 업데이트를 피하기 위해 변경사항을 조절할 수도 있습니다.
(e.g. 일단 리소스 재생성 없이 즉시 적용할 수 있는 변경사항부터 적용하고, 리소스 재생성을 요구하는 변경사항은 나중에 적용하기)

설정 변경 후 적용 (Apply)

이전 실행 계획 섹션에서 우리는 무엇이 변경될 지 알아냈습니다. 그럼 이번엔 적용해보죠.

이전과 동일하게 terraform apply 를 통해 변경사항을 적용하면 됩니다.

설정 변경 후 terraform apply

실행 계획에서 예측한 대로, 기존 인스턴스를 제거하고, 새 인스턴스를 생성하는 것을 확인할 수 있습니다.

설정 변경 후 EC2 Dashboard

EC2 대시보드에서 확인해보면, 인스턴스를 제거하고 새롭게 생성한 것을 확인할 수 있습니다.
Amazon Linux AMI로 바뀐 것이 보입니다!

terraform show를 통해 새로운 인스턴스의 속성들을 확인할 수도 있습니다.

여러분들은 Terraform과 함께 인프라를 변경하는 것이 얼마나 쉬운지 보았습니다.
다음 섹션에서는 우리가 만든 인프라를 통째로 제거해 보겠습니다.

Terraform으로 인프라 제거하기

우리는 Terraform으로 인프라를 어떻게 생성하고 변경하는지 알아봤습니다.
이번 섹션에서는 여러개의 리소스들을 생성하고 의존성을 표시하는지 알아보기 전에,
Terraform으로 관리하는 인프라를 완전히 제거하는 방법을 알아보도록 하겠습니다.
그러니까, 인프라 내에 포함된 모든 리소스들을 제거하는 방법이겠죠.

여러분들의 인프라를 제거한다는 것은 프로덕션 환경에서 아주 드문 일입니다만,
여러분들이 만약 Terraform을 개발, 테스트, QA 환경과 같은 (자주 바뀌는) 인프라를 구성하기 위해 사용한다면,
(모든 리소스를 삭제하기 위해) 인프라를 제거하는 기능을 유용하게 사용할 수도 있을 것입니다.

인프라 제거시 실행 계획 (Execution Plan)

우리의 인프라를 제거하기 전에, 우리는 terraform plan -destroy 명령을 통해 어떤 리소스들이 제거되는지 미리 확인할 수 있습니다.
-destroy 플래그를 사용하는 경우, 우리는 Terraform에게 인프라를 제거하는 실행 계획을 묻게 됩니다.
인프라를 제거한다는 것은, Terraform이 관리하는 모든 리소스를 제거한다는 것을 의미합니다.

여러분들은 이 명령의 결과로 Terraform이 어떤 어떤 리소스들을 관리하고 있고 제거하는지 검증할 수 있습니다.

terraform plan -destroy

현재는 구성해둔 리소스가 AWS의 EC2 인스턴스 하나이므로 해당 인스턴스 하나만 표시가 됩니다.

제거

변경사항 적용과 달리 인프라를 제거하는 작업은 apply 명령이 아닌 destroy 명령을 사용합니다.
terraform destroy 명령을 통해 한번 인프라를 제거해보도록 하겠습니다:

terraform destroy confirmation

당연하겠지만, 정말 인프라를 제거할 것인지 확인하는 절차가 있습니다.
여기서 Ctrl + C로 인터럽트를 하거나, yes 이외의 응답을 하면 인프라 제거를 취소할 수 있습니다.
Terraform은 안전을 위한 매커니즘으로 오직 yes 만 올바른 응답으로 취급합니다.

지금은 연습이기 때문에 마음 편하게 yes를 입력할 수 있지만,
인프라를 제거하는 작업은 취소할 수 없으므로 항상 주의해야합니다.

yes 를 입력하고 Enter 키를 눌러 응답하면 실제로 인프라가 제거되는 것을 확인할 수 있습니다.

terraform destroy

덧붙여서, terraform apply 명령처럼, Terraform은 어떤 순서로 항목을 제거할지 결정할정도로 똑똑합니다.
이번 예제에서는 오직 한 리소스만 존재하기 때문에 순서를 지정해 리소스를 제거할 필요는 없지만,
여러 리소스가 존재하는 복잡한 케이스의 경우라면 Terraform은 올바른 순서로 리소스를 제거 할 것입니다.

여러분들은 로컬 머신에서 Terraform을 통해 인프라를 만들고, 수정하고, 제거하는 방법을 배웠습니다.

Terraform의 강력함은 간편한 관리도 있지만, 리소스간 의존성을 둘 수 있는 점 또한 큰 장점입니다.

다음 섹션에서는 Terraform에서 리소스 간 의존성을 정의하는 방법을 알아보도록 하겠습니다.

리소스 의존성 (Resource Dependency)

이번 섹션에서 우리는 리소스 의존성을 알아볼 것입니다.
여러분들은 여러복수의 리소스를 정의하는것을 보실 수 있을 뿐만 아니라,
다른 리소스의 정보를 참조하도록 리소스 파라미터를 사용하는 것을 확인할 수 있습니다.

지금까지는 우리는 오직 한 리소스만 포함하는 인프라를 구성했었습니다.
하지만 실제 인프라는 다양한 리소스와 여러 리소스 종류를 가지고 있습니다.
Terraform 설정은 여러 리소스들과 리소스 종류를 포함할 수 있고, 이러한 리소스 종류들은 여러 Provider를 통해 더 늘릴 수 있습니다.

이번 섹션에서는 우리는 여러 리소스를 다루는 예제와 함께,
어떻게 리소스가 특정 리소스의 속성을 참조해 해당 리소스를 구성할 수 있는지 알아보겠습니다.

Elastic IP 할당하기

기존에 관리하던 EC2 인스턴스에 Elastic IP를 할당해 기존 설정을 개선해보도록 하겠습니다.
여러분들의 기존 example.tf 환경설정 파일에 다음 항목을 추가하세요:

1
2
3
resource "aws_eip" "ip" {
instance = "${aws_instance.example.id}"
}

변경 후의 코드는 다음과 같을 것입니다:

위 스니펫에서 정의한 리소스는 aws_eip 리소스 타입을 정의하는 것만 제외하면 이전에 EC2 인스턴스 리소스를 추가할 때와 많이 비슷할 것입니다.

aws_eip 리소스 종류는 Elastic IP를 할당하고, 해당 Elastic IP를 EC2 인스턴스에 연동합니다.
aws_eip 리소스 종류는 정의된 단 하나의 파라미터인 instance 속성이 있는데, 해당 속성에 지정된 인스턴스에 해당 Elastic IP를 연동하게 됩니다.
이번에 해당 instance 속성에 설정하는 값은 일종의 템플릿 표기법(interpolation)을 이용하여,
우리가 이전에 관리하던 EC2 인스턴스의 속성 값을 참조할 수 있도록 합니다.

템플릿 표기법(interpolation)은 간단합니다.
위 예제에서는 aws_instance.example 리소스의 id 속성을 참조하라는 의미입니다.

계획과 실행 (Plan & Execute)

실행 플랜을 보기 위해 terraform plan 명령을 실행해보세요.
아마 다음과 같은 출력이 나올 것입니다:

terraform plan (with resource dependency)

테라폼은 EC2 인스턴스 하나와 Elastic IP 하나, 총 두개의 리소스를 만들 것입니다.
여기서 aws_eipinstance 속성 값이 아직 템플릿 표기법(interpolation) 으로 표시되는데,
이는 해당 변수 (variable)는 aws_instance 가 생성될 때 까지는 알 수 없기 때문입니다.

이 이유로, 해당 값은 적용 시점 (apply-time)에서 되기 떄문에 terraform apply 명령의 결과로만 확인할 수 있습니다.

그렇다면, 얼른 확인해보도록 하죠.

terraform apply 명령을 실행해 현재 구성을 적용합니다.

이번에는 다음과 같은 결과가 나올 것입니다:

terraform apply (with resource dependency)

여기서 Terraform이 실제로 동작하는 것을 볼 수 있는데,
Terraform이 Elastic IP를 할당받기 이전에 (Elastic IP 리소스를 생성하기 이전에) EC2 인스턴스를 먼저 생성하는것을 볼 수 있습니다.

이전에 Elastic IP의 instance 속성의 값을 EC2 인스턴스의 ID를 참조하는 템플릿 표기법(interpolation)으로 기입했기 때문에,
Terraform은 의존성을 추론할 수 있고, 인스턴스를 먼저 만들어야 함을 알 수 있습니다.

암시적인 의존성과 명시적인 의존성 (Implicit and Explicit Dependencies)

Terraform에서 대부분의 의존성들은 암시적입니다. Terraform은 리소스간의 속성 참조를 기반으로 의존성을 추론하기 때문입니다.

Terraform은 의존성 정보를 통해 리소스들의 관계를 그래프로 그리고,
어떤 순서로 리소스들을 생성할 지 결정할 뿐만 아니라, 어떤 리소스들이 병렬로 동시에 생성될 수 있을지 추론합니다.
위의 예제에서는, Elastic IP가 EC2 인스턴스에 의존적이므로 그 두개의 리소스는 병렬로 (동시에) 생성될 수 없었습니다.

암시적인 의존성은 잘 동작하고 대개 여러분들이 원하는 것이긴 하지만,
여러분들은 모든 리소스에서 사용 가능한 depends_on 속성을 통해 명시적인 의존성을 정의할 수 있습니다.

이번에는 이전 예제와 동일하게 동작하고 불필요하긴 속성이긴 하지만
aws_eip 리소스에 depends_on 속성을 정의해 명시적인 의존성을 정의해보도록 하겠습니다.

아래와 같이 depends_on 속성을 기입하세요:

1
2
3
4
resource "aws_eip" "ip" {
instance = "${aws_instance.example.id}"
depends_on = ["aws_instance.example"]
}

변경 후의 코드는 다음과 같을 것입니다:

만약 여러분이 Terraform이 생성하는 의존성 구조 (dependency chain)에 대해 확신하지 못했다면,
이번 기회에 terraform graph 명령을 사용해 그래프를 확인해보세요.
terraform graph 명령의 출력 결과는 Graphviz로도 확인할 수 있는 dot 형식 (dot-formatted) 입니다.

terraform graph 명령을 실행하면 다음과 같은 결과가 나올 것입니다:

terraform graph

위 이미지에서 하이라이트한 라인을 보면, aws_eip.ip 리소스가 aws_instance.example 리소스에 의존성을 가지고 있음을 확인할 수 있습니다.

Graphviz를 통해 시각화 한 그래프를 확인해보면 의존성을 더욱 확실히 알 수 있습니다:

Graphviz로 시각화 한 terraform graph

terraform graph 명령에 대해 더 자세히 알기 원하신다면,
Terraform 공식 문서를 참고해보세요.

의존성이 없는 리소스 (Non-Dependent Resources)

우리는 이제 다른 EC2 인스턴스를 구성해 인프라를 늘릴 수도 있습니다.

다른 리소스에 대해 의존성이 없다면, 해당 리소스는 병렬로 (동시에) 생성될 수 있음을 뜻하기도 합니다.

이번에는 Ubuntu 16.04 AMI를 사용하는 another 라는 이름을 가진 새로운 EC2 인스턴스 리소스를 정의해보겠습니다.

기존 환경설정 파일에 다음 리소스를 추가하세요:

1
2
3
4
resource "aws_instance" "another" {
ami = "ami-f293459c"
instance_type = "t2.micro"
}

변경 후의 코드는 다음과 같을 것입니다:

terraform graph 명령을 실행해보면 다음과 같이 aws_intance.another 리소스에는 아무런 의존성이 없고,
이는 병렬로 (동시에) 생성될 수 있을 것 같이 보입니다.

terraform graph (with non-dependent resource)

이번 그래프 역시 Graphviz로 시각화 해보면 다음과 같을 것입니다:

Graphviz로 시각화 한 terraform graph

더욱 눈에 잘 들어오죠?

자, 이번에는 terraform destroy 명령으로 인프라를 제거합시다. (네, 모든 리소스를 삭제할겁니다!)
왜냐하면 인프라를 제거하고 terraform apply 명령을 통해 새롭게 인프라를 생성할때, 병렬로 EC2 인스턴스가 생성되는 것을 보려고 하거든요!

아까 배운대로 terraform plan -destroy 명령으로 인프라 제거시 발생하는 변경사항을 검토하고,
terraform destroy 명령을 통해 인프라를 제거합니다:

인프라를 제거하고!

그 다음 terraform plan 명령으로 새롭게 생성할 인프라를 검토해보고,

새롭게 만드는 인프라 검토하고!

마지막으로 terraform apply 명령을 통해 병렬로 (동시에) EC2 리소스가 생성되는지 확인해봅니다!

동시에 EC2 인스턴스 생성

우와!!! terraform apply 명령을 실행하는 동안, Terraform이 정말 병렬로 EC2 인스턴스를 생성하는것을 확인할 수 있습니다!

어때요? 멋지죠!?

다음 섹션으로 넘어가기 전에…

다음 섹션으로 넘어가기 전에 Terraform 환경설정 파일에서 방금 만들었던 another EC2 인스턴스 리소스를 제거하고 terraform destroy 명령을 다시 실행해 인프라를 제거하세요.
왜냐하면 이제 another 리소스를 쓸 일이 없고, 다음 장에서 소개하는 프로비저닝은 인스턴스가 생성되는 시점에만 동작하기 때문입니다.

여기까지 따라오시는 동안 인프라를 만들고 삭제하는건 많이 해보셨을 것이기 때문에, 이번에는 따로 스크린샷을 첨부하진 않습니다.
(사실 귀찮아서… ㅠㅠ)
이번에 terraform destroy 명령을 실행할 때에는 terraform apply 명령을 실행했을 때와는 반대로 Elastic IP가 먼저 삭제되는 것을 확인하실 수 있으실 겁니다 :)

이번 섹션에서는 복수 리소스를 정의하는 것과 함께 기본적인 리소스 의존성, 그리고 템플릿(interpolation)을 통한 속성 참조를 소개했습니다.
다음 섹션에서는, Provision을 사용해 우리가 실행한 인스턴스에 기본적인 bootstrapping을 수행하는 방법을 알아보도록 하겠습니다.

프로비저닝 (Provision)

여기까지 오신 여러분들은 인프라를 생성하고 수정하는건 이제 꽤 익숙해졌을겁니다.
이번에는 프로비저너(Provisioner)들을 사용하여 인스턴스들이 생성된 후 어떻게 해당 인스턴스들을 초기화 (initialize) 할 수 있는지에 대해 알아볼 것입니다.

만약 여러분들이 이미지 기반의 인프라 (아마 Packer로 생성한 이미지)를 이미 사용하고 있다면,
지금까지 알아본 내용으로도 충분할 것입니다.

하지만 인스턴스들에 초기 설정 (initial setup)을 하기 원한다면, 프로비저너(Provisioner)를 사용해 파일을 업로드하고, 쉘 스크립트를 실행하고, 설정 관리을 위한 소프트웨어 같은 것들을 설치하고 실행하도록 여러분을 도울 것입니다.

프로비저너 정의하기 (Defining a Provisioner)

프로비저너 (Provisioner)를 정의하기 위해서는,
이전에 설정한 Terraform 설정 파일에서 example EC2 인스턴스의 리소스를 다음처럼 변경하면 됩니다:

1
2
3
4
5
6
7
8
resource "aws_instance" "example" {
ami = "ami-983ce8f6"
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo ${aws_instance.example.public_ip} > ip_address.txt"
}
}

변경 후의 코드는 다음과 같을 것입니다:

여러분들은 리소스 블럭 내부에 프로비저너(Provisioner) 블럭이 새롭게 추가된 것을 확인하실 수 있을겁니다.
이전 섹션에서 여러 리소스를 정의해서 사용할 수 있었던 것 처럼, 프로비저너(Provisioner) 역시 복수개를 정의하여 여러 단계의 프로비저닝 과정을 구성할 수 있습니다.

Terraform은 여러 종류의 프로비저너(Provisioner)를 지원하지만,
일단 이번 예제에서는 local-exec 프로비저너(Provisioner)만을 사용해보도록 하겠습니다.

local-exec 프로비저너(Provisioner)는 다른 프로비저너들과는 다르게 인스턴스 접속에 필요한 정보들 (e.g. SSH Key, Username, Password)에 대해 신경쓰지 않아도 되거든요.

local-exec 프로비저너(Provisioner)는 The local-exec provisioner Teraform을 실행하고 있는 로컬 머신에서 명령을 실행하는 역할을 합니다.

위의 프로비저너 스니펫은 example EC2 인스턴스에 할당된 Elastic IP를 ip_address.txt 파일로 출력합니다.

프로비저너 실행하기 (Running Provisioners)

프로비저너들은 리소스가 생성될때만 동작합니다.
이미 실행중인 서버에 대해 구성을 관리하고 소프트웨어를 변경하기 위해 프로비저너를 사용하는 것은 올바르지 않습니다.
그냥 서버를 부트스트랩(bootstrap)하는 방법이라고 생각하세요.

서버에 대한 구성 관리 (configuration management)가 필요하다면,
진짜 구성 관리 솔루션을 실행시키기 위한 수단으로 Terraform 프로비저닝을 사용하세요!

기존에 생성한 인프라가 제거되었는지 다시 한번 확인하고, terraform apply 명령을 실행하세요:

terraform apply (with local-exec provisioner)

짠! Terraform이 EC2 인스턴스를 생성하고, local-exec 프로비저너가 동작하는 것을 확인하실 수 있습니다.

Terraform은 프로비저너들의 모든 출력을 콘솔에 표시하지만, 이번에는 redirection으로 인해 출력 결과가 보이지 않을 겁니다.
그래서 ip_address.txt 파일을 확인해 local-exec 프로비저너가 잘 동작했는지 검증해보도록 하죠.

cat ip_address.txt 명령과 terraform show | grep ip 명령을 각각 실행해보고, 결과를 비교해보세요:

local-exec 프로비저너 검증

우리가 요구한대로, 해당 Elastic IP의 Public IP가 해당 텍스트 파일에 잘 들어간 것을 확인할 수 있습니다!

실패한 프로비저너와 더럽혀진 리소스 (Failed Provisioners and Tainted Resources)

만일 리소스는 성공적으로 만들었지만 프로비저닝이 실패한다면, Teraform은 에러를 뱉고 해당 리소스를 tainted (더럽혀진 리소스)로 표기할 것입니다.

tainted 상태로 표시되는 리소스는 물리적으론 생성된 상태이지만, 프로비저닝이 실패했기 때문에 사용하기에 안전하다고 여길수 없습니다.

프로비저닝이 실패한 상태에서 (리소스가 tainted 상태일 때) 여러분들이 다음 실행 계획 (execution plan)을 생성한다면 (terraform plan 명령을 실행한다면),
Terraform은 해당 리소스가 사용하기에 안전하다고 개런티 할 수 없기 때문에 프로비저닝에 실패한 해당 리소스에 대해 프로비저닝을 다시 시도하지 않습니다.
대신 해당 tainted 상태의 리소스를 삭제하고 다시 리소스를 생성한 뒤 프로비저닝을 다시 시도합니다.

Terraform은 또한 apply 단계에서 프로비저닝 실패시 자동으로 작업한 내용을 롤백(rollback)하고 해당 리소스를 제거하지 않습니다.
그냥 tainted 상태로 마크만 해 둘것입니다.
왜냐하면, 실행 계획 (Execution Plan)에서 생성한 계획은 리소스를 ‘생성’ 한다는 것이었지, ‘삭제’ 하는건 아니었기 때문입니다.
만약 롤백을 위해 리소스를 ‘삭제’하게 된다면, 이는 실행 계획의 존재 목적에 반하게 되는 것이기도 합니다.

대신 만약 여러분이 tainted 상태의 리소스가 존재하는 상태에서 새로운 실행 계획을 만든다면 (terraform plan 명령을 실행한다면),
새롭게 만들어진 실행 계획에서는 깨끗한 상태를 위해서 tainted 상태의 해당 리소스를 삭제하도록 할 것입니다.

다음

프로비저닝은 인스턴스를 부트스트랩(bootstrap)이 가능하게 하기 때문에 중요합니다.

다시 한번 말씀드리지만, Terraform에서의 프로비저닝은 구성 관리(configuration management)를 대체하는 위한 것이 아닙니다.
그냥 머신을 처음 쓰기 위해 부트스트랩(bootstrap)하는거에요.

여러분들이 구성 관리(configuration management)를 사용한다면,
Terraform에서의 프로비저닝은 구성 관리 도구(configuration management tool)를 부트스트랩(bootstrap) 하기 위한 수단으로 사용하셔야 합니다.

다음 섹션에서 우리는 설정을 파라미터화(parameterize) 하기 위해 변수(variable)를 알아볼 것입니다.

입력 변수 (Input Variables)

여러분들은 유용한 설정들을 만들기 위한 Terraform 지식을 충분히 얻었습니다.
하지만 우리는 아직도 액세스 키 (Access Key), AMI와 같은 정보를 설정파일에 직접 기입(hard-coding)해야 합니다.

Terraform 설정을 공유할 수 있고, 버전관리 할 수 있게하려면, 우리는 설정을 ‘파라미터화’ (parameterize) 해야합니다.
이번 섹션에서는 이를 가능하게하는 입력 변수에 대해 소개할 것입니다.

변수 정의하기 (Defining Variables)

첫번째로 우리가 만들었던 설정 파일에서 AWS 액세스 키 (access key), 비밀 키 (secret key), 그리고 리전 정보를 변수들로 뽑아내 봅시다.

example.tf 설정 파일을 만들었던 디렉토리에서 아래 내용을 variables.tf 파일로 저장하세요.

참고: Terraform은 작업 디렉토리 (Working directory) 내의 모든 .tf 파일을 불러오기 때문에, 파일명은 원하는 이름을 써도 됩니다. 여기선 그냥 편의를 위해서 variables.tf를 쓸 뿐입니다.

위 스니펫은 Terraform 환경설정 내에 세개의 변수를 정의합니다.
처음 두 변수(access_key, secret_key)들은 빈 블럭({})을 가지고 있고,
세번째 변수는 기본값(default = "ap-northeast-2")을 설정했습니다.

만약 기본값이 설정되어 있으면, 해당 변수는 선택항목(optional) 입니다.
반대로 기본값이 설정되어 있지 않으면, 해당 변수는 필수항목(required)가 되겠죠.

이 상태에서 terraform plan 명령을 실행하면, 아래 화면처럼 Terraform이 할당되지 않은 변수에 입력할 값을 물어볼 것입니다.

terraform plan 명령 실행시 할당되지 않은 값에 대한 질문

환경설정에서 변수 사용하기 (Using Variables in Configuration)

이번엔 AWS Provider 설정을 다음과 같이 변경하세요:

1
2
3
4
5
provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}

변경 후의 example.tf 코드는 다음과 같을 것입니다:

위 스니펫은 더 많은 템플릿 표기법(interpolations)을 사용합니다.
이전에 리소스를 참조했던 것과는 다르게, 이번에는 var. 접두사를 사용합니다.
이는 Terraform에게 변수에 접근하려는 것임을 알려주게 됩니다.
즉, 위 스니펫은 지정한 변수를 사용해 AWS Provider를 구성할 수 있도록 합니다.

변수에 값 할당하기 (Assigning Variables)

변수에 값을 할당하는 방법은 여러가지 방법이 있습니다.
아래에서 소개하는 각 방법들은 어떤 순서로 변수의 값을 선택되는지에 대한 순서이기도 합니다.

즉, 아래는 내림차순으로 된 변수를 선택할 때 고려되는 우선순위입니다 (아래로 갈수록 우선순위 낮음)

명령줄 플래그 (Command-line flags)

여러분은 명령줄(command-line)에서 -var 플래그를 통해 변수의 값을 직접 할당하실 수 있습니다.
apply, plan, refresh와 같이 환경설정을 읽는 Terraform의 명령이라면 -var 플래그를 사용할 수 있습니다.

아래는 명령줄 플래그로 변수의 값을 할당하는 예제입니다:

1
2
3
$ terraform plan \
-var 'access_key=foo' \
-var 'secret_key=bar'

다만 이 방법으로 변수에 값을 할당한다면 해당 값들은 저장되지 않기 때문에, Terraform의 명령들을 실행할때마다 반복해서 입력해야만 합니다.

파일에서 읽기 (From a file)

변수의 값들을 쭉 보존하고 싶다면, 파일을 하나 만들어서 안에 값들을 집어넣는 방법을 써도 됩니다.
아래처럼 terraform.tfvars 이름을 가진 파일을 만들기만 하면 됩니다:

만약 현재 작업 디렉토리에 terraform.tfvars 파일이 존재한다면,
Terraform은 자동으로 해당 파일을 읽어 변수에 값을 할당합니다.

만약 변수 값을 가진 파일이 terraform.tfvars가 아니라면,
여러분은 -var-file 플래그를 사용해 직접 읽어올 파일을 지정할 수 있습니다.

또한 이 파일은 이전에 Terraform 설정 파일 (.tf)과 동일하게 HCL(Hashicorp Configuration Language)를 사용하고,
Terraform 설정 파일처럼 JSON으로도 작성해서 사용하실 수 있습니다.

Terraform은 사용자 아이디 (username)과 비밀번호를 버전 관리 시스템에 저장하는것을 권장하지 않습니다.
하지만, 여러분들은 로컬에 비밀 변수 파일(secret variables file)을 만들고 -var-file 플래그를 통해 사용할 수 있습니다.
그리고 한 명령에서 복수개의 -var-file 플래그를 사용할 수 있습니다.

가령 버전 관리 시스템에 어떤건 체크인되고 어떤건 체크인되지 않는 상황이라면 다음과 같을 것입니다:

1
2
3
$ terraform plan \
-var-file="secret.tfvars" \
-var-file="production.tfvars"
환경변수에서 읽기 (From environment variables)

Terraform은 변수의 값을 찾기 위해 TF_VAR_${name} 형식의 이름을 가진 환경변수 또한 읽을 것입니다.
예를 들면, TF_VAR_access_key 라는 이름의 환경변수가 존재한다면 Terraform은 access_key 라는 이름의 변수의 값으로 해당 환경변수의 값을 할당합니다.
(TF_VAR_access_key=foo 인 경우 access_key의 값으로 foo 할당)

참고: 환경변수는 오직 문자열 형식으로만 변수 값을 할당합니다.
리스트(List)와 맵(Map) 타입의 변수을 할당하고 싶다면, 다른 매커니즘을 사용해 값을 할당해야 합니다.

UI 입력 (UI Input)

만약 여러분이 아무것도 하지 않고 (값 할당 없이) terraform plan 명령이나 terraform plan 명령을 실행한다면,
아래와 같이 Terraform은 사용자에게 값을 입력하도록 물어볼 것입니다.

이전에 언급했듯, 이 방법은 값이 저장되지 않습니다. 하지만 Terraform을 처음 사용하는 사용자에게는 좋은 사용자 경험이 될 것입니다.

terraform plan 명령 실행시 할당되지 않은 값에 대한 질문

참고: UI 입력은 환경변수와 동일하게 오직 문자열 형식으로만 변수 값을 할당합니다.
리스트(List)와 맵(Map) 타입의 변수을 할당하고 싶다면, 다른 매커니즘을 사용해 값을 할당해야 합니다.

변수 기본값 (Variable Defaults)

만약 위에서 소개한 방법 중 그 어떤 곳에서도 변수의 값을 할당하는 곳이 없고, 정의한 변수가 기본값을 가지고 있다면 해당 기본값을 변수에 할당합니다.

리스트 (Lists)

리스트는 명시적으로도 정의할 수 있고 암묵적으로도 정의할 수 있습니다.

1
2
3
4
5
# brackets 표기를 사용해 암묵적으로 리스트 타입 정의
variable "cidrs" { default = [] }
# 리스트 타입 명시
variable "cidrs" { type = "list" }

여러분들은 terraform.tfvars 파일에서 리스트의 값들을 할당하실 수 있습니다:

1
cidrs = [ "10.0.0.0/16", "10.1.0.0/16" ]

맵 (Maps)

우리는 기존 환경설정 내의 민감한 정보들(API Key나 Secret Key 같은)을 변수를 사용하도록 교체했습니다.
하지만, AMI의 경우에는 아직까지 직접 기입하고 있죠.
불행하게도, AMI는 사용중인 리전에 따라 지정되어야 합니다. (서울 리전에서의 Ubuntu 16.04 LTS AMI와 버지니아 리전의 Ubuntu 16.04 LTS AMI의 AMI ID가 다르거든요)

한가지 옵션으로 그냥 유저에게 리전에 적합한 AMI를 직접 입력하도록 묻는 것이 있겠지만, Terraform은 맵(Map)을 사용해 그거보다 더 나은 방법으로 문제를 해결할 수 있습니다.

맵(Map)은 테이블에서 특정 키로 값을 찾아 변수에 할당하기 위한 방법입니다.
아래 예제는 이를 정말 잘 보여줍니다. 우리의 AMI들을 맵으로 추출하고 us-west-2 리전을 지원해보도록 만들어 봅시다.

다음 내용을 variables.tf 파일에 추가하세요:

1
2
3
4
5
6
7
variable "amis" {
type = "map"
default = {
ap-northeast-2 = "ami-983ce8f6"
us-west-2 = "ami-06b94666"
}
}

변경 후 variables.tf 코드는 다음과 같을 것입니다:

Map 타입의 변수는 명시적으로 정의할 수 있고,
아니면 기본 값에 Map 타입의 값을 지정함으로써 해당 변수의 타입이 맵이라는 것을 알려 암묵적으로 정의할수도 있습니다.
위 예제에서는 두 경우 모두를 포함합니다.

자, 그럼 example.tfaws_instance 리소스를 다음처럼 변경하세요:

1
2
3
4
resource "aws_instance" "example" {
ami = "${lookup(var.amis, var.region)}"
instance_type = "t2.micro"
}

변경 후의 example.tf의 코드는 다음과 같습니다:

이번에 새로운 방식의 템플릿 표기(interpolation)를 보실 겁니다. 바로 함수 호출이죠.
lookup 함수는 동적으로 맵의 특정 키의 값을 찾아봅니다.
위 스니펫 기준으로는 var.amis 맵에서 var.region의 값을 키로 사용하여 값을 찾아봅니다.

지금 위 예제에서는 사용하지 않지만, ${var.amis["ap-northeast-2"]}를 통해 정적 참조도 가능한 것을 알아두세요.

맵 할당하기 (Assigning Maps)

우리는 위에서 기본 값을 정의했지만, 맵 또한 -var-var-file 플래그를 통해 맵에 대한 값을 할당할 수 있습니다.

예를 들면 다음과 같습니다:

1
$ terraform plan -var 'amis={ ap-northeast-2 = "foo", us-west-2 = "bar" }'

참고: -var 플래그를 통해 맵을 할당할 수 있지만,
할당하려는 해당 변수의 기본값을 {}으로 설정해 타입을 암시적으로 설정하거나 type = map을 통해 명시적으로 타입을 정의하여야 합니다.
그렇게 하지 않으면, Terraform은 문자열 타입의 변수로 간주하기 때문에 에러를 출력할 것입니다.

아래는 파일을 파일에서 맵을 정의하고, 맵의 값을 정의하고, 맵을 조회해보는 예제입니다. 먼저 변수를 정의하는 것부터 시작하죠.
아직 example.tf 파일이 있는 디렉토리에 계시다면, map-playground 라는 이름의 새로운 디렉터리를 생성하고
해당 디렉토리로 들어간 후 변수를 정의하기 위해 아래 내용으로 variables.tf 파일을 새로 만들어봅시다.
(이전에도 말씀드렸듯이 Terraform은 현재 작업 디렉토리 (Working directory)내의 모든 .tf 파일을 찾아서 불러들이기 떄문에, 동일한 디렉터리에 새로 설정파일을 생성하게되면 이전 예제와 설정이 섞일 수 있습니다. 이를 방지하기 위해 디렉토리를 새로 만드는 것입니다.)

1
2
3
4
variable "region" {}
variable "amis" {
type = "map"
}

그리고, 변수를 정의했으니 값을 할당해줘야겠죠? 이번에는 맵에 대한 값을 파일로 저장합니다.
다음 내용을 terraform.tfvars 파일로 저장하세요.

1
2
3
4
amis = {
ap-northeast-2 = "ami-983ce8f6"
us-west-2 = "ami-def456"
}

마지막으로, 다음 내용을 output.tf 파일로 저장하세요.
아래 스니펫은 lookup 함수를 사용해 amis 맵에서 region 변수의 값을 키로 사용하여 값을 찾아 출력할 것입니다.
output이 무얼 뜻하는지 더 궁금하시겠지만, 일단은 조금만 참으세요. 다음 섹션에서 설명할겁니다.

1
2
3
output "ami" {
value = "${lookup(var.amis, var.region)}"
}

자, 그럼 terraform apply -var region=us-west-2 명령을 실행해보세요:

region에 따라 ami가 다른걸 보세요!

region에 따라 ami가 다르게 출력되는 것을 확인하실 수 있습니다!

다음

Terraform은 여러분들의 설정을 파라미터화하기 위한 변수를 제공합니다.
맵은 여러분들이 조건에 따라 테이블의 값들을 찾아볼 수 있도록 할 것입니다.

다음 장부터는 위에서 맵을 사용해보기 위해 새롭게 만든 디렉토리와 환경설정들을 더이상 필요로 하지 않습니다.
map-playground 디렉토리와 디랙토리 내의 모든 파일들을 삭제하고,
이전에 EC2 인스턴스와 Elastic IP를 정의했던 Terraform 설정파일이 있는 디렉토리로 다시 되돌아가세요.

출력 변수 (Output Variables)

이전 섹션에서 입력 변수(input variables)를 Terraform 구성 설정을 파라미터화(parameterize) 하는 방법으로 소개했습니다.
이번 섹션에서는 출력 변수(output variables)를 Terraform 유저가 데이터를 쉽게 질의하고 볼 수 있도록 관리하는 방법으로 소개할 것입니다.

잠재적으로 복잡한 인프라를 만들때, Terraform은 여러분들의 모든 리소스에 대한 100~1000개의 속성 값들을 저장합니다.
하지만 Terraform를 사용하는 유저는 로드 밸런서 IP, VPN 주소와 같은 오직 몇가지의 중요한 속성 값만 관심을 가지고 있을 것입니다.

출력 변수는 Terraform에게 어떤 정보가 중요한지 알리는 방법입니다.
출력 변수로 지정한 데이터는 terraform apply 명령을 실행하면 출력되고, terraform output 명령을 사용해 질의할 수도 있습니다.

출력 변수 정의하기 (Defining Outputs)

이전에 우리가 생성한 Elastic IP 리소스의 공인 IP(Public IP)를 보기 위해 출력 변수를 정의해봅시다.

example.tf 파일을 열어서 아래 내용을 추가하세요:

1
2
3
output "ip" {
value = "${aws_eip.ip.public_ip}"
}

변경 후의 코드는 다음과 같을 것입니다:

위 스니펫은 ip라는 이름의 출력 변수를 정의합니다. value 필드는 해당 출력 변수가 어떤 값을 가질지를 정의하고, 거의 항상 하나 이상의 템플릿 표기법(interpolations)들을 가지고 있습니다. 대부분의 값들은 동적이기 때문이죠.
이번 경우에는 Elastic IP 리소스의 public_ip 속성을 값으로 출력할 것입니다.

여러개의 출력 변수를 정의하기 위해 복수의 output 블럭을 사용할 수도 있습니다.

출력 변수 확인하기 (Viewing Outputs)

출력 변수에 값을 채워넣기 위해 terraform apply 명령을 실행해보세요. 출력 변수를 정의했다면 반드시 한번 해야 합니다.

아마 명령을 실행한 결과가 이전과 조금 다를겁니다.
이렇개 실행 결과의 끝 부분을 보시면 여러분이 지정한 출력 변수를 확인하실 수 있죠:

terraform apply

terraform apply 명령은 출력 변수의 값들을 강조해 보여줍니다.

또한 여러분들은 terraform apply 명령을 수행해 출력 변수에 값을 할당한 이후부터 terraform output [NAME] 명령을 사용해 출력 변수의 값을 질의할 수 있습니다.

별도로 출력 변수의 이름을 인자로 넘기지 않은 경우 (terraform output 명령을 실행한 경우), Terraform은 모든 출력 변수의 이름과 값을 출력할 것입니다.
반대로 출력 변수의 이름을 인자로 넘기는 경우 (terraform output ip), Teraform은 해당 이름의 출력 변수의 값만을 출력합니다.

terraform output, terraform output ip 명령을 각각 실행해 결과를 비교해보세요:

terraform output

이 명령은 스크립트(e.g. 쉘 스크립트)에서 출력 변수의 값을 추출할 때 유용하게 사용할 수 있습니다.

다음

여러분들은 이제 어떻게 Terraform 환경설정을 입력 변수(input variables)로 파라미터화하고,
출력 변수(output variables)로 중요한 데이터를 추출하고, 프로비저너(provisioners)를 사용해 리소스를 부트스트랩(bootstrap)하는지 알겁니다.

다음 섹션에서 우리는 어떻게 모듈(modules)을 사용하고, 구조에 유용한 추상화, 그리고 Terraform 환경설정을 재사용 하는 방법을 알아볼 것입니다.

그동안 가이드를 따라오면서 만들고 수정했던 Terraform 환경설정 파일들과 리소스는 더이상 사용하지 않습니다.
terraform destroy 명령을 통해 해당 리소스들을 모두 삭제하시고, 모든 환경설정 파일 또한 삭제하세요. 다음 섹션에서 새롭게 Terraform 환경설정을 생성할 것입니다.

모듈 (Modules)

여기까지는 우리는 Terraform을 환경설정을 직접 수정하여 Terraform을 구성했습니다.
인프라가 성장함에 따라, 이 방법은 몇가지 문제를 가지고 있습니다: 인프라를 구성하는 구조(organization)의 부재, 재사용성의 부재, 그리고 팀을 위한 관리의 어려움.

Terraform의 모듈은 그룹으로 관리되는 Terraform 설정들을 가지고 있는 패키지(self-contained packages of Terraform configurations)입니다.
모듈은 재사용한 컴포넌트를 만들고, 구조를 개선시키며, 인프라를 각 부분을 블랙박스로 처리(to treat pieces of infrastructure as a black box.) 할 수 있습니다.

이 섹션은 모듈을 사용하는 기초를 다룹니다. 모듈을 작성하는 방법은 모듈 문서에서 상세히 다루고 있으니 관심이 있으시다면 해당 문서를 참고하시기 바랍니다.

주의: 이 엑션의 예제들은 AWS 프리 티어에 적용되지 않습니다.
조금의 요금이라도 지불할 의사가 없으시다면, 이 섹션의 예제를 실행하지 마세요.

모듈 사용하기 (Using Modules)

아래 예제에서는 완전한 Consul 클러스터를 구성하는 Consul Terraform 모듈을 사용할 것입니다.

아래 내용으로 새롭게 Terraform 환경설정 파일을 만드세요.
저는 consul-terraform.tf 으로 저장했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
variable "access_key" {}
variable "secret_key" {}
variable "region" {
default = "ap-northeast-2"
}
variable "key_name" {}
variable "key_path" {}
provider "aws" {
access_key = "${var.access_key}"
secret_key = "${var.secret_key}"
region = "${var.region}"
}
module "consul" {
# source 속성의 원본 값은 아래와 같으나,
# 현재 Consul의 Terraform의 AWS 템플릿에 서울 리전 (ap-northeast-2) AMI가 정의되지 않아
# AWS 서울 리전으로 사용할 수 없는 문제가 있어 fork한 버전을 대신 사용합니다.
# Consul PR URL: https://github.com/hashicorp/consul/pull/2620
# 위 PR이 머지된다면, 아래 코드를 사용하셔도 무방합니다.
#
# source = "github.com/hashicorp/consul/terraform/aws"
source = "github.com/mooyoul/consul?ref=tf-module-support-aws-seoul//terraform/aws"
key_name = "${var.key_name}"
key_path = "${var.key_path}"
region = "${var.region}"
servers = "3"
}

전체 코드는 다음과 같을 것입니다:

참고로 위 provider 블럭은 AWS CLI에서 사용하는 환경변수를 사용한다면 생략할 수 있습니다. 자세한 내용은 AWS Provider 문서의 Argument ReferenceAWS Provider 문서의 Environment Variables를 참고하세요.
중요: 이 Consul 모듈은 기본 VPC를 가지고 있는 AWS 계정을 필요로합니다.

module 블럭은 Terraform에게 모듈을 만들고 관리하도록 알립니다.
이전에 봤던 리소스를 정의하는 resource 블럭과 아주 유사하죠.
다만 모듈은 리소스와 달리 오직 하나의 논리적 이름 (logical name, 별명으로 생각하시면 됩니다.)만 가지고 있습니다.
위 예제에서는 consul이 논리적 이름이 되겠죠.

module 블럭 내에 정의된 source 속성은 모듈 내 속성중 유일한 필수 항목입니다.
source 속성은 Terraform에게 어디서 모듈을 받아올 지 알립니다.
Terraform은 여러분들을 위해 모듈들을 자동으로 내려받고 관리해줄 것입니다.

위 예제에서는 모듈을 Github에서 가져오지만, Terraform은 Git, Mercurial, HTTP, 그리고 파일 경로와 같은 다양한 소스들에서 모듈을 받아올 수 있습니다.

이외 다른 설정들은 모듈의 인자(parameter) 입니다. 위 예제에서는 변수를 참조하도록 값을 채워 넣었습니다.

여러분들이 모듈을 정의하고 나서 plan이나 apply 같은 명령을 실행하기 위해서 실행 전에 한번은 모듈을 얻어오는(내려받고 설치하는) 과정이 필요할 것입니다.
terraform get 명령으로 바로 그 작업을 수행할 수 있습니다. 바로 실행해보세요:

terraform get

Terraform이 알아서 모듈을 얻어오는 것을 확인하실 수 있습니다.

terraform get 명령은 내려받지 않은 모듈들이 있다면, 내려받지 않은 모듈들을 내려받을 것입니다.
기본적으로 terraform get 명령은 업데이트를 확인하지 않습니다. 그렇기 때문에 안전하게 (그리고 빠르게) 여러번 실행할 수 있습니다.
만약 업데이트를 확인하고 내려받고 싶으시다면 -u 플래그를 사용하시면 됩니다. Golang을 쓰시는 분들은 뭔가 좀 익숙하시죠?

모듈을 계획하고 적용하기 (Planning and Apply Modules)

모듈들을 내려받았다면, 우리는 이제 plan 명령을 통해 계획을 점검하고 apply 명령을 통해 이를 적용해볼 수 있습니다.
여러분들이 terraform plan 명령을 실행한다면 다음과 같은 출력을 볼것입니다:

개념적으로 모듈은 블랙박스(block box)처럼 취급되더라도,
게획(plan)에서 Terraform은 모듈이 괸리하는 각 리소스를 보여주므로 계획에서 수행하는 작업에 대한 상세한 정보를 볼 수 있습니다.

만약 여러분들이 줄여진 계획(실행계획) 출력을 원하신다면 -module-depth=number 플래그를 사용해 Terraform이 모듈별로 요약을 출력하도록 할 수 있습니다.

예로 terraform plan -module-depth=0 명령을 실행해보면 이전과 다르게 출력이 줄어든 것을 확인할 수 있습니다:

terraform plan -module-depth=0

다음은 terraform apply 명령을 실행해 모듈을 만들어봅시다.
위에서 경고했듯이, 이 Consul 모듈은 AWS 프리티어에 해당하지 않는 리소스를 생성하기 때문에 약간의 비용이 발생하니 참고하시기 바랍니다.

자, 그럼 terraform apply 명령을 실행해봅시다!

몇분정도만 기다리면, 세개의 서버로 이루어진 Consul 클러스터가 동작하는 것을 얻을 겁니다!
Consul이 어떻게 동작하고, Consul을 어떻게 설치하고, 그리고 Consul을 어떻게 클러스터로 구성하는지 아무런 지식을 가지고 있지 않지만,
우리는 단 몇분만에 실제로 돌아가는 Consul 클러스터를 만들었습니다. 짱이죠?

모듈 출력 (Module Outputs)

위의 서버와 같은 속성을 설정해 모듈을 파라미터화(parameterize)했던 것처럼, 모듈은 정보를 출력 할 수도 있습니다 (리소스에서 정보를 출력했던 것처럼요).

여러분들은 모듈들이 어떤 출력을 지원하는지 알기 위해서 각 모듈드의 코드나 문서를 참조해야합니다만,
이 가이드에서는 그냥 여러분들께 Consul 모듈이 구성된 Consul 서버들에 대한 주소 정보를 가지고있는 server_address라는 이름을 가진 출력값을 가지고 있다는걸 알려드릴겁니다.

server_address를 참조하기 위해서 일단 출력 변수에 저장해보도록 하겠습니다.
모듈 출력은 출력 변수 뿐만 아니라 다른 리소스(Elastic IP 리소스를 구성할 때 EC2 인스턴스의 ID를 참조했던 것 처럼), 다른 Provider를 구성할 때 등 어디에서든지 참조해 사용할 수 있습니다.

이전에 만들었던 consul-terraform.tf 파일에 아래 내용을 추가하세요:

1
2
3
output "consul_address" {
value = "${module.consul.server_address}"
}

변경 후 코드는 다음과 같습니다:

모듈 출력을 참조하기 위한 문법은 아주 친숙할겁니다.
문법은 ${module.NAME.ATTRIBUTE} 입니다.
NAME은 전에 우리가 할당한 논리적 이름(logical name)이고,
ATTRIBUTE는 모듈이 출력하는 속성의 이름입니다. server_address가 이에 해당하겠죠.

여러분들이 terraform apply 명령을 다시 실행한다면 Terraform은 아무런 변경사항을 만들지 않을겁니다만,
명령의 실행 결과에서 Consul 서버의 주소를 consul_address 이름의 출력값으로 확인할 수 있을 것입니다:

terraform apply 실행 후 확인할 수 있는 consul_address 출력

다음

생성한 Consul 모듈은 이제 더이상 사용하지 않습니다. 필요치 않은 과금을 방지하려면 반드시 terraform destroy 명령을 통해 인프라를 제거하세요!

어떤 source 종류들을 지원하는지, 어떻게 모듈을 작성하는지 등 모듈에 대한 자세한 정보를 얻기 원하신다면 모듈 문서를 참고하세요.

다음 단계들

여러분들이 Terraform이 유용하다는 것을 알 수 있을뿐만 아니라, 여기서 배운 지식들로 여러분들의 인프라 구축을 향상시킬 수 있기를 바랍니다.

드디어 여러분들은 첫 걸음마를 뗐습니다.
더 많은 것들을 배우시고 싶은 분들을 위해 참고할만한 몇가지 링크를 남겨놓으니 참고하시기 바랍니다.

  • Terraform 문서 - Terraform이 어떻게 동작하는지에 대한 기술 상세정보를 포함해 Terraform의 모든 기능들에 대한 자세한 레퍼런스가 담겨 있습니다.
  • Terraform 예제 - Terraform을 사용하는 완벽한 기능의 구성 파일들이 담겨 있습니다. Terraform으로 어떤 것을 할 수 있는지 더 자세히 알아볼 수 있습니다.
  • Terraform 불러오기(Import) - Import 문서는 기존 인프라를 Terraform으로 불러오기 위한 내용을 다룹니다.

회고

처음에는 직접 가이드를 써보려고 했는데 생각보다 범위가 넓어 Terraform의 Getting Started 문서를 한국어로 번역하는것을 기본으로 살을 좀 더 붙였습니다.
영어실력이 미천하여 다소 번역이 딱딱하거나 엉성할 수 있습니다 ㅠㅠ

사실 저는 DevOps 엔지니어가 아니라 풀스택 개발자입니다만, 최근에는 서비스를 배포하는 방법이 점점 다양하고 중요해지는 것 같아 DevOps를 열심히 공부하고 있습니다.
웹 생태계는 해가 지날수록 눈 깜짝하는 사이에 정말 많은것이 쏟아져나오고 빠르게 변화하고 있는데, DevOps 역시 정말 빠르게 변화하고 진화하는 것 같네요. 흥미진진합니다.

Hashicorp의 다른 제품들, 그리고 그것들을 묶어서 만든 에코시스템인 Atlas도 정말 대단합니다.
Hashicorp - Devops Defined를 보면 목표도 확실한 것 같고요.
예전에 Hashicorp의 대표인 Mitchell Hashimoto의 인터뷰 기사를 본 적이 있는데, DevOps를 바로잡기 위헤서 Terraform과 같은 도구들을 만들었다고 언급하기도 했죠.

오픈소스 프로젝트들에서 vagrant up 뿐만 아니라 terraform apply도 자주 마주칠 수 있기를 기대해봅니다!

기념으로 남기는 링크