<참고> 테라폼으로 시작하는 IaC - 김민수, 김재준, 이규석, 이유종 저 한빛미디어
모든 실습은 Mac OS 기준으로 작성되었습니다
3.10 조건문
- 테라폼에서의 조건식은 3항 연산자 형태를 갖는다.
- 조건은 true 또는 false로 확인되는 모든 표현식을 사용할 수 있다
- 일반적으로 비교, 논리 연산자를 사용해 조건을 확인한다.
# <조건 정의> ? <옳은 경우> : <틀린 경우>
var.a != "" ? var.a : "default-a"
# var.a가 빈 문자열이 아니라면 var.a를, 비어있을때는 "default-a"를 반환한다.
- 조건식의 각 조건은 비교 대상의 형태가 다르면 테라폼 실행시 조건 비교를 위해 형태를 추론하여 자동변환
- 이때 협업자는 작업자 사이에 의미 전달이 명확하지 않아 혼란을 겪을 수 있으므로 명시적인 형태로 작성하기를 권장.
var.example ? 12 : "hello" # 비권장
var.example ? "12" : "hello" # 권장
var.example ? tostring(12) : "hello" # 권장
- 조건식은 단순히 특정 속성에 대한 정의, 로컬 변수에 대한 재정의, 출력 값에 대한 조건 정의 뿐만 아니라 리소스 생성 여부에 응용할 수 있다.
-count에 조건식을 결합한 경우 다음과 같이 특정 조건에 따라 리소스 생성 여부를 선택할 수 있다.
#main.tf
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 : ""
}
# 변수 우선순위3 : 환경 변수 (TF_VAR 변수 이름)
$ 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 = ""
...
$ terraform state list
# 환경 변수 삭제
$ unset TF_VAR_enable_file
$ export | grep TF_VAR_enable_file
# 재실행
$ terraform plan && terraform apply -auto-approve
...
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
content = "foo!"
...
terraform state list
local_file.foo[0]
#
$ echo "local_file.foo[0]" | terraform console
$ echo "local_file.foo[0].content" | terraform console
"foo!"
3.11 함수
- 테라폼은 프로그래밍 언어적인 특성을 가지고 있어서 값의 유형을 변경하거나 조합할 수 있는 내장 함수를 사용할 수 있다.
- 단, 내장된 함수 외에 사용자가 구현하는 별도의 사용자 정의 함수를 지원하지는 않는다
- 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형변환이있다.
- 테라폼 코드에 함수를 적용하면 변수,리소스속성,데이터소스 속성, 출력값 표현 시 작업을 동적이고 효과적으로 수행 할 수있다.
# main.tf
resource "local_file" "foo" {
content = upper("foo! bar!")
filename = "${path.module}/foo.bar"
}
#실행
$ terraform init && terraform plan && terraform apply -auto-approve
$ cat foo.bar ; echo
FOO! BAR!
# 내장 함수 간단 사용
$ terraform console
> upper("foo!")
"FOO!"
> max(5, 12, 9)
12
> lower(local_file.foo.content)
"foo! bar!"
> upper(local_file.foo.content)
"FOO! BAR!"
> cidrnetmask("172.16.0.0/12")
"255.240.0.0"
> exit
3.12 프로비저너
- 프로바이더와 비슷하게 '제공자'로 해석되나, 프로바이더로 실행되지 않는 커맨드와 파일 복사 같은 역할을 수행한다.
- ex) 클라우드에 리눅스 VM 또는 AWS EC2 생성 후 특정 패키지를 설치해야 하거나 파일을 생성해야 하는 경우 이것들은 테라폼 구성과는 별개로 동작해야 한다.
- 프로비저너로 실행된 결과는 테라폼의 상태 파일과 동기화되지 않으므로 프로비저닝에 대한 결과가 항상 같다고 보장할 수 없다.
- 따라서 프로비저너 사용을 최소하 하는 것이 좋다.
- 프로비저너의 종류에는 파일 복사 명령어 실행을 위한 file, local-exec, remote-exec 가 있다.
3.12.1 프로비저너 사용 방법
- 프로비저너의 경우 리소스 프로비저닝 이후 동작하도록 구성할 수 있다.
- ex) 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}"
}
}
# 실행
$ terraform init && terraform plan
#
terraform apply -auto-approve
...
Plan: 1 to add, 0 to change, 0 to destroy.
local_file.foo: Creating...
local_file.foo: Provisioning with 'local-exec'...
local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "echo The content is SECRET"]
local_file.foo (local-exec): The content is SECRET
local_file.foo: Provisioning with 'local-exec'...
local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "abc"]
local_file.foo (local-exec): /bin/sh: abc: command not found
local_file.foo: Creation complete after 0s [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
# 테라폼 상태에 프로비저너 정보(실행 및 결과)가 없다
$ terraform state list
local_file.foo
$ terraform state show local_file.foo
# local_file.foo:
resource "local_file" "foo" {
content = "SECRET"
content_base64sha256 = "CRexOpCRkV1UtjNvRZCVOczkUrNmGyHzhkGKJXiDswo="
content_base64sha512 = "/rZUHUkqHVA5TMRI6cTQisOBxckKZWsZIBus/flGK4eopVeaR4EGCcIwfeyS9SyI8hj9MHWv4CYpvF/QHOc0/Q=="
content_md5 = "44c7be48226ebad5dca8216674cad62b"
content_sha1 = "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1"
content_sha256 = "0917b13a9091915d54b6336f45909539cce452b3661b21f386418a257883b30a"
content_sha512 = "feb6541d492a1d50394cc448e9c4d08ac381c5c90a656b19201bacfdf9462b87a8a5579a47810609c2307dec92f52c88f218fd3075afe02629bc5fd01ce734fd"
directory_permission = "0777"
file_permission = "0777"
filename = "./foo.bar"
id = "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1"
}
$ cat foo.bar ; echo
SECRET
$ cat terraform.tfstate | jq
{
"version": 4,
"terraform_version": "1.5.2",
"serial": 1,
"lineage": "3ceb706c-97cf-e8e8-60bf-ff601a3d9fa2",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "local_file",
"name": "foo",
"provider": "provider[\"registry.terraform.io/hashicorp/local\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"content": "SECRET",
"content_base64": null,
"content_base64sha256": "CRexOpCRkV1UtjNvRZCVOczkUrNmGyHzhkGKJXiDswo=",
"content_base64sha512": "/rZUHUkqHVA5TMRI6cTQisOBxckKZWsZIBus/flGK4eopVeaR4EGCcIwfeyS9SyI8hj9MHWv4CYpvF/QHOc0/Q==",
"content_md5": "44c7be48226ebad5dca8216674cad62b",
"content_sha1": "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1",
"content_sha256": "0917b13a9091915d54b6336f45909539cce452b3661b21f386418a257883b30a",
"content_sha512": "feb6541d492a1d50394cc448e9c4d08ac381c5c90a656b19201bacfdf9462b87a8a5579a47810609c2307dec92f52c88f218fd3075afe02629bc5fd01ce734fd",
"directory_permission": "0777",
"file_permission": "0777",
"filename": "./foo.bar",
"id": "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1",
"sensitive_content": null,
"source": null
},
"sensitive_attributes": []
}
]
}
],
"check_results": null
}
# graph 확인 : 프로비저너 정보(의존성)이 없다
$ terraform graph > graph.dot
# 삭제
$ terraform destroy -auto-approve
local_file.foo: Refreshing state... [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# local_file.foo will be destroyed
- resource "local_file" "foo" {
- content = "SECRET" -> null
- content_base64sha256 = "CRexOpCRkV1UtjNvRZCVOczkUrNmGyHzhkGKJXiDswo=" -> null
- content_base64sha512 = "/rZUHUkqHVA5TMRI6cTQisOBxckKZWsZIBus/flGK4eopVeaR4EGCcIwfeyS9SyI8hj9MHWv4CYpvF/QHOc0/Q==" -> null
- content_md5 = "44c7be48226ebad5dca8216674cad62b" -> null
- content_sha1 = "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1" -> null
- content_sha256 = "0917b13a9091915d54b6336f45909539cce452b3661b21f386418a257883b30a" -> null
- content_sha512 = "feb6541d492a1d50394cc448e9c4d08ac381c5c90a656b19201bacfdf9462b87a8a5579a47810609c2307dec92f52c88f218fd3075afe02629bc5fd01ce734fd" -> null
- directory_permission = "0777" -> null
- file_permission = "0777" -> null
- filename = "./foo.bar" -> null
- id = "3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1" -> null
}
Plan: 0 to add, 0 to change, 1 to destroy.
local_file.foo: Destroying... [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
local_file.foo: Provisioning with 'local-exec'...
local_file.foo (local-exec): Executing: ["/bin/sh" "-c" "echo The deleting filename is ./foo.bar"]
local_file.foo (local-exec): The deleting filename is ./foo.bar
local_file.foo: Destruction complete after 0s
Destroy complete! Resources: 1 destroyed.
# 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}"
}
}
# 실행
$ terraform apply -auto-approve
...
│ Error: local-exec provisioner error
│
│ with local_file.foo,
│ on main.tf line 14, in resource "local_file" "foo":
│ 14: provisioner "local-exec" {
│
│ Error running command 'abc': exit status 127. Output: /bin/sh: abc: command not found
...
# 커맨드를 찾을 수 없으므로 에러가 발생한다.
3.12.2 local-exec 프로비저너 - 테라폼이 실행되는 환경에서 수행할 커맨드를 정의
- 리눅스나 윈도우 등 테라폼을 실행하는 환경에 맞게 설정 커맨드를 정의해야 한다.
- 사용하는 인수 값
- command(필수) : 실행할 명령줄을 입력하며 << 연산자를 통해 여러 줄의 커맨드 입력 가능
- working_dir(선택) : command의 명령을 실행할 디렉터리를 지정해야 하고 상대/절대 경로로 설정
- interpreter(선택) : 명령을 실행하는 데 필요한 인터프리터를 지정하며, 첫 번째 인수로 인터프리터 이름이고 두 번째부터는 인터프리터 인수 값
- environment(선택) : 실행 시 환경 변수 는 실행 환경의 값을 상속받으면, 추가 또는 재할당하려는 경우 해당 인수에 key = value 형태로 설정
- ex) macOS 환경에 따라 각 인수 값을 활용한 테라폼 구성을 적용(아래 코드 참조)
- 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!!"
}
}
}
# 실행
$ terraform init -upgrade
$ terraform plan && terraform apply -auto-approve
...
local_file.foo: Destroying... [id=3c3b274d119ff5a5ec6c1e215c1cb794d9973ac1]
local_file.foo: Destruction complete after 0s
null_resource.example1: Creating...
null_resource.example1: Provisioning with 'local-exec'...
null_resource.example1 (local-exec): Executing: ["bash" "-c" " echo Hello!! > file.txt\n echo $ENV >> file.txt\n"]
null_resource.example1: Creation complete after 0s [id=5182470933108311782]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
$ terraform state list
null_resource.example1
$ terraform state show null_resource.example1
# null_resource.example1:
resource "null_resource" "example1" {
id = "5182470933108311782"
}
$ cat /tmp/file.txt
Hello!!
world!!
3.12.3 원격지 연결
- remote-exec와 file 프로비저너를 사용하기 위해서는 원격지에 연결할 SSH, WinRM 연결 정의가 필요하다.
# connection 블록으로 원격지 연결 정의
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 블록은 리소스에 선언되는 경우 해당 리소스 내에 구성된 프로비저너에 대해 공통으로 선언된다.
- connection 블록이 프로비저너 내에 선언되는 경우 해당 프로비저너에만 적용된다.
- 적용되는 인수와 설명
인수 | 연결타입 | 설명 | 기본값 |
type | SSH/WinRM | 연결 유형으로 ssh 또는 winrm | ssh |
user | SSH/WinRM | 연결에 사용되는 사용자 | ssh:root winrm:Administrator |
password | SSH/WinRM | 연결에 사용되는 비밀번호 | ssh: 22 winrm: 5985 |
host | SSH/WinRM | (필수) 연결 대상 주소 | 5m |
port | SSH/WinRM | 연결 대상의 타입별 사용 포트 | (별도 설명) |
timeout | SSH/WinRM | 연결 시도에 대한 대기 값 | |
script_path | SSH/WinRM | 스크립트 복제시 생성되는 경로 | |
private_key | SSH | 연결시 사용할 SSH Key를 지정하며, password 인수보다 우선함 | |
certificate | SSH | 서명된 CA 인증서로 사용 시 private_key와 함께 사용 | |
agent | SSH | ssh-agent를 사용해 인증하지 않는 경우 false로 설정하며 Windows의 경우 Pageant만 사용 가능 | |
agent_identity | SSH | 인증을 위한 ssh- agent의기본사용자 | |
host_key | SSH | 원격 호스트 또는 서명된 CA의 연결을 확인하는데 사용되는 공개키 | |
target_platform | SSH | 연결 대상 플랫폼으로 windows 또는 unix | unix |
https | WinRM | true인 경우 HTTPS로 연결 | false |
insecure | WinRM | true인 경우 HTTPS 유효성 무시 | false |
use_ntlm | WinRM | true인 경우 NTLM 인증을 사용 | false |
cacert | WinRM | 유효성 검증을 위한 CA 인증서 |
- 원격 연결이 요구되는 프로비저너의 경우 스크립트 파일을 원격 시스템에 업로드해 해당 시스템의 기본 쉘에서 실행하므로
- 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
- connection 적용 시 배스천호스트를 설정하는 인수와 설명
인수 | 설명 | 기본값 |
bastion_host | 설정하게 되면 배스천호스트 연결이 활성화되며 연결되성 호스트를 지정 | |
bastion_host_key | 호스트 연결을 위한 공개 키 | |
bastion_port | 배스천 호스트에 연결할 포트 | port 인수 값 |
bastion_user | 배스천 호스트에 연결할 사용자 | user 인수 값 |
bastion_password | 배스천 호스트 연결에 사용할 비밀번호 | password 인수 값 |
bastion_private_key | 배스천 호스트 연결에 사용할 SSH 키파일 | private_key 인수 값 |
bastion_certificate | 서명된 CA 인증서 내용으로 bastion_private_key와 함께 사용 |
3.12.4 file 프로비저너
- 테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는데 사용한다.
- 사용되는 인수
- source : 소스 파일 또는 디렉터리. 현재 작업 중인 디렉터리에 대한 상대 또는 절대 경로로 지정. content와 함께 사용할 수 없다.
- content : 연결 대상에 복사할 내용을 정의하며 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source와 함께 사용할 수 없다.
- destination : 필수 항목. 항상 절대 경로로 지정되어야 하며, 파일 또는 디렉터리.
- destination 지정 시 주의해야 할 점
- ssh 연결의 경우 대상 디렉터리가 존재해야 함
- winrm연결은 디렉터리가 없는 경우 자동으로 생성됨.
- destination이 /tmp인 경우 source가 디렉터리로 /foo 처럼 마지막에 /가 없는 경우 대상 디렉터리에 지정한 디렉터리가 업로드 되어 연결된 시스템에 /tmp/foo 디렉터리가 업로드 된다.
- source가 디렉터리로 /foo/처럼 마지막에 /가 포함되는 경우 source 디렉터리 내의 파일만 /tmp디렉터리에 업로드 된다.
-ex)
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"
}
}
3.12.5 remode-exec 프로비저너
- 원격지 환경에서 실행한 커맨드와 스크립트를 정의한다.
- 예로 EC2 생성 후 해당 VM 에서 명령을 실행하고 패키지를 설치하는 등의 동작을 의미.
- 사용되는 인수(배타적)
- inline : 명령에 대한 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 명령을 , 로 구분해 구성한다.
- script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.
- scripts : 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 스크립트 경로를 , 로 구분해 구성한다
- script 또는 scripts의 대상 스크립트 실행에 필요한 인수는 관련 구성에서 선언할 수 없으므로
- 필요할 때 file 프로바이더로 해당 스크립트를 업로드하고 inline 인수를 활용해 스크립트에 인수를 추가한다.
ex)
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",
]
}
}
3.13 null_resource와 terraform_data
- 테라폼 1.4 버전이 릴리스되면서 기존 null_resource 리소스를 대체하는 terraform_data 리소스가 추가되었다.
3.13.1 null_resource - 아무작업도 수행하지 않는 리소스
- 이런 리소스가 필요한 이유?
- 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생
- 프로바이더가 제공하는 리소스 수명 주기 관리만으로 이를 해결하기 어렵다.
- 주로 사용되는 시나리오
- 프로비저닝 수행 과정에서 명령어 실행
- 프로비저너와 함께 사용
- 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
- 출력을 위한 데이터 가공
- 다음 상황을 가정하여 EC2 인스턴스를 프로비저닝 하기 위해 aws_instance 리소스 구성 시 앞서 확인한 프로비저너를 활용하여 웹 서비스를 실행해보자.
AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시키고 싶다
웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성해야 한다.
# 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.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가 필요하다
# 실행 : 테라폼 구성 정의에서 상호 참조가 발생하는 상황으로, 실제 실행되는 코드를 작성하여 plan 수행 시 에러 발생
# 두 리소스의 종속성이 상호 참조되어 발생하는 에러
$ terraform init
$ terraform plan
╷
│ Error: Cycle: aws_instance.example, aws_eip.myeip
│
│
╵
- main.tf 파일 내용 수정 : 둘 중 하나의 실행 시점을 한 단계 뒤로 미뤄야 한다.
- 이 경우에 실행에 간격을 추가하여 실제 리소스와는 무관한 동작을 수행하기 위해 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-04c3eed40a831edae" # az b 의 subnet을 사용했더니 t2.micro가 지원을 하지 않았다.
private_ip = "172.31.0.100"
key_name = "test-web-key" # 각자 자신의 EC2 SSH Keypair 이름 지정
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/*******/.ssh/test-web-key.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"
}
# 프로비저너 필요로 설치
terraform plan
terraform init -upgrade
# 실행 : EIP 할당 전 (임시) 유동 공인 IP 출력
terraform plan
terraform apply -auto-approve
...
null_resource.echomyeip: Still creating... [10s elapsed]
null_resource.echomyeip (remote-exec): 3.39.85.144
null_resource.echomyeip: Creation complete after 10s [id=6193470913700672762]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
Outputs:
eip = "3.39.85.144"
public_ip = "13.125.230.115"
$ terraform state list
aws_eip.myeip
aws_instance.example
aws_security_group.instance
null_resource.echomyeip
$ terraform state show aws_eip.myeip
# aws_eip.myeip:
resource "aws_eip" "myeip" {
allocation_id = "eipalloc-0a2a746ed8281ff58"
associate_with_private_ip = "172.31.0.100"
association_id = "eipassoc-0849d538cd0683ede"
domain = "vpc"
id = "eipalloc-0a2a746ed8281ff58"
instance = "i-0be1186dd6ed93e27"
network_border_group = "ap-northeast-2"
network_interface = "eni-0b892a05860a22ef3"
private_dns = "ip-172-31-0-100.ap-northeast-2.compute.internal"
private_ip = "172.31.0.100"
public_dns = "ec2-3-39-85-144.ap-northeast-2.compute.amazonaws.com"
public_ip = "3.39.85.144"
public_ipv4_pool = "amazon"
tags_all = {}
vpc = true
}
$ terraform state show aws_instance.example
resource "aws_instance" "example" {
ami = "ami-0c9c942bd7bf113a2"
arn = "arn:aws:ec2:ap-northeast-2:************:instance/i-0be1186dd6ed93e27"
associate_public_ip_address = true
availability_zone = "ap-northeast-2a"
cpu_core_count = 1
cpu_threads_per_core = 1
disable_api_stop = false
disable_api_termination = false
ebs_optimized = false
get_password_data = false
hibernation = false
id = "i-0be1186dd6ed93e27"
instance_initiated_shutdown_behavior = "stop"
instance_state = "running"
instance_type = "t2.micro"
ipv6_address_count = 0
ipv6_addresses = []
key_name = "test-web-key"
monitoring = false
placement_partition_number = 0
primary_network_interface_id = "eni-0b892a05860a22ef3"
private_dns = "ip-172-31-0-100.ap-northeast-2.compute.internal"
private_ip = "172.31.0.100"
public_dns = "ec2-13-125-230-115.ap-northeast-2.compute.amazonaws.com"
public_ip = "13.125.230.115"
secondary_private_ips = []
security_groups = [
"t101sg",
]
source_dest_check = true
subnet_id = "subnet-04c3eed40a831edae"
tags = {
"Name" = "Single-WebSrv"
}
tags_all = {
"Name" = "Single-WebSrv"
}
tenancy = "default"
user_data = "e202c5c73810abfef204d705d1e3e45cdae80d6b"
user_data_replace_on_change = false
vpc_security_group_ids = [
"sg-0d6bb2e17dc246f63",
]
capacity_reservation_specification {
capacity_reservation_preference = "open"
}
cpu_options {
core_count = 1
threads_per_core = 1
}
credit_specification {
cpu_credits = "standard"
}
enclave_options {
enabled = false
}
maintenance_options {
auto_recovery = "default"
}
metadata_options {
http_endpoint = "enabled"
http_put_response_hop_limit = 1
http_tokens = "optional"
instance_metadata_tags = "disabled"
}
private_dns_name_options {
enable_resource_name_dns_a_record = false
enable_resource_name_dns_aaaa_record = false
hostname_type = "ip-name"
}
root_block_device {
delete_on_termination = true
device_name = "/dev/sda1"
encrypted = false
iops = 100
tags = {}
throughput = 0
volume_id = "vol-0f5f99bc29359926e"
volume_size = 8
volume_type = "gp2"
}
}
$ terraform state show null_resource.echomyeip
# null_resource.echomyeip:
resource "null_resource" "echomyeip" {
id = "6193470913700672762"
}
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
$ terraform graph > graph.dot
# 데이터소스 값 확인
$ echo "aws_instance.example.public_ip" | terraform console
"13.125.230.115"
$ echo "aws_eip.myeip.public_ip" | terraform console
"3.39.85.144"
# 출력된 EC2 퍼블릭IP로 curl 접속 확인
$ MYIP=$(terraform output -raw eip)
$ while true; do curl --connect-timeout 1 http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
Hello, T101 Study
------------------------------
2023년 7월 22일 토요일 23시 10분 42초 KST
Hello, T101 Study
------------------------------
2023년 7월 22일 토요일 23시 10분 43초 KST
Hello, T101 Study
------------------------------
2023년 7월 22일 토요일 23시 10분 45초 KST
Hello, T101 Study
------------------------------
2023년 7월 22일 토요일 23시 10분 46초 KST
Hello, T101 Study
- 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() # 테라폼으로 실행 계획을 생성할 떄마다 재실행
}
...생략...
}
3.13.2 terraform_data
- 이 리소스 또한 자체적으로 아무것도 수행하지 않지만 null_resource는 별도의 프로바이더 구성이 필요하다는 점과 비교하여 추가 프로바이더 없이 테라폼 자체에 포함된 기본 수명주기 관리자가 제공된다는 것이 장점이다.
- 사용 시나리오는 기본 null_resourcr와 동일하며 강제 재실행을 위한 trigger_replace와 상태 저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 제공된다.
- triggers_replace에 정의되는 값이 기존 map 형태에서 tuple로 변경되어 쓰임이 더 간단해졌다
- terraform_data 리소스의 trigger_replace 정의와 동작 예제
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.14 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
}
$ terraform init && terraform plan && terraform apply -auto-approve
Outputs:
file_content = "foo!"
$ cat foo.bar ; echo
foo!
$ terraform state list
local_file.a
$ echo "local_file.a" | terraform console
{
"content" = "foo!"
"content_base64" = tostring(null)
"content_base64sha256" = "wOCqrqBQvPO+JsDCPVj6iQwN+3nIojAWtKhs0oym6nE="
"content_base64sha512" = "TSCmPxazw2YStHurNILfRykjK/J4evgArH/2KnQuzQZodz4cq1f/ig2GeQO7mBI+Qx5jkTQEZxLCGs3mPtsB3Q=="
"content_md5" = "35af8b7a9490467f75f19c1e5459f7e7"
"content_sha1" = "4bf3e335199107182c6f7638efaad377acc7f452"
"content_sha256" = "c0e0aaaea050bcf3be26c0c23d58fa890c0dfb79c8a23016b4a86cd28ca6ea71"
"content_sha512" = "4d20a63f16b3c36612b47bab3482df4729232bf2787af800ac7ff62a742ecd0668773e1cab57ff8a0d867903bb98123e431e639134046712c21acde63edb01dd"
"directory_permission" = "0777"
"file_permission" = "0777"
"filename" = "./foo.bar"
"id" = "4bf3e335199107182c6f7638efaad377acc7f452"
"sensitive_content" = (sensitive value)
"source" = tostring(null)
}
- local_file 의 이름을 a → b로 변경 가정
# main.tf
resource "local_file" "b" {
content = "foo!"
filename = "${path.module}/foo.bar"
}
output "file_content" {
value = local_file.b.content
}
# plan 확인 : 기존 리소스를 제거하고 새로운 리소스를 생성하려 계획
$ terraform plan
...
Plan: 1 to add, 0 to change, 1 to destroy.
- 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
}
# 실행 - 제거나 생성 안함.
$ terraform plan
...
local_file.b: Refreshing state... [id=4bf3e335199107182c6f7638efaad377acc7f452]
Terraform will perform the following actions:
# local_file.a has moved to local_file.b
resource "local_file" "b" {
id = "4bf3e335199107182c6f7638efaad377acc7f452"
# (10 unchanged attributes hidden)
}
Plan: 0 to add, 0 to change, 0 to destroy.
$ terraform apply -auto-approve
Outputs:
file_content = "foo!"
$ terraform state list
local_file.b
$ echo "local_file.b" | terraform console
{
"content" = "foo!"
"content_base64" = tostring(null)
"content_base64sha256" = "wOCqrqBQvPO+JsDCPVj6iQwN+3nIojAWtKhs0oym6nE="
"content_base64sha512" = "TSCmPxazw2YStHurNILfRykjK/J4evgArH/2KnQuzQZodz4cq1f/ig2GeQO7mBI+Qx5jkTQEZxLCGs3mPtsB3Q=="
"content_md5" = "35af8b7a9490467f75f19c1e5459f7e7"
"content_sha1" = "4bf3e335199107182c6f7638efaad377acc7f452"
"content_sha256" = "c0e0aaaea050bcf3be26c0c23d58fa890c0dfb79c8a23016b4a86cd28ca6ea71"
"content_sha512" = "4d20a63f16b3c36612b47bab3482df4729232bf2787af800ac7ff62a742ecd0668773e1cab57ff8a0d867903bb98123e431e639134046712c21acde63edb01dd"
"directory_permission" = "0777"
"file_permission" = "0777"
"filename" = "./foo.bar"
"id" = "4bf3e335199107182c6f7638efaad377acc7f452"
"sensitive_content" = (sensitive value)
"source" = tostring(null)
}
- 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
}
3.15 CLI를 위한 시스템 환경 변수 - 실행 방식과 출력 내용에 대한 옵션을 조절
- 영구적으로 로컬 환경에 적용되는 옵션이나 별도 서버 환경에서 실행하기 위한 옵션을 부여할 수 있다.
- 이를 통해 로컬 작업 환경과 다른 환경 구성에서만 사용될 특정 옵션을 적용한다.
Mac/리눅스/유닉스: export <환경 변수 이름>=<값>
Windows CMD: set <환경 변수 이름>=<값>
Windows PowerShell: $Env:<환경 변수 이름>='<값>'
3.15.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 동작을 실행하면 테라폼 출력에 관련 로그가 출력된다
TF_LOG=info terraform plan
TF_LOG=info terraform plan
2022-04-10T20:01:25.331+0900
[INFO] Terraform version: 1.1.7 2022-04-10T20:01:25.331+0900
[INFO] Go runtime version: go1.17.2
2022-04-10720:01:25.331+0900
[INFO] CLI args: []string{"terraform", "plan"}
2022-04-10T20:01:25.334+0900
[INFO] Loading CLI configuration from /PATH/.terraform.d/credentials.tfrc.ison
2022-04-10T20:01:25.340+0900 [INFO] CLI command args: []string{"plan"}
2022-04-10T20:01:25.490+0900 [INFO] backend/local: starting Plan operation
var.my_var Enter a value:
3.15.2 TF_INPUT
- 값을 false 또는 0으로 설정하면 테라폼 실행 시 인수에 -input=false 를 추가한 것과 동일한 수행 결과를 확인
- 환경에 맞게 TF_INPUT을 0으로 설정하고 terraform plan 동작 실행하면 입력받는 동작을 수행하지 않으므로
- 입력 변수를 입력해야 하는 경우 에러가 출력된다
TF_INPUT=0 terraform plan
Error : No value for required variable
3.15.3 TF_VAR_name
- TF_VAR_<변수 이름>을 사용하면 입력 시 또는 default로 선언된 변수 값을 대체한다 ← 3.6절에서 확인!
3.15.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
<에러>
terraform plan
<정상 계획 예측 출력>
3.15.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
Chapter 4 프로바이더
- 테라폼은 terraform 바이너리 파일을 시작으로 로컬 환경에나 배포 서버와 같은 원격 환경에서 원하는 대상을 호출하는 방식으로 실행된다. 이때 ‘원하는 대상’은 호출하는 방식이 서로 다르지만 대상의 공급자, 즉 프로바이더가 제공하는 API를 호출해 상호작용을 한다. 여기서 테라폼이 대상과의 상호작용을 할 수 있도록 하는 것이 ‘프로바이더’다.
- 각 프로바이더의 API구현은 서로 다르지만 테라폼의 고유 문법으로 동일한 동작을 수행하도록 구현되어 있다. 프로바이더는 플러그인 형태로 테라폼에 결합되어 대상이 되는 클라우드, SaaS, 기타 서비스 API를 사용해 동작을 수행한다. 각 프로바이더는 테라폼이 관리하는 리소스 유형과 데이터 소스를 사용할 수 있도록 연결한다.
- 즉, 테라폼은 프로바이더 없이는 어떤 종류의 인프라와 서비스도 관리할 수 없다는 의미다. 대부분의 프로바이더는 대상 인프라 환경이나 서비스 환경에 대한 리소스를 관리하므로, 프로바이더를 구성할 때는 대상과의 연결과 인증에 필요한 정보가 제공되어야 한다.
- 각 프로바이더는 테라폼 실행 파일과는 별도로 자체 관리되고 게시된다. 테라폼 레지스트리 사이트에서 주요 프로바이더와 관련 문서를 확인 가능( 테라폼 레지스트리 https://registry.terraform.io/ )
4.1 프로바이더 구성
- 테라폼 레지스트리의 프로바이더 목록에는 유지 보수 및 게시에 대한 권한에 따라 Tier 정보가 제공
- 지정하는 프로바이더의 요구사항을 정의 : 레지스트리의 Overview 항목 오른쪽에 있는 [USE PROVIDER] 클릭해 현재 버전의 정의 방법을 확인
Terraform Registry
registry.terraform.io
4.1.1 로컬 이름과 프로바이더 지정
- terraform 블록의 required_providers 블록 내에 <로컬 이름> = { } 으로 여러 개의 프로바이더를 정의할 수 있다. 여기서 사용되는 로컬 이름은 테라폼 모듈 내에서 고유해야 한다. 로컬 이름과 리소스 접두사는 독립적으로 선언되며, 각 프로바이더의 소스 경로가 지정되면 프로바이더의 고유 접두사가 제공된다. 만약 동일한 접두사를 사용하는 프로바이더가 선언되는 경우 로컬 이름을 달리해 관련 리소스에서 어떤 프로바이더를 사용하는지 명시적으로 지정할 수 있다.
- 예를 들어 다음의 main.tf에서처럼 동일한 http 이름을 사용하는 다수의 프로바이더가 있는 경우 각 프로바이더에 고유한 이름을 부여하고 리소스와 데이터 소스에 어떤 프로바이더를 사용할지 provider 인수에 명시한다. 단, 동일한 source에 대해 다수의 정의는 불가능한다.
- 동일한 http 접두사를 사용하는 다수의 프로바이더 사용 정의
terraform {
required_providers {
architech-http = {
source = "architect-team/http"
version = "~> 3.0"
}
http = {
source = "hashicorp/http"
}
aws-http = {
source = "terraform-aws-modules/http"
}
}
}
data "http" "example" {
provider = aws-http
url = "https://checkpoint-api.hashicorp.com/v1/check/terraform"
request_headers = {
Accept = "application/json"
}
}
4.1.2 단일 프로바이더의 다중 정의
- 동일한 프로바이더를 사용하지만 다른 조건을 갖는 경우, 사용되는 리소스마다 별도로 선언된 프로바이더를 지정해야 하는 경우가 있다.
- 예를 들면, AWS 프로바이더를 사용하는데 서로 다른 권한의 IAM을 갖는 Access ID 또는 대상 리전을 지정해야 하는 경우다.
- 이때는 프로바이더 선언에서 alias를 명시하고
- 사용하는 리소스와 데이터 소스에서는 provider 메타인수를 사용해 특정 프로바이더를 지정할 수 있다.
- provider 메타인수에 지정되지 않은 경우 alias가 없는 프로바이더가 기본 프로바이더로 동작한다.
# main.tf
provider "aws" {
region = "ap-southeast-1"
}
provider "aws" {
alias = "seoul"
region = "ap-northeast-2"
}
resource "aws_instance" "app_server1" {
ami = "ami-06b79cf2aee0d5c92"
instance_type = "t2.micro"
}
resource "aws_instance" "app_server2" {
provider = aws.seoul
ami = "ami-0ea4d4b8dc1e46212"
instance_type = "t2.micro"
}
#실행
$ terraform init && terraform plan
$ terraform apply -auto-approve
#
$ terraform state list
aws_instance.app_server1
aws_instance.app_server2
$ echo "aws_instance.app_server1.public_ip" | terraform console
"3.1.222.44"
$ echo "aws_instance.app_server2.public_ip" | terraform console
"3.35.210.51"
$ aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table
------------------------------------------------------------------------------------------
| DescribeInstances |
+----------------------------------------------------------------------------------------+
|| Reservations ||
|+-----------------------------------+--------------------------------------------------+|
|| OwnerId | ************ ||
|| ReservationId | r-07b6fbaec0bc67254 ||
|+-----------------------------------+--------------------------------------------------+|
||| Instances |||
||+------------------------------+-----------------------------------------------------+||
||| AmiLaunchIndex | 0 |||
||| Architecture | x86_64 |||
||| ClientToken | de513df1-e58c-4cd3-86c6-0f17b2c1543d |||
||| CurrentInstanceBootMode | legacy-bios |||
||| EbsOptimized | False |||
||| EnaSupport | True |||
||| Hypervisor | xen |||
||| ImageId | ami-0ea4d4b8dc1e46212 |||
||| InstanceId | i-0c3b76f0a8d03ca0b |||
||| InstanceType | t2.micro |||
||| KeyName | test-web-key |||
||| LaunchTime | 2023-07-05T09:30:15+00:00 |||
||| PlatformDetails | Linux/UNIX |||
:...skipping...
$ aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table --region ap-southeast-1
-----------------------------------------------------------------------------------------
| DescribeInstances |
+---------------------------------------------------------------------------------------+
|| Reservations ||
|+-----------------------------------+-------------------------------------------------+|
|| OwnerId | ************ ||
|| ReservationId | r-0a26f24f627177622 ||
|+-----------------------------------+-------------------------------------------------+|
||| Instances |||
||+---------------------------+-------------------------------------------------------+||
||| AmiLaunchIndex | 0 |||
||| Architecture | x86_64 |||
||| ClientToken | terraform-20230722144348375700000001 |||
||| CurrentInstanceBootMode | legacy-bios |||
||| EbsOptimized | False |||
||| EnaSupport | True |||
||| Hypervisor | xen |||
||| ImageId | ami-06b79cf2aee0d5c92 |||
||| InstanceId | i-0f8bf35149fdbd687 |||
||| InstanceType | t2.micro |||
||| LaunchTime | 2023-07-22T14:43:49+00:00 |||
||| PlatformDetails | Linux/UNIX |||
||| PrivateDnsName | ip-172-31-17-218.ap-southeast-1.compute.internal |||
||| PrivateIpAddress | 172.31.17.218 |||
||| PublicDnsName | ec2-3-1-222-44.ap-southeast-1.compute.amazonaws.com |||
||| PublicIpAddress | 3.1.222.44 |||
||| RootDeviceName | /dev/xvda |||
||| RootDeviceType | ebs |||
||| SourceDestCheck | True |||
||| StateTransitionReason | |||
||| SubnetId | subnet-07b6da77e2cf7219e |||
||| UsageOperation | RunInstances |||
||| UsageOperationUpdateTime | 2023-07-22T14:43:49+00:00 |||
||| VirtualizationType | hvm |||
...
# 삭제
terraform destroy -auto-approve
4.1.3 프로바이더 요구사항 정의
- 테라폼 실행시 요구되는 프로바이더 요구사항은 terraform 블록의 required_providers블록에 여러개를 정의할 수 있다.
- source 에는 프로바이더 다운로드 경로를 지정하고 version은 버전 제약을 명시한다.
- 프로바이더 요구사항 정의 블록
terraform {
required_providers {
<프로바이더 로컬 이름> = {
source = [<호스트 주소>/]<네임스페이스>/<유형>
version = <버전 제약>
}
...
}
}
- 호스트 주소 : 프로바이더를 배포하는 주소로서 기본값은 registry.terraform.io
- 네임스페이스 : 지정된 레지스트리 내에서 구분하는 네임스페이스로, 공개된 레지스트리 및 Terraform Cloud의 비공개 레지스트리의 프로바이더를 게시하는 조직을 의미
- 유형 : 프로바이더에서 관리되는 플랫폼이나 서비스 이름으로 일반적으로 접두사와 일치하나 일부 예외가 있을 수 있음
- 프로바이더는 기능이나 조건이 시간이 지남에 따라 변경될 수 있다.
- 이 같은 변경에 특정 버전을 명시하거나 버전 호환성을 정의할 때, version에 명시할 수 있다.
- 이 값이 생략되는 경우 terraform init을 하는 당시의 가장 최신 버전으로 선택된다. (3.3장 버전 설정 확인)
4.1.4 프로바이더 설치
- 테라폼을 실행하기 전 terraform init 명령을 통해 정의된 프로바이더를 다운로드, 복사, 캐시에서 읽어오게 된다.
- 항상 지정된 구성에 대해 동일한 프로바이더를 설치하도록 하려면 테라폼 구성에 사용되는 프로바이더에 대해 명시적으로 terraform 블록에 정의하거나 .terraform.lock.hcl 잠금 파일을 코드 저장소에 공유하는 방안이 요구된다.
- 2장에서 살펴본 것처럼 required_providers에 지정된 프로바이더가 있는 경우 코드상 구성에서 사용 여부에 관계없이 프로바이더를 다운로드하게 되고,
- required_providers에 지정하지 않더라고 테라폼 구성 코드사에서 사용된 프로바이더를 테라폼에서 추론해 최신 버전의 프로바이더를 다운로드 한다.
4.1.5 프로바이더 간 전환 여부
- 클라우드를 대상으로 테라폼을 사용하는 경우 다른 클라우드 프로바이더로 전환이 가능할까? ⇒ 불가능하다!
- 테라폼은 인프라에 대한 단일 프로비저닝 도구로 사용되지만 대상이 되는 환경은 서로 다른 API로 구현된 프로바이더가 제공된다.
- 프로바이던간 대응되는 리소스 예시
Resource | AWS | GCP |
VPC, Subnets, Firewall | aws_vpc aws_subnet |
google_compute_network google_compute_subnetwork google_compute_firewall |
Load Balancer | aws_elb aws_security_group |
google_compute_backend_service google_compute_global_forwarding_rule google_compute_target_http_proxy |
Virtual Machine | aws_instance | google_compute_instance |
Database | aws_db_instance | google_sql_database_instance |
4.2 프로바이더 에코시스템
- 테라폼의 에코시스템은 사용자가 사용하는 방식과 구조에 테라폼을 적용할 수 있도록 설계된다.
- 에코시스템을 위한 테라폼 통합은 워크플로 파트너와 인프라 파트너로 나눈다.
- 워크플로 파트너는 테라폼 실행 및 Terraform Cloud/Enterprise와 연계하여 동작하는 기능을 제공하는 항목으로 이루어져 있다.
- 대표적으로 테라폼 구성을 관리하기 위한 VCS를 제공하는 깃허브, 깃랩, 비트버킷, 애저 데브옵스가 이 항목에 해당된다.
- 프로바이더의 경우 인프라 파트너로 해당된다.
- 인프라 파트너는 사용자가 테라폼으로 대상 플랫폼의 API로 상호작용 가능한 리소스를 관리할 수 있도록 한다.
- 인프라 파트너의 분류와 프로바이더 대상
분류 | 프로바이더 대상 |
퍼블릭 클라우드 | IaaS, SaaS 및 PaaS를 포함한 다양한 서비스를 제공하는 대규모 글로벌 클라우드 제공 |
컨테이너 오케스트레이션 | 컨테이너 프로비저닝 및 배포를 지원 |
IaaS(Infrastructure-as-a-Service) | 스토리지, 네트워킹 및 가상화와 같은 솔루션을 제공하는 인프라 및 IaaS 제공 |
보안 및 인증 | 인증 및 보안 모니터링 플랫폼 |
자산 관리 | sw 라이선스, hw 자산 및 클라우드 리소스를 비롯한 주요 조직 및 IT 리소스의 자산 관리를 제공 |
CI/CD | 지속적인 통합 및 지속적인 제공/배포 |
로깅 및 모니터링 | 로거, 측정 도구 및 모니터링 서비스와 같은 서비스를 구성하고 관리하는 기능 |
유틸리티 | 임의 값 생성, 파일 생성, http 상호 작용 및 시간 기반 리소스와 같은 도우미 기능 |
클라우드 자동화 | 구성 관리와 같은 전문화된 클라우드 인프라 자동화 관리 기능 |
데이터 관리 | 데이터 센터 스토리지, 백업 및 복구 솔루션 |
네트워킹 | 라우팅, 스위칭, 방화벽 및 SD-WAN 솔루션과 같은 네트워크별 하드웨어 및 가상화된 제품과 통합 |
VCS(버전 제어 시스템) | Terraform 내에서 VCS(버전 제어 시스템) 프로젝트, 팀 및 리포지토리에 중점 |
통신 및 메시징 | 통신, 이메일 및 메시징 플랫폼과 통합 |
데이터베이스 | 데이터베이스 리소스를 프로비저닝 및 구성하는 기능 |
PaaS(Platform-as-a-Service) | 다양한 하드웨어, 소프트웨어 및 애플리케이션 개발 도구를 제공하는 플랫폼 및 PaaS |
웹서비스 | 웹 호스팅, 웹 성능, CDN 및 DNS 서비스 |
4.3 프로바이더 경험해보기
- 실습
# provider_data.tf 파일 생성 : 리전 별 AMI ID값이 다르므로, 필터를 활용하자
provider "aws" {
region = "ap-northeast-2"
alias = "region_1"
}
provider "aws" {
region = "ap-southeast-1"
alias = "region_2"
}
data "aws_region" "region_1" {
provider = aws.region_1
}
data "aws_region" "region_2" {
provider = aws.region_2
}
data "aws_ami" "ubuntu_region_1" {
provider = aws.region_1
most_recent = true
owners = ["************"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
data "aws_ami" "ubuntu_region_2" {
provider = aws.region_2
most_recent = true
owners = ["************"]
filter {
name = "name"
values = ["ubuntu/images/hvm-ssd/ubuntu-focal-20.04-amd64-server-*"]
}
}
# ec2.tf 파일 생성 - 리전, AMI 참조
resource "aws_instance" "region_1" {
provider = aws.region_1
ami = data.aws_ami.ubuntu_region_1.id
instance_type = "t2.micro"
}
resource "aws_instance" "region_2" {
provider = aws.region_2
ami = data.aws_ami.ubuntu_region_2.id
instance_type = "t2.micro"
}
# 실행
# [터미널1] ap-northeast-2
while true; do aws ec2 describe-instances --region ap-northeast-2 --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done
# [터미널2] ap-southeast-1
while true; do aws ec2 describe-instances --region ap-southeast-1 --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done
# init & plan & apply
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
# 확인
aws ec2 describe-instances --region ap-northeast-2 --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text
aws ec2 describe-instances --region ap-southeast-1 --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text
** 주의사항
Warning 1 : Multiregion is hard 프로덕션 수준의 멀티 리전은 어렵다
- Active-Active 멀티 리전 서비스를 위해서 ‘지역간 지연 시간, 고유 ID, 최종 일관성’ 등 여러가지 고려사항이 많아서 쉽지 않다.
Warning 2 : Use aliases sparingly Alias 를 빈번하게 사용하지 말자
- 별칭을 사용하여 두 리전에 배포하는 단일 테라폼 모듈은 한 리전이 다운 시, plan 과 apply 시도가 실패합니다
프로덕션 환경은 멀티 리전의 별칭을 사용하는 것보다는, 3장에서 설명한 것 처럼 환경을 완전히 격리해야 합니다. 이를 통해 영향도를 최소화 할 수 있습니다.
'Terraform > Study_과제' 카테고리의 다른 글
7주차 - 워크플로 (0) | 2023.08.20 |
---|---|
6주차 - 협업 (0) | 2023.08.11 |
4주차 - State & 모듈 (0) | 2023.07.29 |
2주차 - Terraform 기본 사용 2/3 (0) | 2023.07.15 |
1주차 - Terraform 기본 사용 1/3 (0) | 2023.07.09 |