๐ก โํ ๋ผํผ์ผ๋ก ์์ํ๋ IaCโ ์ฑ ์ผ๋ก ์งํํ๋ Terraform ์คํฐ๋[T101] 3์ฃผ์ฐจ ์ ๋ฆฌ๋ด์ฉ์ ๋๋ค.
์ด๋ฒ์๊ฐ์ ํ ๋ผํผ ๊ธฐ๋ณธ์ฌ์ฉ ๋ง์ง๋ง ๋จ๊ณ(3/3)์ด๋ค. ์ด๋ฒ์ฃผ์ฐจ์์๋ ์กฐ๊ฑด๋ฌธ, ํจ์, ํ๋ก๋น์ ๋, data block์ ๋ํด ๋ฐฐ์ด ๋ค ํ๋ก๋ฐ์ด๋๋ฅผ ๊ฒฝํํด๋ณด๊ณ ๋ง๋ฌด๋ฆฌ๋๋ค. ๊ฐ์ธ์ ์ผ๋ก null_resource์ ๋ํด ์๋ชฐ๋๋ค. ๋ง์ด ์ฌ์ฉํ ํ๋ฌ๊ทธ์ธ ์ค ํ๋๋ผ๊ณ ํ๋ค..! ์ด๋ฒ ๊ธฐํ์ ์ ๋ฐฐ์๋๋ฉด ์ข์ ๊ฒ ๊ฐ๋ค. (์ง๊ธ์ terraform_data๊ฐ ๊ฐ์ ๊ธฐ๋ฅ์ ์ํํ๋ค.)
์กฐ๊ฑด ๋ฌธ์ ๊ฒฝ์ฐ C์ธ์ด์ ์ผํญ์ฐ์ฐ์์ ์ ์ฌํ๋ค. ๊ทธ ์ธ์๋ ์ง์ํ์ง ์๋ ๋ชจ์์ด๋ค.
ํ์: condition ? true_val : false_val
์ค์ต
variable "enable_file" {
default = true
}
resource "local_file" "foo" {
count = var.enable_file ? 1 : 0
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "content" {
value = var.enable_file ? local_file.foo[0].content : ""
}
์์ ์ฝ๋์ ๋ด์ฉ์ var.enable_file์ ๊ฐ์ ์ ๋ ฅํ์ง ์๊ฑฐ๋, true๋ก ์ค์ ํ๋ฉด foo.bar๋ผ๋ local file์ ์์ฑํ๋ค. ๋ฐ๋์ ๊ฒฝ์ฐ๋ผ๋ฉด ๋ฆฌ์์ค๋ฅผ ์์ฑํ์ง ์๋๋ค.
false
๋ฅผ ์ง์ ํ ๊ฒฝ์ฐ$ export TF_VAR_enable_file**=false**
$ export | grep TF_VAR_enable_file
TF_VAR_enable_file=false
$ terraform init && terraform plan && terraform apply -auto-approve
...
Changes to Outputs:
+ content = ""
You can apply this plan to save these new output values to the Terraform state, without changing any real
infrastructure.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
content = ""
true
๋ฅผ ์ง์ ํ๊ฒฝ์ฐ(=์๋ฌด๊ฒ๋ ์
๋ ฅํ์ง ์์)# ์๋ฌด๊ฒ๋ ์
๋ ฅํ์ง ์์์ ๋
$ terraform init && terraform plan && terraform apply -auto-approve
...
+ content = "foo!"
local_file.foo[0]: Creating...
local_file.foo[0]: Creation complete after 0s [id=4bf3e335199107182c6f7638efaad377acc7f452]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
content = "foo!"
$ terraform state list
local_file.foo[0]
$ echo "local_file.foo[0].content" | terraform console
โท
โ Warning: Value for undeclared variable
โ
โ The root module does not declare a variable named "ec2_instance_type" but a value was found in file
โ "terraform.tfvars". If you meant to use this value, add a "variable" block to the configuration.
โ
โ To silence these warnings, use TF_VAR_... environment variables to provide certain "global" settings to
โ all configurations in your organization. To reduce the verbosity of these warnings, use the
โ -compact-warnings option.
โต
"foo!"
์์ ์์์ฒ๋ผ, ์กฐ๊ฑด๋ฌธ์ด ์์ฃผ ์ ์ ์ฉ๋๋ค.
ํจ์๋ ๋ด์ฅํจ์๋ง ์ฌ์ฉ๊ฐ๋ฅํ๋ค. ์ฌ์ฉ์ ์ ์ํจ์์ ๊ฐ์ด ์ง์ ๋ง๋ค ์ ์๋ค. **๊ณต์๋ฌธ์** ์์ ํ์ธํ๋ฉด์ ํ์ธํ ํจ์๋ฅผ ๊ฐ๋จํ๊ฒ ์ ๋ฆฌํด๋ดค๋ค.
toset
: ํด๋น ํจ์๋ ์งํฉ๊ณผ ๊ฐ์ด ์ค๋ณต๋ ์์๋ฅผ ์ ๊ฑฐํ๊ณ , ์ ๋ ฌ์ํจ๋ค.
toset(["b", "a","b"]) =[โaโ, โbโ]
Slice
: ๋ชฉ๋ก ๋ด์์ ์ผ๋ถ ์ฐ์ ์์(elements)๋ฅผ ์ถ์ถํฉ๋๋ค. ์์ ์ธ๋ฑ์ค(startindex
)๋ ํฌํจ๋์ง๋ง ๋ ์ธ๋ฑ์ค(endindex
)๋ ์ ์ธ
[**length](https://developer.hashicorp.com/terraform/language/functions/length)
:** list, map ๋๋ string์ ๊ธธ์ด๋ฅผ ๊ณ์ฐํฉ๋๋ค. ๋ฆฌ์คํธ ๋๋ ๋งต์ด๋ฉด ์ปฌ๋ ์
(collection)์ ์์ ์, ๋ฌธ์์ด์ด๋ฉด ๋ฌธ์ ์๋ฅผ ๋ฐํํฉ๋๋ค.
์ซ์ ๊ด๋ จ ํจ์
min, max, ceil, floor
ํจ์๋ ์กด์ฌํ๋ค.๋ฌธ์์ด ๊ด๋ จ ํจ์
[ "ami-xyz","AMI-ABC","ami-efg" ]
[ "ami-xyz","ami-abc","ami-efg" ]
"ami-xyz,AMI-ABC,ami-efg"
Collection ํจ์
MAP ๊ด๋ จ ํจ์ โ map ํจ์๋ ์ง์ํ์ง ์๊ณ , tomap
ํจ์๋ฅผ ์ง์
variable "ami" {
type = map
default = {
"us-east-1" = "ami-xyz",
"ca-central-1" = "ami-efg",
"ap-south-1" = "ami-ABC"
}
description = "A map of AMI ID's for specific regions"
}
ํ๋ก๋น์ ๋๋ ํ๋ก๋ฐ์ด๋๋ก ์คํ๋์ง ์๋ ์ปค๋งจ๋์ ํ์ผ ๋ณต์ฌ ๊ฐ์ ์ญํ ์ ์ํํ๋ค.
ํ๋ก๋น์ ๋๋ก ์คํ๋ ๊ฒฐ๊ณผ๋ ํ ๋ผํผ์ ์ํ ํ์ผ๊ณผ ๋๊ธฐํ๋์ง ์์ผ๋ฏ๋ก ํ๋ก๋น์ ๋์ ๋ํ ๊ฒฐ๊ณผ๊ฐ ํญ์ ๊ฐ๋ค๊ณ ๋ณด์ฅํ ์ ์๋ค โ ์ ์ธ์ ๋ณด์ฅ ์๋จ
๊ทธ๋ ๊ธฐ์, ํ๋ก๋น์ ๋๋ณด๋จ userdata ๋ฑ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
ํ๋ก๋น์ ๋๋ ์์ฑํ ๋๋ง ์คํ๋๊ณ ์ถํ ์์
์ ์๋ค. ๊ทธ๋์ provisioner
๊ฐ ์คํจํ๋ฉด ๋ฆฌ์์ค๊ฐ ์๋ชป๋์๋ค๊ณ ํ๋จํ๊ณ ๋ค์ terraform apply
ํ ๋ ์ ๊ฑฐํ๊ฑฐ๋ ๋ค์ ์์ฑํ๋ค. provisioner
์์ when = "destroy"
๋ฅผ ์ง์ ํ๋ฉด ํด๋น ํ๋ก๋น์ ๋๋ ๋ฆฌ์์ค๋ฅผ ์ ๊ฑฐํ๊ธฐ ์ ์ ์คํ๋๊ณ ํ๋ก๋น์ ๋๊ฐ ์คํจํ๋ค๋ฉด ๋ค์ terraform apply
ํ ๋ ๋ค์ ์คํํ๊ฒ ๋๋ค. ๋ฌธ์์ ๋ฐ๋ฅด๋ฉด ์ด ๋๋ฌธ์ ์ ๊ฑฐ ํ๋ก๋น์ ๋๋ ์ฌ๋ฌ ๋ฒ ์คํํด๋ ๊ด์ฐฎ๋๋ก ์์ฑํด์ผ ํ๋ค๊ณ ํ๋ค.
์ฐธ๊ณ ์๋ฃ
์ค์๋ธ๊ณผ ์ฐ๋ํด์ ์ธ๊ฑฐ๋ฉด, ์๋์ ๋งํฌ๋ก ์งํํ๋ฉด ๋๋ค.
https://github.com/ansible/terraform-provider-ansible/tree/main/examples
์๋์ ๊ฐ์ด ์๊ฒฉ์ ๋ด์ฉ์ ์ ๋ฌํ ์ ์์ง๋ง, ๊ฐ๊ธ์ user_data๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค.
user_data = base64encode(templatefile("${path.module}/ubuntu_docker.tftpl", {}))
connection: remote-exec์ file ํ๋ก๋น์ ๋๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด, ์๊ฒฉ์ ์ฐ๊ฒฐํ ์ ๋ณด๋ฅผ ๋ช ์ํด์ผ ํ๋ค. ์ฃผ๋ก SSH/WinRM๋ง ์กด์ฌํ๋ค.
resource "aws_instance" "web" {
...
connection {
type = "ssh"
user = "root"
password = var.root_password
host = self.public_ip
}
provisioner "file" {
source = "script.sh"
destination = "/tmp/script.sh"
}
provisioner "remote-exec" {
inline = [
"chmod +x /tmp/script.sh",
"/tmp/script.sh args",
]
}
}
์๋ฌด์์ ๋ ์ํํ์ง ์๋ ๋ฆฌ์์ค์ด๋ค.
์ด๋ฐ ๋ฆฌ์์ค๊ฐ ํ์ํ ์ด์ ๋ ํ ๋ผํผ ํ๋ก๋น์ ๋ ๋์์ ์ค๊ณํ๋ฉด์ ์ฌ์ฉ์๊ฐ ์๋์ ์ผ๋ก ํ๋ก๋น์ ๋ํ๋ ๋์์ ์กฐ์จํด์ผ ํ๋ ์ํฉ์ด ๋ฐ์ํ์ฌ, ํ๋ก๋ฐ์ด๋๊ฐ ์ ๊ณตํ๋ ๋ฆฌ์์ค ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ๋ง์ผ๋ก๋ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ด๋ค.
์ฃผ๋ก ์ฌ์ฉ๋๋ ์๋๋ฆฌ์ค
์์ ์ํฉ
EC2์ ์ธ์คํด์ค๋ก ์น์๋น์ค๋ฅผ ์คํํ๋ค. ์น์๋น์ค ์ค์ ์ ๊ณ ์ ๋ IP(EIP)๊ฐ ํ์ํ๋ค.
์๋์ ๊ฐ์ด ์ํ์ฐธ์กฐ ์๋ฌ๊ฐ ๋ฐ์ํ๋ ์ํฉ์์, null_resource๋ฅผ ์ถ๊ฐํด ํด๊ฒฐํ ์ ์์
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_security_group" "instance" {
name = "t101sg"
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
instance_type = "t2.micro"
subnet_id = "subnet-dbc571b0"
private_ip = "172.31.1.100"
vpc_security_group_ids = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, T101 Study" > index.html
nohup busybox httpd -f -p 80 &
EOF
tags = {
Name = "Single-WebSrv"
}
# (1) ์ฌ๊ธฐ์์ eip ๋ฆฌ์์ค์ ๋ํ ์ ๊ทผ์ ํ๋ฉด ์ํ์ฐธ์กฐ๋ก ์๋ฌ๊ฐ ๋ฐ์ํจ.
provisioner "remote-exec" {
inline = [
"echo ${aws_eip.myeip.public_ip}"
]
}
}
# (1)๋ฒ์ ๋ด์ฉ์ ๋์ฒดํ ์ ์๋ Null_resource
resource "null_resource" "echomyeip" {
provisioner "remote-exec" {
connection {
host = aws_eip.myeip.public_ip
type = "ssh"
user = "ubuntu"
private_key = file("/home/kaje/kp-kaje.pem") # ๊ฐ์ ์์ ์ EC2 SSH Keypair ํ์ผ ์์น ์ง์
#password = "qwe123"
}
inline = [
"echo ${aws_eip.myeip.public_ip}"
]
}
}
resource "aws_eip" "myeip" {
#vpc = true
instance = aws_instance.example.id
associate_with_private_ip = "172.31.1.100"
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
์ด ๋ฆฌ์์ค ๋ํ, null_resource์ ๋์ผํ ์ญํ ์ ํ๋, ํ ๋ผํผ ์์ฒด์ ํฌํจ๋ ๊ธฐ๋ณธ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ์๊ฐ ์ ๊ณต๋๋ค.
triggers_replace: ์ธ์คํด์ค์ ์ํ๋ฅผ ์ ์ฅํ๋ฉฐ, ์ํ๊ฐ ๋ณ๊ฒฝ๋๋ฉด ์๋์ ๋ช ๋ น์ด๋ฅผ ์ํํ๋ค.
์๋์ ์์๋ฅผ ํตํด ํ์ธํ๋ฉด,
resource "terraform_data" "foo" {
triggers_replace = [
local_file.foo
]
provisioner "local-exec" {
command = "echo 'terraform_data test'"
}
}
output "terraform_data_output" {
value = terraform_data.foo.output # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ "world"
}
variable "enable_file" {
default = true
}
resource "local_file" "foo" {
count = var.enable_file ? 1 : 0
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "content" {
value = var.enable_file ? local_file.foo[0].content : ""
}
$ terraform apply -auto-approve
local_file.foo[0]: Refreshing state... [id=4bf3e335199107182c6f7638efaad377acc7f452]
terraform_data.foo: Refreshing state... [id=bdfbfd37-fccc-4f02-6542-08f1bbb3d2a1]
...
terraform_data.foo: Destroying... [id=bdfbfd37-fccc-4f02-6542-08f1bbb3d2a1]
terraform_data.foo: Destruction complete after 0s
local_file.foo[0]: Creating...
local_file.foo[0]: Creation complete after 0s [id=4bf3e335199107182c6f7638efaad377acc7f452]
terraform_data.foo: Creating...
terraform_data.foo: Provisioning with 'local-exec'...
terraform_data.foo (local-exec): Executing: ["/bin/sh" "-c" "echo 'terraform_data test'"]
terraform_data.foo (local-exec): terraform_data test
terraform_data.foo: Creation complete after 0s [id=4301eef8-bbc6-58f0-6948-a6f6ac176b6c]
Apply complete! Resources: 2 added, 0 changed, 1 destroyed.
Outputs:
content = "foo!"
$ terraform apply
terraform_data.foo: Refreshing state... [id=3396b6e8-ffca-dac3-f0d5-c41455864c42]
local_file.foo[0]: Refreshing state... [id=4bf3e335199107182c6f7638efaad377acc7f452]
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
+ create
Terraform will perform the following actions:
# local_file.foo[0] will be created
+ resource "local_file" "foo" {
+ content = "foo!"
+ content_base64sha256 = (known after apply)
+ content_base64sha512 = (known after apply)
+ content_md5 = (known after apply)
+ content_sha1 = (known after apply)
+ content_sha256 = (known after apply)
+ content_sha512 = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./foo.bar"
+ 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.foo[0]: Creating...
local_file.foo[0]: Creation complete after 0s [id=4bf3e335199107182c6f7638efaad377acc7f452]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
content = "foo!"
state์ ๊ธฐ๋ก๋๋ ๋ฆฌ์์ค์ ์ด๋ฆ์ด ๋ณ๊ฒฝ๋๋ฉด ๊ธฐ์กด ๋ฆฌ์์ค ์ญ์ ํ ์ฌ์์ฑํ๋ค. ์ด๋ฆ์ ๋ณ๊ฒฝํ์ง๋ง, ์ธํ๋ผ๋ฅผ ์ ์งํ๊ณ ์ถ์ ๋ moved block์ ์ฌ์ฉํ๋ค.
resource "local_file" "a" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.a.content
}
resource "local_file" "b" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
moved {
from = local_file.a
to = local_file.b
}
output "file_content" {
value = local_file.b.content
}
์ด์ ๊ฐ์ด moved block์ ์ฌ์ฉํ๋ฉด, ์ธํ๋ผ๋ฅผ ์ ์งํ ์ ์๋ค.
ํ๋ก๋ฐ์ด๋๋ ์ธํ๋ผ ๋ฆฌ์์ค๋ฅผ ์ ๊ณตํ๋ ์ ์ฒด๋ผ๊ณ ์๊ฐํ๋ฉด ๋๋ค. Terraform์ ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ์ฌ ํ๋ก๋ฐ์ด๋๋ผ๊ณ ๋ถ๋ฆฌ๋ ํด๋ผ์ฐ๋, SaaS, ๋ค๋ฅธ API์ ์ํธ์์ฉํ๋ค.
Terraform ์ด ์ด๋ค ๊ณต๊ธ์์ ์ฌ์ฉํ ์ง ํํํ๊ธฐ ์ํด, provider.tf
์ ๋ณ๋๋ก ์ ์ํ๋ค.
ํ๋ก๋ฐ์ด๋๋ terraform init
๋ช
๋ น์ด๋ฅผ ํตํด, ํ์ํ ํ๋ฌ๊ทธ์ธ์ ๊ฒ์ ๋ฐ ๋ค์ด๋ก๋ํ๋ฉฐ lock.hcl ํ์ผ์ ํ๋ก๋ฐ์ด๋๋ฅผ ๋ช
์ํ์ฌ ์์ผ๋ก์ ์ฝ๋ ์ํ์์ ์ฌ์ฉ๋๋ ํ๋ฌ๊ทธ์ธ์ ์ ํํ๋ค. (์์ํ์ง ๋ชปํ, ๋์์ ๋ฐฉ์งํ๋ ์ญํ ์ ํ๋ค.) terraform init
๋ช
๋ น์ด๋ ๋ฐฑ์๋ ์ค์ ํน์ ํ๋ก์ ํธ ์์์ ์ํํ๊ธฐ์ ์ฌ๋ฌ ์์
์ด ์ผ์ด๋๋ค. ํ๋ก๋ฐ์ด๋๋ง ์
๊ทธ๋ ์ด๋ํ๊ณ ์ถ์ผ๋ฉด, terraform init -upgrade
๋ฅผ ์ํํ๋ค.
์๋์ ๊ทธ๋ฆผ์ผ๋ก ํ๋ฒ์ ์ดํดํ ์ ์๋ค.
์ถ์ฒ:https://malwareanalysis.tistory.com/619
์๋์ ๊ฐ์ด, ํํธ๋์ฌ ํน์ ํ๋ฌ๊ทธ์ธ์ ์ ๊ณตํ๋ ์
์ฒด๋ผ๋ฉด ํ
๋ผํผ์ ํตํด ๋ฆฌ์์ค๋ฅผ ์ ์ํ ์ ์๋ค.
Terraform๊ณผ ํํธ๋ ๋ชฉ๋ก์ ์๋์ ์ด๋ฏธ์ง ์ฐธ๊ณ
terraform {
required_providers {
kubernetes = {
source = "hashicorp/kubernetes"
}
}
}
provider "kubernetes" {
config_path = "~/.kube/config"
}
resource "kubernetes_deployment" "nginx" {
metadata {
name = "nginx-example"
labels = {
App = "t101-nginx"
}
}
spec {
replicas = 2
selector {
match_labels = {
App = "t101-nginx"
}
}
template {
metadata {
labels = {
App = "t101-nginx"
}
}
spec {
container {
image = "nginx:1.7.8"
name = "example"
port {
container_port = 80
}
}
}
}
}
}
resource "kubernetes_service" "nginx" {
metadata {
name = "nginx-example"
}
spec {
selector = {
App = kubernetes_deployment.nginx.spec.0.template.0.metadata[0].labels.App
}
port {
node_port = 30080
port = 80
target_port = 80
}
type = "NodePort"
}
}
$ terraform init && terraform plan && terraform apply -auto-approve
...
Plan: 2 to add, 0 to change, 0 to destroy.
kubernetes_deployment.nginx: Creating...
kubernetes_deployment.nginx: Still creating... [10s elapsed]
kubernetes_deployment.nginx: Creation complete after 16s [id=default/nginx-example]
kubernetes_service.nginx: Creating...
kubernetes_service.nginx: Creation complete after 0s [id=default/nginx-example]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
kubernetes_deployment.nginx
kubernetes_service.nginx
Every 1.0s: kubectl get pods,svc MacBook-Pro.local: Wed Sep 13 21:30:54 2023
NAME READY STATUS RESTARTS AGE
pod/nginx-example-868fbd6dcc-8r9bv 1/1 Running 0 89s
pod/nginx-example-868fbd6dcc-xp4rg 1/1 Running 0 89s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 114d
service/nginx-example NodePort 10.103.116.226 <none> 80:30080/TCP 74s
์ด์ฒ๋ผ ์ ์์ ์ผ๋ก ํ ์คํธ๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.