Terraform은 Infrastructure as Code 도구라서 AWS를 사용하는 경우 VPC나 EC2 인스턴스, 시큐리티 그룹 등을 Terraform으로 관리할 수 있다. 이런 요소들은 직접 웹 콘솔에서 만들거나(API를 써도 되지만...) Terraform 도구를 사용하면 되지만 서버의 환경 설정 등의 프로비저닝 작업을 모두 Terraform으로 관리하는 것은 고민되는 접근이다.
여기서 환경 설정이란 것은 Python이나 Node.js를 설치한다거나 apt-get 업데이트를 한다거나 nginx를 설치한다거나 하는 등의 일이다. 사실 이런 작업은 Ansible이나 Chef 등이 더 적합하다고 보지만 어디까지를 Terraform으로 하고 어디까지를 Ansible이나 Chef로 할지는 각 조직에서 결정할 일이다.
이전에 Terraform을 사용할 때는 AWS에서 주로 썼기에 Packer로 프로비저닝한 머신 이미지를 AMI로 만들어서 Terraform에서 사용했기에 Terraform에서는 프로비저닝을 직접 사용하지는 않았고 잘 살펴보지도 않았다. Terraform에서도 Provisioner를 제공하고 있어서 여기서 직접 프로비저닝하거나 다른 도구와 연결해서 사용할 수 있다. 얼마 전에 Terraform으로 Digital Ocean의 Droplet 생성하면서 provisioner
를 처음 사용해 보고 사용방법을 좀 더 정리해 보게 되었다. 여기서 사용한 Terraform 버전은 v0.11.2다.
Provisioner
Provisioner는 Terraform으로 리소스를 생성하거나 제거할 때 로컬이나 원격에서 스크립트를 실행할 수 있는 기능으로 0.8부터 추가되었다. 기본적으로 provisioner
는 생성할 때만 실행되고 그 뒤에 업데이트되거나 하진 않는다. 그래서 provisioner
가 실패하면 리소스가 잘못되었다고 판단하고 다음 terraform apply
할 때 제거하거나 다시 생성한다. provisioner
에서 when = "destroy"
를 지정하면 해당 프로비저너는 리소스를 제거하기 전에 실행되고 프로비저너가 실패한다면 다음 terraform apply
할 때 다시 실행하게 된다. 문서에 따르면 이 때문에 제거 프로비저너는 여러 번 실행해도 괜찮도록 작성해야 한다고 한다.
local-exec
프로비저너
local-exec 프로비저너는 이름 그대로 로컬에서 실행하는 프로비저너다. 여기서 로컬은 terraform apply
를 실행하는 현재 머신을 의미한다.
resource "null_resource" "demo" {
provisioner "local-exec" {
command = "echo hello world"
}
}
위처럼 .tf
파일을 작성했다. null_resource는 말 그대로 빈 리소스를 의미한다. 그래서 하나의 리소스와 묶이지 않은 프로비저닝 등을 사용하거나 할 때 사용할 수 있는데 여기서는 데모용으로 사용했다. 다른 리소스와 크게 다른 부분은 없고 provisioner "local-exec"
를 지정해서 echo hello world
를 실행하도록 했다.
$ 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:
+ null_resource.demo
id: <computed>
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
null_resource.demo: Creating...
null_resource.demo: Provisioning with 'local-exec'...
null_resource.demo (local-exec): Executing: ["/bin/sh" "-c" "echo hello world"]
null_resource.demo (local-exec): hello world
null_resource.demo: Creation complete after 0s (ID: 822915894771826369)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
terraform apply
를 실행하면 리소스를 생성하고 나서 프로비저너를 실행하면서 hello world
가 출력된 것을 볼 수 있다.
resource "null_resource" "cluster" {
provisioner "local-exec" {
command = "echo hello world"
}
provisioner "local-exec" {
command = "python --version"
}
}
provisioner
는 한 리소스 안에서 여러 번 정의해도 상관없고 작성한 순서대로 실행된다.
$ 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:
+ null_resource.cluster
id: <computed>
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
null_resource.cluster: Creating...
null_resource.cluster: Provisioning with 'local-exec'...
null_resource.cluster (local-exec): Executing: ["/bin/sh" "-c" "echo hello world"]
null_resource.cluster (local-exec): hello world
null_resource.cluster: Provisioning with 'local-exec'...
null_resource.cluster (local-exec): Executing: ["/bin/sh" "-c" "python --version"]
null_resource.cluster (local-exec): Python 3.6.3
null_resource.cluster: Creation complete after 0s (ID: 6241181854377804821)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
이를 실행하면 아까처럼 hello world
가 출력되고 내 로컬 머신의 Python 버전이 출력된 것을 볼 수 있다. 이 결과는 앞에 생성한 리소스를 다 지우고 다시 실행한 것이다. 생성 시에만 프로비저너가 실행되므로 이미 생성한 리소스에 프로비저너를 추가하더라도 실행되지 않는다. 더 정확히는 리소스가 변경된 것이 아니므로 이미 최신 상태라고 나오므로 실행이 되지 않는다.
아직 사용 사례는 다 모르지만 local-exec
는 주로 로컬에 스크립트나 프로그램을 실행하거나 클라우드에 실행한 IP나 ID 같은 정보를 로컬 파일로 만들 때 사용하는 것으로 보인다.
remote-exec
프로비저너
remote-exec 프로비저너는 Terraform으로 Digital Ocean의 Droplet 생성하기에서 사용해서 실제 사용방법은 이전 글을 보아도 되긴 한다. local-exec
와는 달리 remote-exec
는 리소스를 생성한 후 타겟 머신에서 실행된다.
remote-exec
는 inline
, script
, scripts
가 있는데 셋 중 한 번에 하나만 사용해야 한다. inline
을 살펴보기 위해 아래처럼 AWS에 EC2 인스턴스를 실행하는 tf 파일을 작성해보자.
provider "aws" {
region = "ap-northeast-1"
}
resource "aws_instance" "demo" {
ami = "ami-d39a02b5" # ubuntu 16.04
instance_type = "t2.micro"
subnet_id = "subnet-xxxxxxxx"
security_groups = ["sg-xxxxxxxx"]
key_name = "your-key-pair"
connection {
user = "ubuntu"
type = "ssh"
private_key = "${file("~/.ssh/id_rsa")}"
timeout = "2m"
}
provisioner "remote-exec" {
inline = [
"hostname",
"lsb_release -a",
]
}
}
원격에 접속해서 실행하려면 어떻게 접속할지를 알아야 하므로 connection을 지정해야 한다. 지금은 ssh
와 winrm
를 지원한다는데 winrm
은 안 써봐서 모르겠고 보통은 ssh
를 쓸 것으로 생각한다. connection
은 resource
아래 선언할 수도 있고 provisioner
아래 선언할 수도 있는데 resource
아래 선언하면 해당 리소스의 모든 프로비저너가 선언한 커넥션을 사용하게 된다. 여기서는 inline
으로 호스트 명과 Ubuntu의 버전 정보를 출력했다.
$ terraform apply
aws_instance.demo: Creating...
ami: "" => "ami-d39a02b5"
aws_instance.demo: Still creating... (10s elapsed)
aws_instance.demo: Still creating... (20s elapsed)
aws_instance.demo: Provisioning with 'remote-exec'...
aws_instance.demo (remote-exec): Connecting to remote host via SSH...
aws_instance.demo (remote-exec): Host: 13.113.162.115
aws_instance.demo (remote-exec): User: ubuntu
aws_instance.demo (remote-exec): Password: false
aws_instance.demo (remote-exec): Private key: true
aws_instance.demo (remote-exec): SSH Agent: true
aws_instance.demo (remote-exec): Connected!
aws_instance.demo (remote-exec): ip-10-10-1-47
aws_instance.demo (remote-exec): No LSB modules are available.
aws_instance.demo (remote-exec): Distributor ID: Ubuntu
aws_instance.demo (remote-exec): Description: Ubuntu 16.04.3 LTS
aws_instance.demo (remote-exec): Release: 16.04
aws_instance.demo (remote-exec): Codename: xenial
aws_instance.demo: Creation complete after 34s (ID: i-0f8e4ce616fee9e52)
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
EC2 인스턴스를 생성하면 SSH 접속에 성공한 뒤에 호스트 명과 Ubuntu 버전을 출력한 것을 볼 수 있다. inline
대신 script
를 사용하면 로컬에 있는 파일을 원격 서버에 복사한 뒤에 실행한다. 복잡한 스크립트의 경우 유용하고 scripts
는 여러 파일을 실행해야 할 때 사용한다.
resource "aws_instance" "demo" {
# 중략
connection {
user = "ubuntu"
type = "ssh"
private_key = "${file("~/.ssh/id_rsa")}"
timeout = "2m"
}
provisioner "remote-exec" {
script = "./bootstrap.sh"
}
}
앞에서 본 파일을 위처럼 수정한 뒤 bootstrap.sh
파일에 똑같은 명령어를 입력한 뒤에 실행하면 아까와 똑같은 결과가 나오는 것을 확인할 수 있다.
그 밖의 프로비저너
위에서 본 프로비저너가 가장 간단하게 사용할 수 있는 프로비저너고 프로비저너 문서를 보면 다양한 프로비저너가 있는 걸 볼 수 있다. 단순히 파일을 복사하거나 Chef, Salt와 연결할 수 있는 프로비저너도 존재한다.
앞에서도 얘기했듯이 어디까지를 테라폼의 영역으로 가져가고 어디서부터 프로비저닝 도구를 사용할지는 상황에 맞게 고민해 보아야 할 문제이다.
Comments