Provisioner는 머신 이미지 내부에 필요한 설정이나 소프트웨어를 설치할 때 사용한다. 예를 들면 필요 패키지 설치, 사용자 생성 등 이런 머신 이미지 내 필요한 작업을 Provisioner를 통해서 할 수 있다. 그리고 이 Provisioner의 종류가 굉장히 많다. 문서를 참조하면 더 많은 정보를 알 수 있다.
module "security_group" {
source = "tedilabs/network/aws//modules/security-group"
version = "0.24.0"
name = "${local.vpc_name}-provisioner-userdata"
description = "Security Group for SSH."
vpc_id = "vpc-04a888e12b489eb12"
ingress_rules = [
{
id = "ssh"
protocol = "tcp"
from_port = 22
to_port = 22
cidr_blocks = ["0.0.0.0/0"]
description = "Allow SSH from anywhere."
},
{
id = "http"
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
description = "Allow HTTP from anywhere."
}
]
egress_rules = [
{
id = "all/all"
description = "Allow to communicate to the Internet."
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
}
]
tags = local.common_tags
}
이제 Userdata를 이용해서 AWS EC2 인스턴스를 만드는 부분이다. 기본적으로 ami, instance_type, subnet_id, associate_public_ip_address는 할당해주었다. 여기서 새롭게 보이는 key_name은 EC2에 SSH로 접속할 때 사용했던 Key pair의 Name이다. 그리고 이제 핵심 부분인 user_data가 있다. 보면 EOT로 시작해서 EOT로 끝나는데 그냥 Multiple 문자열을 받기 위해 저렇게 사용한다. 그리고 첫 부팅시에만 적용될 sudo apt-get update와 nginx를 설치하는 작업을 한다. 그 다음 위에서 설명한 security_group을 지정했다.
이제 이 부분이 핵심이다. null_resource는 이 역시 Terraform에서 지원하는 Provider 중 하나인데 이 녀석은 딱 한가지 Attribute를 가지고 있다. triggers 라는 Attribute인데 이 triggers는 그 안에 지정한 것들 중 하나라도 변경 사항이 생기면 이 null_resource라는 전체 리소스를 재실행한다. 즉 리소스를 다시 만들어낸다. 그리고 이것을 사용해서 실제 provisioner를 사용했다.
triggers에 3개가 있다. instance_id, script, index_file. 우선 instance_id는 위에서 만든 provisioner라는 이름의 리소스를 가진 리소스의 ID가 변경되거나, script, index_file은 files 폴더에 있는 파일의 해시값이 변경된다(파일 내용이 변경됨)면 이 리소스가 재실행된다는 의미이다.
여기에 local-exec는 로컬 PC에서 명령어를 수행하는 부분이다. 의미없지만 잘 수행되는지 Hello World를 echo로 실행한다.
file은 로컬에서 리모트로 파일을 전송한다. 그래서 source랑 destination 속성이 있다. 저 부분은 뭐가 뭔지 한 눈에 알 수 있을것이고 connection 부분이 중요하다. 이 부분이 어떻게 전송할지에 대한 내용이기 때문이다. 우선 SSH로 보내고 유저 이름은 "ubuntu"이다. Host는 위에서 provisioner라는 리소스 이름의 인스턴스 Public IP가 된다. 그리고 private_key가 이제 EC2에 SSH로 접속할 때 사용하는 .pem 파일이다. 이 .pem 파일은 AWS Console에서 EC2 인스턴스 만들 때 Key pair로 사용하는 그 .pem 파일이다.
그리고 remote-exec는 리모트 머신에서 명령어를 수행한다. 그 수행하는 명령어가 바로 script = "실행할 스크립트 파일의 경로" 또는 inline = ["명령어", "명령어"]이다. 이 명령어를 작성하는 방법은 세가지가 있다. script, scripts, inline. script와 inline은 보았고, scripts는 스크립트 파일 한개가 아니라 여러개를 실행할 때 사용한다.
그래서 이 main.tf에 대해 Apply를 하면 기존에 있는 VPC를 사용해서(저 위에 vpc-id가 기존에 있는 내 VPC ID를 붙인 것) 해당 VPC의 Subnet역시 기존에 있는 Subnet에 EC2 두개가 만들어진다. (Userdata로, Provisioner로) 그리고 각 EC2는 Nginx를 설치하는데 Userdata는 설치하는 것까지가 끝이다. 그리고 그것은 딱 한번 최초 부팅시에만 실행될 것이다. 그게 Userdata의 특징이니까. 그러나 provisioner로 만든 EC2는 null_resource를 사용해서 감시하는 3개의 트리거가 발동될 때마다 새롭게 null_resource에 작성한 작업들을 수행하게 된다. 왜냐하면 null_resource에서 작업하기로 하는 HOST가 전부 provisioner로 만든 EC2의 Public IP를 가르키기 때문.
그래서 이 것을 Apply해보자. 다음과 같이 정상적으로 Apply가 됐고 EC2 인스턴스 두개가 생겼다.
AWS Console에 들어가보면 만든 EC2 두개가 보인다.
그리고 각 EC2의 Security도 잘 적용된 상태다.
이 상태에서 저 Outputs에서 알려준 Public IP를 접근해보자. Nginx를 두 EC2 모두 설치했기 때문에 80으로 접속 시 Nginx 화면이 보여야한다.
Userdata로 만든 EC2
Provisioner로 만든 EC2
Provisioner로 만든 EC2는 왜 다를까? null_resource에서 index.html 파일을 Nginx가 기본으로 뿌려주는 경로에 위치시켰기 때문에 내가 만든 이 index.html 파일이 보이는 것이다.
그럼 여기서 한 발 더 나아가서 이 파일(index.html)이 수정되면? => 트리거가 발동 => provisioner로 만든 EC2가 Replace 될 것
files/index.html
다음과 같이 수정해보자.
<h1>Hello Chyoni!</h1>
그 후 Apply 진행. 변경된다는 Plan이 보인다.
작업이 진행되고 다시 provisioner로 띄운 EC2로 들어가보면 다음과 같이 변경 사항이 적용됐음을 확인 가능하다.