https://www.terraform.io/language/resources/provisioners/syntax
일단 이번 글은 위 공식문서를 기반으로 작성합니다.
우선 이번 포스팅에서 알아볼 두개의 개념에 대해 미리 알아보도록 하겠습니다.
AWS EC2의 Userdata, Terraform Provisioner입니다.
Terraform의 Provisioner는 여러개의 provisioner를 제공합니다. file, local_exec, remote_exec입니다. file은 로컬에서 리모트 머신으로 파일을 복사할 떄쓰입니다. 또한 local_exec는 로컬 P에서 명령어를 수행할떄 쓰이며, remote_exec는 리모트 머신에서 명령어를 수행할 떄 사용됩니다. 여기에는 SSH, WIN_rm2개의 프로토콜이 사용되는데, 각각 Unix/Linux, Window에 접근하기 위한 프로코콜입니다.
그 다음으로는 Userdata가 있습니다. 이는 cloud-init이라는게 리눅스에는 맨 처음에 cloud-init이라는 패키지가 설치되어 있습니다. 이는 부팅 시점에 부트스트래핑, 사용자 설정, 파일구성, 소프트웨어 설치를 Linux에서 담당하는 패키지입니다. 즉 이는 AMI이미지를 활용하여 인스턴스의 첫 부팅 시점에만 실해오디는 것이라고 보시면 됩니다.
반면에 Terraform Provisioner는 기본적으로 첫 리소스 생성시점, 리소스 삭제 시점, 매번 수행 등 다양한 옵션을 주어 활용할 수 있습니다.
이제 바로 코드로 들어가겠습니다.
/versions.tf
terraform {
required_version = "~> 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
이는 테라폼 apply를 위한 의존성 버전을 명시해 주었습니다.
/files/index.html
<h1>Hello fastcampus!</h1>
/files/install-nginx.sh
#!/bin/bash
sudo apt-get update
sudo apt-get install -y nginx
그 다음에는 이번 실습떄 쓰일 html파일과, Nginx설치를 위한 shel-script파일이 있습니다.
/outputs.tf
output "provisioner_instance" {
value = {
public_ip = aws_instance.provisioner.public_ip
public_dns = aws_instance.provisioner.private_dns
private_ip = aws_instance.provisioner.private_ip
private_dns = aws_instance.provisioner.private_dns
}
}
output "userdata_instance" {
value = {
public_ip = aws_instance.userdata.public_ip
public_dns = aws_instance.userdata.public_dns
private_ip = aws_instance.userdata.private_ip
private_dns = aws_instance.userdata.private_dns
}
}
그 다음에는 Provisioner, userdata인스턴스 각각의 공인Ip, 사설Ip, 공인dns, 사설dns등을 작성해 두었습니다.
/main.tf
provider "aws" {
region = "ap-northeast-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
locals {
vpc_name = "default"
common_tags = {
"Project" = "provisioner-userdata"
}
}
resource "aws_default_vpc" "default" {
tags = {
Name = local.vpc_name
}
}
module "security_group" {
source = "tedilabs/network/aws//modules/security-group"
version = "0.24.0"
name = "${local.vpc_name}-provisioner-userdata"
description = "Security Group for test."
vpc_id = aws_default_vpc.default.id
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
###################################################
resource "aws_instance" "userdata" {
ami = data.aws_ami.ubuntu.image_id
instance_type = "t2.micro"
key_name = "fastcampus"
user_data = <<EOT
#!/bin/bash
sudo apt-get update
sudo apt-get install -y nginx
EOT
vpc_security_group_ids = [
module.security_group.id,
]
tags = {
Name = "fastcampus-userdata"
}
}
###################################################
# Provisioner - in EC2
###################################################
resource "aws_instance" "provisioner" {
ami = data.aws_ami.ubuntu.image_id
instance_type = "t2.micro"
key_name = "fastcampus"
vpc_security_group_ids = [
module.security_group.id,
]
tags = {
Name = "fastcampus-provisioner"
}
provisioner "remote-exec" {
inline = [
"sudo apt-get update",
"sudo apt-get install -y nginx",
]
connection {
type = "ssh"
user = "ubuntu"
host = self.public_ip
}
}
}
###################################################
# Provisioner - in null-resources
###################################################
# resource "aws_instance" "provisioner" {
# ami = data.aws_ami.ubuntu.image_id
# instance_type = "t2.micro"
# key_name = "fastcampus"
# vpc_security_group_ids = [
# module.security_group.id,
# ]
# tags = {
# Name = "fastcampus-provisioner"
# }
# }
# resource "null_resource" "provisioner" {
# triggers = {
# insteance_id = aws_instance.provisioner.id
# script = filemd5("${path.module}/files/install-nginx.sh")
# index_file = filemd5("${path.module}/files/index.html")
# }
# provisioner "local-exec" {
# command = "echo Hello World"
# }
# provisioner "file" {
# source = "${path.module}/files/index.html"
# destination = "/tmp/index.html"
# connection {
# type = "ssh"
# user = "ubuntu"
# host = aws_instance.provisioner.public_ip
# }
# }
# provisioner "remote-exec" {
# script = "${path.module}/files/install-nginx.sh"
# connection {
# type = "ssh"
# user = "ubuntu"
# host = aws_instance.provisioner.public_ip
# }
# }
# provisioner "remote-exec" {
# inline = [
# "sudo cp /tmp/index.html /var/www/html/index.html"
# ]
# connection {
# type = "ssh"
# user = "ubuntu"
# host = aws_instance.provisioner.public_ip
# }
# }
# }
그 다음에는 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/default_vpc
이 링크에서 보실 수 있다싶이 aws의 default_vpc를 활용할 수 있게 도와주는 것이라고 보시면 됩니다.
현제 Select되어 있는 것이 기본적으로 생성되어 있는 기본 default VPC입니다
그 다음으로는 이번 실습에서 사용될 security그룹을 만들어 주었습니다.
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group
위 공식문서를 참고하여 vpc_id, ingress_rules, egress_rules를 작성해 주었습니다.
그 다음에는 user_data, provisioner를 위한 EC2인스턴스를 https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance
이 링크를 참조하여 작성했습니다.
userdata인스턴스를 작성할 때, user_data옵션을 주어 기본적으로 인스턴스가 런칭될 떄 실행되게 했습니다.
...
user_data = <<EOT
#!/bin/bash
sudo apt-get update
sudo apt-get install -y nginx
EOT
...
여기서 EOT는 시작의 끝과 마지막을 알리는 것이라고 보시면 됩니다.
또한 key_name은 해당 EC2인스턴스의 접속에 필요한 Key_pair를 써주는 칸입니다. 저는 이미 만들어논 fastcampus라는 키페어가 존재합니다.
이제 provisioner EC2인스턴스를 만들어 주었습니다. 여기서는 remote-exec프로비저너 중 Inline속성을 사용했습니다. 이 외에도 script, scripts를 통해서 외부에서 스크립트 파일을 주입받을 수도 있습니다.
또한 이 provisioner인스턴스의 remote-exec 프로비저너를 사용하기 위한, 커넥션을 설정해 주었습니다. 여기서 host에 self.public_ip를 주었는데 여기서 self는 aws_instance 리소스의provisioner를 가리킵니다.즉 ssh를 통해서, Ubuntu라는 유저를 통해 해당 인스턴스의 공인 IP로 접속을 시도해서 프로비저닝을 하겠다라는 의미입니다. 이제 Apply를 해보도록 하겠습니다.
다음과 같이 8개의 리소스를 설치하겠다고 합니다.
그럼 쫙 로그가 뜨게되는데, user_data에 제공한 내용은 aws내에서 실행되기 때문에 찍히지 않지만, provisioner에 Inline으로 적어준 코드는 terraform에서 관리하기 때문에 디버깅이 가능합니다.
다음과 같이 remote_exec의 연결과정이 찍힙니다.
그리고 결과는 위와 같은데, 위의 인스턴스의 public_ip를 들어가 보겠습니다.
다음과 같이 정상적으로 nginx이 깔린것을 확인할 수 있습니다.
그 외의 팁은, user_data의 스크립트 코드에 수정을 가하면, user_data인스턴스가 아예 딴걸로 대체되는 것을 주의하셔야 합니다. 이는 aws에서 관리하기 떄문입니다. 이 반면에 terraform의 inline에 다른 명령어를 하나 추가해도 replace되지 않는데, 이는 인스턴스가 생성되는 시점에만 한번 실행되기 때문입니다.
이 2개는 각각 위에서 사용한 self에 대한 것이고, Create-Time에 실행되는데, 만약 provision이 되는 시점을 바꾸고 싶다면
이렇게 when을 주어 바꾸어 주시면 됩니다.
마지막으로 null_resource를 이용하여 간단한 실습 하나만 더 해보겠습니다.
https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource
이 null_resource의 특징은 기본적인 resource의 lifecycle을 따라가지만, 더한 액션은 하지 않는다는 것입니다. 또한, triggers속성에 어떠한 값이 바뀌면 해당 null_resource는 replaced되는것이 특징입니다.
/main.tf
provider "aws" {
region = "ap-northeast-2"
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
owners = ["099720109477"] # Canonical
}
locals {
vpc_name = "default"
common_tags = {
"Project" = "provisioner-userdata"
}
}
resource "aws_default_vpc" "default" {
tags = {
Name = local.vpc_name
}
}
module "security_group" {
source = "tedilabs/network/aws//modules/security-group"
version = "0.24.0"
name = "${local.vpc_name}-provisioner-userdata"
description = "Security Group for test."
vpc_id = aws_default_vpc.default.id
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
###################################################
resource "aws_instance" "userdata" {
ami = data.aws_ami.ubuntu.image_id
instance_type = "t2.micro"
key_name = "fastcampus"
user_data = <<EOT
#!/bin/bash
sudo apt-get update
sudo apt-get install -y nginx
EOT
vpc_security_group_ids = [
module.security_group.id,
]
tags = {
Name = "fastcampus-userdata"
}
}
###################################################
# Provisioner - in EC2
###################################################
# resource "aws_instance" "provisioner" {
# ami = data.aws_ami.ubuntu.image_id
# instance_type = "t2.micro"
# key_name = "fastcampus"
# vpc_security_group_ids = [
# module.security_group.id,
# ]
# tags = {
# Name = "fastcampus-provisioner"
# }
# provisioner "remote-exec" {
# inline = [
# "sudo apt-get update",
# "sudo apt-get install -y nginx",
# ]
# connection {
# type = "ssh"
# user = "ubuntu"
# host = self.public_ip
# }
# }
# }
###################################################
# Provisioner - in null-resources
###################################################
resource "aws_instance" "provisioner" {
ami = data.aws_ami.ubuntu.image_id
instance_type = "t2.micro"
key_name = "fastcampus"
vpc_security_group_ids = [
module.security_group.id,
]
tags = {
Name = "fastcampus-provisioner"
}
}
resource "null_resource" "provisioner" {
triggers = {
insteance_id = aws_instance.provisioner.id
script = filemd5("${path.module}/files/install-nginx.sh")
index_file = filemd5("${path.module}/files/index.html")
}
provisioner "local-exec" {
command = "echo Hello World"
}
provisioner "file" {
source = "${path.module}/files/index.html"
destination = "/tmp/index.html"
connection {
type = "ssh"
user = "ubuntu"
host = aws_instance.provisioner.public_ip
}
}
provisioner "remote-exec" {
script = "${path.module}/files/install-nginx.sh"
connection {
type = "ssh"
user = "ubuntu"
host = aws_instance.provisioner.public_ip
}
}
provisioner "remote-exec" {
inline = [
"sudo cp /tmp/index.html /var/www/html/index.html"
]
connection {
type = "ssh"
user = "ubuntu"
host = aws_instance.provisioner.public_ip
}
}
}
terraform의 filemd5함수를 이용하면, files/*의 내용이 하나라도 조금 바뀌면 hash값이 바뀜으로서 새로운 null_resource가 생성되게끔 할 수 있게 코드를 작성했습니다.
그 다음에 여러개의 privisioner를 설정해 주었는데, file provisioner를 통해서, 로컬의 file/index.html을 인스턴스의 /temp/index.html로 복사하기 위함입니다. 그리고 그 아래에는 이전에도 했듯이 connection블럭으로 접속 방법과 어디에 접속할건지, 어떤 유저로 접속할건지 명시해줍니다. 그 다음에는 remote_exec를 통해서 files/install-nginx.sh 쉘스크립트를 실행하여 nginx를 설치해 주었습니다. 또한 그 다음으로는 리모트 머신의 /tmp/index.html파일을 루트권한이 필요한 /var/www/html/index.html폴더로 옮겨서 nginx의 기본 접속 페이지를 수정해 주는 과정을 거칩니다. 이를 위해 sudo권한으로 실행해 주는 것이 핵심입니다.
이제 다시 apply해주겠습니다.
user_data리소스는 변경하지 않았으므로, null_resource하나만 추가하는 것을 보실 수 있습니다.
이제 결과의 공인 IP를 바탕으로 provisioner_instance의 공인 IP에 들어가서 기본 Nginx페이지가 우리가 설정한 Index.html로 설정되었는지 확인해보겠습니다.
네 잘 작동하는 것을 확인할 수 있습니다. 이제 Index.html을 바꾸고 다시 apply해보겠습니다.
/files/index.html
<h1>Hello Hyunseo!!</h1>
당연히 null_resource의 hash값이 바뀌어서 갱신이 되는 것을 보실 수 있습니다. 이제 다시 provisioner의 공인IP에 들어가서 확인해보면
잘 바뀐것을 확인해볼 수 있습니다.
'DevOps > AWS Architecture' 카테고리의 다른 글
[ DevOps ] - (테라폼을 이용한 인프라 관리) 실습 AWS VPC에 OpenVPN구성 (1) | 2022.08.03 |
---|---|
[ DevOps ] - (테라폼을 이용한 인프라 관리) 테라폼 워크스페이스 디렉토리 구성 전략 (0) | 2022.08.03 |
[ DevOps ] - (패커를 이용한 머신 이미지 관리) - 디버깅하는 방법 (Debugging) (0) | 2022.08.02 |
[ DevOps ] - (패커를 이용한 머신 이미지 관리) - 후 처리기 (Post-processor) (0) | 2022.08.02 |
[ DevOps ] - (패커를 이용한 머신 이미지 관리) - 데이터 소스 활용 ( Data-source ) (0) | 2022.08.02 |