๐กcloudNet@ ํ์ ๊ฐ์๋ค ๋์ด ์งํํ๋ Terraform 101 Study 4๊ธฐ 3์ฃผ์ฐจ ๋ด์ฉ์ผ๋ก,
[ํ ๋ผํผ์ผ๋ก ์์ํ๋ IaC] ๋์๋ฅผ ์ฐธ์กฐํ์์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค..
1. ํ๋ก๋น์ ๋
ํ๋ก๋น์ ๋๋ ํ๋ก๋ฐ์ด๋์ ๋น์ทํ๊ฒ ‘์ ๊ณต์’๋ก ํด์๋๋, ํ๋ก๋ฐ์ด๋๋ก ์คํ๋์ง ์๋ ์ปค๋งจ๋์ ํ์ผ ๋ณต์ฌ ๊ฐ์ ์ญํ ์ ์ํํ๋ค. ์๋ฅผ ๋ค์ด ํด๋ผ์ฐ๋์ ๋ฆฌ๋ ์ค VM์ ์์ฑํ๋ ๊ฒ์ ๋ํด ํน์ ํจํค์ง๋ฅผ ์ค์นํด์ผ ํ๊ฑฐ๋ ํ์ผ์ ์์ฑํด์ผ ํ๋ ๊ฒฝ์ฐ, ์ด๊ฒ๋ค์ ํ ๋ผํผ์ ๊ตฌ์ฑ๊ณผ ๋ณ๊ฐ๋ก ๋์ํด์ผ ํ๋ค.
ํ๋ก๋น์ ๋๋ก ์คํ๋ ๊ฒฐ๊ณผ๋ ํ ๋ผํผ์ ์ํ ํ์ผ๊ณผ ๋๊ธฐํ๋์ง ์์ผ๋ฏ๋ก ํ๋ก๋น์ ๋์ ๋ํ ๊ฒฐ๊ณผ๊ฐ ํญ์ ๊ฐ๋ค๊ณ ๋ณด์ฅํ ์ ์๋ค.
๋ฐ๋ผ์ ํ๋ก๋น์ ๋ ์ฌ์ฉ์ ์ต์ํ ํ๋ ๊ฒ์ด ์ข๋ค.
ํ๋ก๋น์ ๋ ์ข ๋ฅ์๋ ํ์ผ ๋ณต์ฌ์ ๋ช ๋ น์ด ์คํ์ ์ํ file, local-exec, remote-exec๊ฐ ์๋ค.
1.1 ํ๋ก๋น์ง๋ ์ฌ์ฉ ๋ฐฉ๋ฒ
ํ๋ก๋น์ ๋์ ๊ฒฝ์ฐ ๋ฆฌ์์ค ํ๋ก๋น์ ๋ ์ดํ ๋์ํ๋๋ก ๊ตฌ์ฑํ ์ ์๋ค. ์๋ฅผ ๋ค์ด AWS์ EC2 ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ๋ ํ CLI๋ฅผ ํตํด ๋ณ๋ ์์ ์ ์ํํ๋ ์ํฉ์ด ์๋ค.
์ค์ต์ ํตํด ํ๋ก๋น์ ๋์ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ๋ฐฉ์์ ํ์ธํด๋ณด๊ฒ ๋ค.
# main.tf
variable "sensitive_content" {
default = "secret"
sensitive = true
}
resource "**local**_**file**" "foo" {
content = upper(var.sensitive_content)
filename = "${path.module}/foo.bar"
**provisioner "local-exec" {
command = "echo The content is ${self.content}"
}
provisioner "local-exec" {
command = "abc"
on_failure = continue
}
provisioner "local-exec" {
when = destroy
command = "echo The deleting filename is ${self.filename}"
}**
}
local_file์ content์ ์ง์ ํ var.sensitive_content ๋ณ์์ ์์ฑ์ด ๋ฏผ๊ฐํ๋ค๊ณ ์ ์ธ ๋์๊ธฐ ๋๋ฌธ์ ํ๋ฉด์ ์ถ๋ ฅ๋์ง ์๋๋ค.
๋ ๋ฒ์งธ ํ๋ก๋ฐ์ด๋์์๋ abc๋ผ๋ ์ปค๋งจ๋๋ฅผ ์ํํ๋ค. ์๋ง ์ด๋ฐ ์ปค๋งจ๋๋ ์์ ๊ฒ์ด๋ฏ๋ก, apply ๋์์ด ์คํจํด์ผ ํ๋ค.
ํ์ง๋ง on_failure = continue ์ ์ธ์ด ์์ผ๋ฏ๋ก ์คํจ ์์๋ ๋ค์ ๋จ๊ณ๋ก ๋์ด๊ฐ๋ค.
on_failure = continue ์ ์ธ์ ์ฃผ์์ฒ๋ฆฌํ๊ณ ์งํํด๋ณด๊ฒ ๋ค.
# main.tf
variable "sensitive_content" {
default = "secret"
**sensitive = true**
}
resource "local_file" "foo" {
content = upper(var.sensitive_content)
filename = "${path.module}/foo.bar"
provisioner "local-exec" {
command = "echo The content is ${self.content}"
}
provisioner "local-exec" {
command = "abc"
**#on_failure = continue**
}
provisioner "local-exec" {
when = destroy
command = "echo The deleting filename is ${self.filename}"
}
}
์คํ ์คํจ ์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ณ ์ข ๋ฃ๋๋ค.
1.2 local-exec ํ๋ก๋น์ ๋
local-exec๋ ํ ๋ผํผ์ด ์คํ๋๋ ํ๊ฒฝ์์ ์ํํ ์ปค๋งจ๋๋ฅผ ์ ์ํ๋ค. ์ด๋ ๋ฆฌ๋ ์ค๋ ์๋์ฐ ๋ฑ ํ ๋ผํผ์ ์คํํ๋ ํ๊ฒฝ์ ๋ง๊ฒ ์ค์ ์ปค๋งจ๋๋ฅผ ์ ์ํด์ผ ํ๋ค.
์ฌ์ฉํ๋ ์ธ์ ๊ฐ์ ๋ค์๊ณผ ๊ฐ๋ค.
- command(ํ์) : ์คํํ ๋ช ๋ น์ค์ ์ ๋ ฅํ๋ฉฐ << ์ฐ์ฐ์๋ฅผ ํตํด ์ฌ๋ฌ ์ค์ ์ปค๋งจ๋ ์ ๋ ฅ ๊ฐ๋ฅ
- working_dir(์ ํ) : command์ ๋ช ๋ น์ ์คํํ ๋๋ ํฐ๋ฆฌ๋ฅผ ์ง์ ํด์ผ ํ๊ณ ์๋/์ ๋ ๊ฒฝ๋ก๋ก ์ค์
- interpreter(์ ํ) : ๋ช ๋ น์ ์คํํ๋ ๋ฐ ํ์ํ ์ธํฐํ๋ฆฌํฐ๋ฅผ ์ง์ ํ๋ฉฐ, ์ฒซ ๋ฒ์งธ ์ธ์๋ก ์ธํฐํ๋ฆฌํฐ ์ด๋ฆ์ด๊ณ ๋ ๋ฒ์งธ๋ถํฐ๋ ์ธํฐํ๋ฆฌํฐ ์ธ์ ๊ฐ
- environment(์ ํ) : ์คํ ์ ํ๊ฒฝ ๋ณ์ ๋ ์คํ ํ๊ฒฝ์ ๊ฐ์ ์์๋ฐ์ผ๋ฉด, ์ถ๊ฐ ๋๋ ์ฌํ ๋นํ๋ ค๋ ๊ฒฝ์ฐ ํด๋น ์ธ์์ key = value ํํ๋ก ์ค์
์์ ์ฝ๋
Unix/Linux/macOS | Windows |
resource "null_resource" "example1" { provisioner "local-exec" { command = <<EOF echo Hello!! > file.txt echo $ENV >> file.txt EOF interpreter = [ "bash" , "-c" ] working_dir = "/tmp" environment = { ENV = "world!!" } } } |
resource "null_resource" "example1" { provisioner "local-exec" { command = <<EOF Hello!! > file.txt Get-ChildItem Env:ENV >> file.txt EOF interpreter = [ "PowerShell" , "-Command" ] working_dir = "C:\\windows\temp" environment = { ENV = "world!!" } } } |
command์ << ์ฐ์ฐ์๋ฅผ ํตํด๋ค์ค ๋ผ์ธ์ ๋ช ๋ น์ ์ํํ๋ฉฐ ๊ฐ ํ๊ฒฝ์ ๋ง๋ ์ธํฐํ๋ฆฌํฐ๋ฅผ ์ง์ ํด ํด๋น ๋ช ๋ น์ ์ํํ๋ค. Apply ์ํ ์ ์ด ๋ช ๋ น์ ์คํ ์์น๋ฅผ working_dir๋ฅผ ์ฌ์ฉํด ์ง์ ํ๊ณ command์์ ์ฌ์ฉํ๋ ํ๊ฒฝ ๋ณ์์ ๋ํด environment์์ ์ง์ ํ๋ค.
(apply๋ฅผ ์ํํ๋ฉด working_dir ์์น์ file.txt์ ๊ธฐ๋ก๋ ๋ด์ฉ์ ํ์ธํ ์ ์๋ค.)
# main.tf
resource "**null**_resource" "example1" {
provisioner "local-exec" {
command = <<EOF
echo Hello!! > file.txt
echo $ENV >> file.txt
EOF
**interpreter** = [ "bash" , "-c" ]
**working_dir** = "/tmp"
**environment** = {
ENV = "world!!"
}
}
}
1.3 ์๊ฒฉ์ง ์ฐ๊ฒฐ
remote-exec์ file ํ๋ก๋น์ ๋๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ ์๊ฒฉ์ง์ ์ฐ๊ฒฐํ SSH, WinRM ์ฐ๊ฒฐ ์ ์๊ฐ ํ์ํ๋ค.
connection ๋ธ๋ก ๋ฆฌ์์ค ์ ์ธ ์, ํด๋น ๋ฆฌ์์ค ๋ด์ ๊ตฌ์ฑ๋ ํ๋ก๋น์ ๋์ ๋ํด ๊ณตํต์ผ๋ก ์ ์ธ๋๊ณ , ํ๋ก๋น์ ๋ ๋ด์ ์ ์ธ๋๋ ๊ฒฝ์ฐ, ํด๋น ํ๋ก๋น์ ๋์์๋ง ์ ์ฉ๋๋ค.
# main.tf
resource "null_resource" "example1" {
**connection {
type = "ssh"
user = "root"
password = var.root_password
host = var.host
}**
provisioner "file" {
source = "conf/myapp.conf"
destination = "/etc/myapp.conf"
}
provisioner "file" {
source = "conf/myapp.conf"
destination = "C:/App/myapp.conf"
**connection {
type = "winrm"
user = "Administrator"
password = var.admin_password
host = var.host
}**
}
}
- connection ์ ์ฉ ์ธ์์ ์ค๋ช - ๋งํฌ
์๊ฒฉ์ฐ๊ฒฐ์ด ์๊ตฌ๋๋ ํ๋ก๋น์ ๋์ ๊ฒฝ์ฐ ์คํฌ๋ฆฝํธ ํ์ผ์ ์๊ฒฉ ์์คํ ์ ์ ๋ก๋ํด ํด๋น ์์คํ ์ ๊ธฐ๋ณธ ์์์์คํํ๋๋ก ํ๋ฏ๋ก script_path์ ๊ฒฝ์ฐ ์ ์ ํ ์์น๋ฅผ ์ง์ ํ๋๋ก ํ๋ค. ๊ฒฝ๋ก๋ ๋์์ธ %RAND% ๊ฒฝ๋ก๊ฐ ํฌํจ๋์ด ์์ฑ๋๋ค.
- Unix/Linux/macOS : /tmp/terraform_%RAND%.sh
- Windows(cmd) : C:/windows/temp/terraform_%RAND%.cmd
- Windows(PowerShell) : C:/windows/temp/terraform_%RAND%.ps1
๋ฒ ์ค์ฒ ํธ์คํธ๋ฅผ ํตํด ์ฐ๊ฒฐํ๋ ๊ฒฝ์ฐ ๊ด๋ จ ์ธ์๋ฅผ ์ง์ํ๋ค - ๋งํฌ
1.4 file ํ๋ก๋น์ ๋
file ํ๋ก๋น์ ๋๋ ํ ๋ผํผ์ ์คํํ๋ ์์คํ ์์ ์ฐ๊ฒฐ ๋์์ผ๋ก ํ์ผ ๋๋ ๋๋ ํฐ๋ฆฌ๋ฅผ ๋ณต์ฌํ๋๋ฐ ์ฌ์ฉ๋๋ค. ์ฌ์ฉ๋๋ ์ธ์๋ ๋ค์๊ณผ ๊ฐ๋ค.
- source : ์์ค ํ์ผ ๋๋ ๋๋ ํฐ๋ฆฌ๋ก, ํ์ฌ ์์ ์ค์ธ ๋๋ ํฐ๋ฆฌ์ ๋ํ ์ํ ๊ฒฝ๋ก ๋๋ ์ ๋ ๊ฒฝ๋ก๋ก ์ง์ ํ ์ ์๋ค. content์ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
- content : ์ฐ๊ฒฐ ๋์์ ๋ณต์ฌํ ๋ด์ฉ์ ์ ์ํ๋ฉฐ ๋์์ด ๋๋ ํฐ๋ฆฌ์ธ ๊ฒฝ์ฐ tf-file-content ํ์ผ์ด ์์ฑ๋๊ณ , ํ์ผ์ธ ๊ฒฝ์ฐ ํด๋น ํ์ผ์ ๋ด์ฉ์ด ๊ธฐ๋ก๋๋ค. source์ ํจ๊ป ์ฌ์ฉํ ์ ์๋ค.
- destination : ํ์ ํญ๋ชฉ์ผ๋ก ํญ์ ์ ๋ ๊ฒฝ๋ก๋ก ์ง์ ๋์ด์ผ ํ๋ฉฐ, ํ์ผ ๋๋ ๋๋ ํฐ๋ฆฌ๋ค.
destination ์ง์ ์ ์ฃผ์ํด์ผ ํ ์ ์ ssh ์ฐ๊ฒฐ์ ๊ฒฝ์ฐ ๋์ ๋๋ ํฐ๋ฆฌ๊ฐ ์กด์ฌํด์ผ ํ๋ฉฐ winrm ์ฐ๊ฒฐ์ ๋๋ ํฐ๋ฆฌ๊ฐ ์๋ ๊ฒฝ์ฐ ์๋์ผ๋ก ์์ฑํ๋ค๋ ๊ฒ์ด๋ค.
๋๋ ํฐ๋ฆฌ๋ฅผ ๋์์ผ๋ก ํ๋ ๊ฒฝ์ฐ์๋ source ๊ฒฝ๋ก ํํ์ ๋ฐ๋ผ ๋์์ ์ฐจ์ด๊ฐ ์๊ธด๋ค. destination์ด /tmp ์ธ ๊ฒฝ์ฐ source๊ฐ ๋๋ ํฐ๋ฆฌ๋ก /foo ์ฒ๋ผ ๋ง์ง๋ง์ / ๊ฐ ์๋ ๊ฒฝ์ฐ ๋์ ๋๋ ํฐ๋ฆฌ์ ์ง์ ํ ๋๋ ํฐ๋ฆฌ๊ฐ ์ ๋ก๋๋์ด ์ฐ๊ฒฐ๋ ์์คํ ์ /tmp/foo ๋๋ ํฐ๋ฆฌ๊ฐ ์ ๋ก๋๋๋ค.
source๊ฐ ๋๋ ํฐ๋ฆฌ๋ก /foo/ ์ฒ๋ผ ๋ง์ง๋ง์ /๊ฐ ํฌํจ๋๋ ๊ฒฝ์ฐ source ๋๋ ํฐ๋ฆฌ ๋ด์ ํ์ผ๋ง /tmp ๋๋ ํฐ๋ฆฌ์ ์ ๋ก๋๋๋ค.
file ํ๋ก๋น์ ๋ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
resource "null_resource" "foo" {
# myapp.conf ํ์ผ์ด /etc/myapp.conf ๋ก ์
๋ก๋
provisioner "file" {
source = "conf/myapp.conf"
destination = "/etc/myapp.conf"
}
# content์ ๋ด์ฉ์ด /tmp/file.log ํ์ผ๋ก ์์ฑ
provisioner "file" {
content = "ami used: ${self.ami}"
destination = "/tmp/file.log"
}
# configs.d ๋๋ ํฐ๋ฆฌ๊ฐ /etc/configs.d ๋ก ์
๋ก๋
provisioner "file" {
source = "conf/configs.d"
destination = "/etc"
}
# apps/app1 ๋๋ ํฐ๋ฆฌ ๋ด์ ํ์ผ๋ค๋ง D:/IIS/webapp1 ๋๋ ํฐ๋ฆฌ ๋ด์ ์
๋ก๋
provisioner "file" {
source = "apps/app1/"
destination = "D:/IIS/webapp1"
}
}
1.5 remote-exec ํ๋ก๋น์ ๋
remote-exec๋ ์๊ฒฉ์ง ํ๊ฒฝ์์ ์คํํ ์ปค๋งจ๋์ ์คํฌ๋ฆฝํธ๋ฅผ ์ ์ํ๋ค. ์๋ฅผ ๋ค๋ฉด AWS์ EC2 ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ณ ํด๋น VM ์์ ๋ช ๋ น์ ์คํํ๊ณ ํจํค์ง๋ฅผ ์ค์นํ๋ ๋ฑ์ ๋์์ ์๋ฏธํ๋ค
์ธ์๋ ์๋ก ๋ฐฐํ์ ์ด๋ฉฐ, ์ฌ์ฉ๋๋ ์ธ์๋ ๋ค์๊ณผ ๊ฐ๋ค.
- inline : ๋ช ๋ น์ ๋ํ ๋ชฉ๋ก์ผ๋ก [ ] ๋ธ๋ก ๋ด์ “ “๋ก ๋ฌถ์ธ ๋ค์์ ๋ช ๋ น์ , ๋ก ๊ตฌ๋ถํด ๊ตฌ์ฑํ๋ค.
- script : ๋ก์ปฌ์ ์คํฌ๋ฆฝํธ ๊ฒฝ๋ก๋ฅผ ๋ฃ๊ณ ์๊ฒฉ์ ๋ณต์ฌํด ์คํํ๋ค.
- scripts : ๋ก์ปฌ์ ์คํฌ๋ฆฝํธ ๊ฒฝ๋ก์ ๋ชฉ๋ก์ผ๋ก [ ] ๋ธ๋ก ๋ด์ “ “๋ก ๋ฌถ์ธ ๋ค์์ ์คํฌ๋ฆฝํธ ๊ฒฝ๋ก๋ฅผ , ๋ก ๊ตฌ๋ถํด ๊ตฌ์ฑํ๋ค
script ๋๋ scripts์ ๋์ ์คํฌ๋ฆฝํธ ์คํ์ ํ์ํ ์ธ์๋ ๊ด๋ จ ๊ตฌ์ฑ์์ ์ ์ธํ ์ ์์ผ๋ฏ๋ก ํ์ํ ๋ file ํ๋ก๋ฐ์ด๋๋ก ํด๋น ์คํฌ๋ฆฝํธ๋ฅผ ์ ๋ก๋ํ๊ณ inline ์ธ์๋ฅผ ํ์ฉํด ์คํฌ๋ฆฝํธ์ ์ธ์๋ฅผ ์ถ๊ฐํ๋ค.
file ํ๋ก๋น์ ๋ ๊ตฌ์ฑ์ ์์๋ ๋ค์๊ณผ ๊ฐ๋ค.
resource "aws_instance" "web" {
# ...
# Establishes connection to be used by all
# generic remote provisioners (i.e. file/remote-exec)
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",
]
}
}
2. null_resource์ terraform_data
ํ ๋ผํผ 1.4 ๋ฒ์ ์ด ๋ฆด๋ฆฌ์ค๋๋ฉด์ ๊ธฐ์กด null_resource ๋ฆฌ์์ค๋ฅผ ๋์ฒดํ๋ terraform_data ๋ฆฌ์์ค๊ฐ ์ถ๊ฐ๋์๋ค. ๋ ๋ฆฌ์์ค์ ์๋ฏธ์ ์ฌ์ฉ ๋ฐฉ์์ ํ์ธํด ๋ณด๊ฒ ๋ค.
2.1 null_resource
null_resource๋ ์ด๋ฆ์ฒ๋ผ ์๋ฌด ์์ ๋ ์ํํ์ง ์๋ ๋ฆฌ์์ค๋ฅผ ๊ตฌํํ๋ค.
์ด๋ฐ ๋ฆฌ์์ค๊ฐ ํ์ํ ์ด์ ๋ ํ ๋ผํผ ํ๋ก๋น์ ๋ ๋์์ ์ค๊ณํ๋ฉด์ ์ฌ์ฉ์๊ฐ ์๋์ ์ผ๋ก ํ๋ก๋น์ ๋ํ๋ ๋์์ ์กฐ์จํด์ผ ํ๋ ์ํฉ์ด ๋ฐ์ํ๋ฉฐ ํ๋ก๋ฐ์ด๋๊ฐ ์ ๊ณตํ๋ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ๋ง์ผ๋ก๋ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ด๋ ต๊ธฐ ๋๋ฌธ์ด๋ค.
์ฃผ๋ก ์ฌ์ฉ๋๋ ์๋๋ฆฌ์ค๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ํ๋ก๋น์ ๋ ์ํ ๊ณผ์ ์์ ๋ช ๋ น์ด ์คํ
- ํ๋ก๋น์ ๋์ ํจ๊ป ์ฌ์ฉ
- ๋ชจ๋, ๋ฐ๋ณต๋ฌธ, ๋ฐ์ดํฐ ์์ค, ๋ก์ปฌ ๋ณ์์ ํจ๊ป ์ฌ์ฉ
- ์ถ๋ ฅ์ ์ํ ๋ฐ์ดํฐ ๊ฐ๊ณต
์๋ฅผ ๋ค์ด ๋ค์๊ณผ ๊ฐ์ ์ํฉ์ ๊ฐ์ ํด๋ณด๊ฒ ๋ค.
- AWS EC2 ์ธ์คํด์ค๋ฅผ ํ๋ก๋น์ ๋ํ๋ฉด์ ์น์๋น์ค๋ฅผ ์คํ์ํค๊ณ ์ถ๋ค
- ์น์๋น์ค ์ค์ ์๋ ๋ ธ์ถ๋์ด์ผ ํ๋ ๊ณ ์ ๋ ์ธ๋ถ IP๊ฐ ํฌํจ๋ ๊ตฌ์ฑ์ด ํ์ํ๋ค. ๋ฐ๋ผ์ aws_eip ๋ฆฌ์์ค๋ฅผ ์์ฑํด์ผ ํ๋ค.
AWS EC2 ์ธ์คํด์ค๋ฅผ ํ๋ก๋น์ ๋ํ๊ธฐ ์ํด aws_instance ๋ฆฌ์์ค ๊ตฌ์ฑ ์ ์์ ํ์ธํ ํ๋ก๋น์ ๋๋ฅผ ํ์ํ์ฌ ์น์๋น์ค๋ฅผ ์คํํ๊ณ ์ ํ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ฝ๋ ๊ตฌ์ฑ์ด ์์ฑ๋๋ค.
๋ค์์ aws_instance์์ aws_eip์ public ip๋ฅผ ์ฌ์ฉํ๋ ค๋ ์์์ด๋ค.
# main.tf
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" # ๊ฐ์ default VPC์ subnet ID ์๋ฌด๊ฑฐ๋
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"
}
provisioner "remote-exec" {
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"
}
aws_eip๊ฐ ์์ฑํ๋ ๊ณ ์ IP๋ฅผ ํ ๋นํ๊ธฐ ์ํด์๋ ๋์์ธ aws_instance์ id๊ฐ์ด ํ์ํ๋ค.
aws_instance์ ํ๋ก๋น์ ๋ ๋์์์๋ aws_eip๊ฐ ์์ฑํ๋ ์์ฑ ๊ฐ์ธ public_ip๊ฐ ํ์ํ๋ค.
ํ ๋ผํผ ๊ตฌ์ฑ ์ ์์์ ์ํธ ์ฐธ์กฐ๊ฐ ๋ฐ์ํ๋ ์ํฉ์ผ๋ก, ์ค์ ์คํ๋๋ ์ฝ๋๋ฅผ ์์ฑํ์ฌ terraform plan์ ์ํํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์ํธ ์ฐธ์กฐ๋๋ ์ข ์์ฑ์ ๋๊ธฐ ์ํด์๋ ๋ ์ค ํ๋์ ์คํ ์์ ์ ํ ๋จ๊ณ ๋ค๋ก ๋ฏธ๋ค์ผ ํ๋ค.
์ด๋ฐ ๊ฒฝ์ฐ ์คํ์ ๊ฐ๊ฒฉ์ ์ถ๊ฐํ์ฌ ์ค์ ๋ฆฌ์์ค์๋ ๋ฌด๊ดํ ๋์์ ์ํํ๊ธฐ ์ํด null_resource๋ฅผ ํ์ฉํ๋ค.
๋ค์๊ณผ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ ํ๊ณ ์คํํด๋ณด๊ฒ ๋ค.
# main.tf
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.0.100"
key_name = "devwoos"
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"
}
}
resource "aws_eip" "myeip" {
#vpc = true
instance = aws_instance.example.id
associate_with_private_ip = "172.31.0.100"
}
resource "null_resource" "echomyeip" {
provisioner "remote-exec" {
connection {
host = aws_eip.myeip.public_ip
type = "ssh"
user = "ubuntu"
private_key = file("/Users/chanw/.ssh/devwoos.pem")
#password = "qwe123"
}
inline = [
"echo ${aws_eip.myeip.public_ip}"
]
}
}
output "public_ip" {
value = aws_instance.example.public_ip
description = "The public IP of the Instance"
}
output "eip" {
value = aws_eip.myeip.public_ip
description = "The EIP of the Instance"
}
null_resource๋ก ์ธํด SSH ์ ๊ทผ์ ๊ณ์ ์๋ํ๋ค ์ ์์ ์ผ๋ก check ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์์ฑ๋ EC2์์ 80port๋ก ์ ๊ทผ ๊ฐ๋ฅํ html ๋ด์ฉ๋ ์ ์์ ์ผ๋ก ๋ํ๋๋ค.
null_resource๋ ์ ์๋ ์์ฑ์ด ‘id’๊ฐ ์ ๋ถ์ด๋ฏ๋ก, ์ ์ธ๋ ๋ด๋ถ์ ๊ตฌ์ฑ์ด ๋ณ๊ฒฝ๋๋๋ผ๋ ์๋ก์ด plan ๊ณผ์ ์์ ์คํ ๊ณํ์ ํฌํจ๋์ง ๋ชปํ๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์๊ฐ null_resource์ ์ ์๋ ๋ด์ฉ์ ๊ฐ์ ๋ก ๋ค์ ์คํํ๊ธฐ ์ํ ์ธ์๋ก trigger๊ฐ ์ ๊ณต๋๋ค.
trigger๋ ์์์ string ํํ์ map ๋ฐ์ดํฐ๋ฅผ ์ ์ํ๋๋ฐ, ์ ์๋ ๊ฐ์ด ๋ณ๊ฒฝ๋๋ฉด null_resource ๋ด๋ถ์ ์ ์๋ ํ์๋ฅผ ๋ค์ ์คํํ๋ค.
trigger ์ ์์ ๋์ ์์ ๋ฅผ ํ์ธํด๋ณด๊ฒ ๋ค.
resource "null_resource" "foo" {
triggers = {
ec2_id = aws_instance.bar.id # instance์ id๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒฝ์ฐ ์ฌ์คํ
}
...์๋ต...
}
resource "null_resource" "bar" {
triggers = {
ec2_id = time() # ํ
๋ผํผ์ผ๋ก ์คํ ๊ณํ์ ์์ฑํ ๋๋ง๋ค ์ฌ์คํ
}
...์๋ต...
}
2.2 terraform_data
ํ ๋ผํผ 1.4 ๋ฒ์ ์์๋ ๊ธฐ์กด null_resource์ ๊ธฐ๋ฅ์ ์ธ ์์๋ฅผ ๋์ฒดํ๊ธฐ ์ํด terraform_data ๋ฆฌ์์ค๊ฐ ์ถ๊ฐ๋์๋ค.(์ฆ..null_resource๋ ์ ์์ด๋ค..)
์ด ๋ฆฌ์์ค ๋ํ ์์ฒด์ ์ผ๋ก ์๋ฌด๊ฒ๋ ์ํํ์ง ์์ง๋ง null_resource๋ ๋ณ๋์ ํ๋ก๋ฐ์ด๋ ๊ตฌ์ฑ์ด ํ์ํ๋ค๋ ์ ๊ณผ ๋น๊ตํ์ฌ ์ถ๊ฐ ํ๋ก๋ฐ์ด๋ ์์ด ํ ๋ผํผ ์์ฒด์ ํฌํจ๋ ๊ธฐ๋ณธ ์๋ช ์ฃผ๊ธฐ ๊ด๋ฆฌ์๊ฐ ์ ๊ณต๋๋ค๋ ๊ฒ์ด ์ฅ์ ์ด๋ค.
์ฌ์ฉ ์๋๋ฆฌ์ค๋ ๊ธฐ์กด null_resource์ ๋์ผํ๋ฉฐ ๊ฐ์ ์ฌ์คํ์ ์ํ triggers_replace์ ์ํ ์ ์ฅ์ ์ํ input ์ธ์์ input์ ์ ์ฅ๋ ๊ฐ์ ์ถ๋ ฅํ๋ output ์์ฑ์ด ์ ๊ณต๋๋ค.
triggers_replace์ ์ ์๋๋ ๊ฐ์ด ๊ธฐ์กด map ํํ์์ tuple๋ก ๋ณ๊ฒฝ๋์ด ์ฐ์์ด ๋ ๊ฐ๋จํด์ก๋ค.
terraform_data ๋ฆฌ์์ค์ trigget_replace ์ ์์ ๋์ ์์ ๋ฅผ ํ์ธํด๋ณด๊ฒ ๋ค.
resource "aws_instance" "web" {
# ...
}
resource "aws_instance" "database" {
# ...
}
# A use-case for terraform_data is as a do-nothing container
# for arbitrary actions taken by a provisioner.
resource "**terraform_data**" "bootstrap" {
**triggers_replace** = [
aws_instance.web.id,
aws_instance.database.id
]
**provisioner** "local-exec" {
command = "bootstrap-hosts.sh"
}
}
resource "**terraform_data**" "foo" {
**triggers_replace** = [
aws_instance.foo.id,
aws_instance.bar.id
]
input = "world"
}
output "terraform_data_output" {
value = **terraform_data.foo.output** # ์ถ๋ ฅ ๊ฒฐ๊ณผ๋ "world"
}
3. moved ๋ธ๋ก
ํ ๋ผํผ์ state์ ๊ธฐ๋ก๋๋ ๋ฆฌ์์ค ์ฃผ์์ ์ด๋ฆ์ด ๋ณ๊ฒฝ๋๋ฉด ๊ธฐ์กด ๋ฆฌ์์ค๋ ์ญ์ ๋๊ณ ์๋ก์ด ๋ฆฌ์์ค๊ฐ ์์ฑ๋จ์ ์์ ์ค๋ช ์์ ํ์ธํ๋ค.
ํ์ง๋ง ํ ๋ผํผ ๋ฆฌ์์ค๋ฅผ ์ ์ธํ๋ค ๋ณด๋ฉด ์ด๋ฆ์ ๋ณ๊ฒฝํด์ผ ํ๋ ์ํฉ์ด ๋ฐ์ํ๊ธฐ๋ ํ๋๋ฐ, ์์๋ฅผ ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ํฉ์ด ์์ ์ ์๋ค.
- ๋ฆฌ์์ค ์ด๋ฆ์ ๋ณ๊ฒฝ
- count๋ก ์ฒ๋ฆฌํ๋ ๋ฐ๋ณต๋ฌธ์ for_each๋ก ๋ณ๊ฒฝ
- ๋ฆฌ์์ค๊ฐ ๋ชจ๋๋ก ์ด๋ํ์ฌ ์ฐธ์กฐ๋๋ ์ฃผ์๊ฐ ๋ณ๊ฒฝ
๋ฆฌ์์ค์ ์ด๋ฆ์ ๋ณ๊ฒฝ๋์ง๋ง ์ด๋ฏธ ํ ๋ผํผ์ผ๋ก ํ๋ก๋น์ ๋ ๋ ํ๊ฒฝ์ ๊ทธ๋๋ก ์ ์งํ๊ณ ์ ํ๋ ๊ฒฝ์ฐ ํ ๋ผํผ 1.1 ๋ฒ์ ๋ถํฐ moved ๋ธ๋ก์ ์ฌ์ฉํ ์ ์๋ค.
‘moved’ ๋จ์ด์ ๋ป์ฒ๋ผ ํ ๋ผํผ state์์ ์ฎ๊ฒจ์ง ๋์์ ์ด์ ์ฃผ์์์ ์ฃผ์๋ฅผ ์๋ฆฌ๋ ์ญํ ์ ์ํํ๋ค. moved ๋ธ๋ก ์ด์ ์๋ state๋ฅผ ์ง์ ํธ์งํ๋ terraform state mv ๋ช ๋ น์ ์ฌ์ฉํ์ฌ state๋ฅผ ์์ ํด์ผ ํ๋ ๋ถ๋ด์ด ์์๋ค๋ฉด, moved ๋ธ๋ก์ state์ ์ ๊ทผ ๊ถํ์ด ์๋ ์ฌ์ฉ์๋ผ๋ ๋ณ๊ฒฝ๋๋ ์ฃผ์๋ฅผ ๋ฆฌ์์ค ์ํฅ ์์ด ๋ฐ์ํ ์ ์๋ค.
ํ๋ฒ ๋ค์ ์ค์ต์ ํตํด ์์๋ค์ ํ์ธํด๋ณด๊ฒ ๋ค.
# main.tf
resource "local_file" "**a**" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.a.content
}
local_file ์ ์ด๋ฆ์ a์์ b๋ก ๋ณ๊ฒฝํด๋ณด๊ฒ ๋ค.
resource "local_file" "**b**" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.**b**.content
}
terraform plan ๊ฒฐ๊ณผ ๊ธฐ์กด ๋ฆฌ์์ค๋ฅผ ์ญ์ ํ๊ณ ์๋ก์ด ๋ฆฌ์์ค๋ฅผ ์์ฑํ๋ ค ๊ณํํ๋ค.
ํด๋น local_file.a์ ํ๋ก๋น์ ๋ ๊ฒฐ๊ณผ๋ฅผ ์ ์งํ ์ฑ ์ด๋ฆ๋ง์ ๋ณ๊ฒฝํ๊ธฐ ์ํด moved ๋ธ๋ก์ ์์ฑํด๋ณด๊ฒ ๋ค.
# main.tf
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
}
๋ค์๊ณผ ๊ฐ์ด ์ ๊ฑฐ๋ ์์ฑ์ด ์์ด ๋ณ๊ฒฝ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค!
4. CLI๋ฅผ ์ํ ์์คํ ํ๊ฒฝ ๋ณ์
ํ ๋ผํผ์ ํ๊ฒฝ ๋ณ์๋ฅผ ํตํด ์คํ ๋ฐฉ์๊ณผ ์ถ๋ ฅ ๋ด์ฉ์ ๋ํ ์ต์ ์ ์กฐ์ ํ ์ ์๋ค. ์์คํ ํ๊ฒฝ ๋ณ์๋ฅผ ์ค์ ํ๋ฉด, ์๊ตฌ์ ์ผ๋ก ๋ก์ปฌ ํ๊ฒฝ์ ์ ์ฉ๋๋ ์ต์ ์ด๋ ๋ณ๋ ์๋ฒ ํ๊ฒฝ์์ ์คํํ๊ธฐ ์ํ ์ต์ ์ ๋ถ์ฌํ ์ ์๋ค.
์ด๋ฅผ ํตํด ๋ก์ปฌ ์์ ํ๊ฒฝ๊ณผ ๋ค๋ฅธ ํ๊ฒฝ ๊ตฌ์ฑ์์๋ง ์ฌ์ฉ๋ ํน์ ์ต์ ์ ์ ์ฉํ๋ค.
- Mac/๋ฆฌ๋ ์ค/์ ๋์ค: export <ํ๊ฒฝ ๋ณ์ ์ด๋ฆ>=<๊ฐ>
- Windows CMD: set <ํ๊ฒฝ ๋ณ์ ์ด๋ฆ>=<๊ฐ>
- Windows PowerShell: $Env:<ํ๊ฒฝ ๋ณ์ ์ด๋ฆ>='<๊ฐ>'
4.1 TF_LOG
ํ ๋ผํผ์ stderr ๋ก๊ทธ์ ๋ํ ๋ ๋ฒจ์ ์ ์ํ๋ค.
trace, debug, info, warn, error, off๋ฅผ ์ค์ ํ ์ ์๊ณ ๊ด๋ จ ํ๊ฒฝ ๋ณ์๊ฐ ์๋ ๊ฒฝ์ฐ off์ ๋์ผํ๋ค.
๋๋ฒ๊น ์ ์ํ ๋ก๊ทธ ๊ด๋ จ ํ๊ฒฝ ๋ณ์ ์ค๋ช ์ ๋ค์๊ณผ ๊ฐ๋ค.
- TF_LOG: ๋ก๊น ๋ ๋ฒจ ์ง์ ๋๋ ํด์
- TF_LOG_PATH: ๋ก๊ทธ ์ถ๋ ฅ ํ์ผ ์์น ์ง์
- TF_LOG_CORE: TF_LOG์ ๋ณ๋๋ก ํ ๋ผํผ ์์ฒด ์ฝ์ด์ ๋ํ ๋ก๊น ๋ ๋ฒจ ์ง์ ๋๋ ํด์
- TF_LOG_PROVIDER: TF_LOG์ ๋ณ๋๋ก ํ ๋ผํผ์์ ์ฌ์ฉํ๋ ํ๋ก๋ฐ์ด๋์ ๋ํ ๋ก๊น ๋ ๋ฒจ ์ง์ ๋๋ ํด์
ํ๊ฒฝ์ ๋ง๊ฒ TF_LOG๋ฅผ info๋ก ์ค์ ํ๊ณ , terraform plan ๋์์ ์คํํ๋ฉด ํ ๋ผํผ ์ถ๋ ฅ์ ๊ด๋ จ ๋ก๊ทธ๊ฐ ์ถ๋ ฅ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
4.2 TF_INPUT
๊ฐ์ false ๋๋ 0์ผ๋ก ์ค์ ํ๋ฉด ํ ๋ผํผ ์คํ ์ ์ธ์์ -input=false๋ฅผ ์ถ๊ฐํ ๊ฒ๊ณผ ๋์ผํ ์ํ ๊ฒฐ๊ณผ๋ฅผ ํ์ธํ ์ ์๋ค.
ํ๊ฒฝ์ ๋ง๊ฒ TF_INPUT์ 0์ผ๋ก ์ค์ ํ๊ณ terraform plan ๋์์ ์คํํ๋ฉด ์ ๋ ฅ๋ฐ๋ ๋์์ ์ํํ์ง ์์ผ๋ฏ๋ก ์ ๋ ฅ ๋ณ์๋ฅผ ์ ๋ ฅํด์ผ ํ๋ ๊ฒฝ์ฐ ์๋ฌ๊ฐ ๋ฐ์ํ ์ ์๋ค.
**TF_INPUT=0 terraform plan**
Error : No value for required variable
4.3 TF_VAR_name
TF_VAR_<๋ณ์ ์ด๋ฆ>์ ์ฌ์ฉํ๋ฉด ์ ๋ ฅ ์ default๋ก ์ ์ธ๋ ๋ณ์ ๊ฐ์ ๋์ฒดํ๋ค.
(๋ณ์ ์ ๋ ฅ ๋ฐฉ์์ ์์ ์ ๋ ฅ ๋ณ์ ์ฑํฐ์์ ํ์ธํ๋ค.)
4.4 TF_CLI_ARGS / TF_CLI_ARGS_subcommand
ํ ๋ผํผ ์คํ ์ ์ถ๊ฐํ ์ธ์๋ฅผ ์ ์ํ๋ค.
TF_CLI_ARGS=”-input=false” terraform apply -auto-approve๋ terraform apply -input=false -auto-approve ์ ๊ฐ๋ค.
**TF_CLI_ARGS="-input=false" terraform apply -auto-approve**
Error: No value for required variable
TF_CLI_ARGS_apply ๋ก ์ธ์๋ฅผ ์ ์ํ๋ฉด terraform apply ์ปค๋งจ๋ ์ํ ์์๋ง ๋์ํ๋ค.
export TF_CLI_ARGS_apply="-input=false"
**terraform apply -auto-approve**
<์๋ฌ>
4.5 TF_DATA_DIR
state ์ ์ฅ ๋ฐฑ์๋ ์ค์ ๊ณผ ๊ฐ์ ์์ ๋๋ ํฐ๋ฆฌ๋ณ ๋ฐ์ดํฐ๋ฅผ ๋ณด๊ดํ๋ ์์น๋ฅผ ์ง์ ํ๋ค.
์ด ๋ฐ์ดํฐ๋ .terraform ๋๋ ํฐ๋ฆฌ ์์น์ ๊ธฐ๋ก๋์ง๋ง, TF_DATA_DIR์ ๊ฒฝ๋ก๊ฐ ์ ์๋๋ฉด ๊ธฐ๋ณธ ๊ฒฝ๋ก๋ฅผ ๋์ฒดํ์ฌ ์ฌ์ฉ๋๋ค.
์ผ๊ด๋ ํ ๋ผํผ ์ฌ์ฉ์ ์ํด์ ํด๋น ๋ณ์๋ ์คํ์๋ง๋ค ์ผ๊ด๋๊ฒ ์ ์ฉ๋ ์ ์๋๋ก ์ค์ ํ๋ ๊ฒ์ด ์ค์ํ๋ค.
์ค์ ๊ฐ์ด ์ด์ ์์๋ง ์ ์ฉ๋๋ ๊ฒฝ์ฐ init ๋ช ๋ น์ผ๋ก ์ํ๋ ๋ชจ๋, ์ํฐํฉํธ ๋ฑ์ ํ์ผ์ ์ฐพ์ง ๋ชปํ๋ค. ์ด๋ฏธ terraform init์ด ์ํ๋ ์ํ์์๋ TF_DATA_DIR๋ก ๊ฒฝ๋ก๋ฅผ ์ฌ์ง์ ํ๊ณ ์คํํ๋ ๊ฒฝ์ฐ ํ๋ฌ๊ทธ์ธ ์ค์น๊ฐ ํ์ํ๋ค๋ ๋ฉ์์ง ์ถ๋ ฅ์ ํ์ธํ ์ ์๋ค.
**TF_DATA_DIR=./.terraform_tmp terraform plan**
Error: Required plugins anr not installed