이제 어느덧 가시다 Team 과의 마지막 스터디 주차입니다. 더 많은 인사이트를 얻을 수 있는 기회였고, 빡쎈(?)일정이었지만 매주 재밌는 내용 덕분에 마지막 주차 까지 무사히 달려올 수 있던 것 같습니다. 이번 주, 마지막 주차는 Terraform with EKS 입니다.
** Terraform 의 기본 문법을 습득하고 EKS 의 Module 에서 어떤 API 를 호출하는지 파악 하는 것이 KeyPoint 입니다.
실습 환경 준비 - Local 에서 진행합니다.
# tfenv 설치
brew install tfenv
# 설치 가능 버전 리스트 확인
tfenv list-remote
# 테라폼 1.5.1 버전 설치
tfenv install 1.8.1
# 테라폼 1.5.1 버전 사용 설정
tfenv use 1.8.1
# tfenv로 설치한 버전 확인
tfenv list
# 테라폼 버전 정보 확인
terraform version
# 자동완성
terraform -install-autocomplete
## 참고 .zshrc 에 아래 추가됨
cat ~/.zshrc
autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C /usr/local/bin/terraform terraform
# AWSCLI : 자신의 PC OS에 맞게 설
brew install awscli
# EKSCTL : 자신의 PC OS에 맞게 설치
brew install eksctl
# Kubectl
brew install kubernetes-cli
# Helm
brew install helm
# jquery, tree, watch
brew install tree jq watch
AWS 서울 리전(ap-northeast-2)에 default VPC 확인 : 없을 경우 아래 (옵션) default VPC 생성 해두기
default VPC가 삭제되어 없을 경우, 아래처럼 default VPC를 생성
# default VPC를 생성
aws ec2 create-default-vpc
# default Subnet 생성
aws ec2 create-default-subnet --availability-zone ap-northeast-2a
aws ec2 create-default-subnet --availability-zone ap-northeast-2b
aws ec2 create-default-subnet --availability-zone ap-northeast-2c
aws ec2 create-default-subnet --availability-zone ap-northeast-2d
mkdir learn-terraform
cd learn-terraform
touch main.tf
#aws ec2 describe-images --owners self amazon
aws ec2 describe-images --owners self amazon --query 'Images[*].[ImageId]' --output text
aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available"
aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId, Name]' --output text
ami-0217b147346e48e84 amzn2-ami-hvm-2.0.20240412.0-x86_64-gp2
aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId]' --output text
ami-0217b147346e48e84
AL2ID=`aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId]' --output text`
echo $AL2ID
ami-0217b147346e48e84
# [터미널1] EC2 생성 모니터링
export AWS_PAGER=""
while true; do aws ec2 describe-instances --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
> **resource “<PROVIDER>_<TYPE>” “<NAME>” {
[CONFIG ...]
}**
>
> - PROVIDER : ‘aws’ 같은 공급자의 이름
> - TYPE : ‘security_group’ 같은 리소스의 유형
> - NAME : 리소스의 이름
> - CONFIG : 한개 이상 arguments
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
}
EOT
# 초기화
terraform init
ls -al
tree .terraform
# plan 확인
terraform plan
# apply 실행
terraform apply
Enter a value: yes 입력
# ec2 생성 확인 : aws 웹 관리 콘솔에서도 확인 - 서울 리전 선택
export AWS_PAGER=""
aws ec2 describe-instances --output table
# 테라폼 정보 확인
terraform state list
aws_instance.example
terraform show
# aws_instance.example:
resource "aws_instance" "example" {
ami = "ami-0217b147346e48e84"
arn = "arn:aws:ec2:ap-northeast-2:236747833953:instance/i-02d7966d0aa05c36a"
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
host_id = null
iam_instance_profile = null
id = "i-02d7966d0aa05c36a"
instance_initiated_shutdown_behavior = "stop"
instance_lifecycle = null
instance_state = "running"
instance_type = "t2.micro"
ipv6_address_count = 0
ipv6_addresses = []
key_name = null
monitoring = false
outpost_arn = null
password_data = null
placement_group = null
placement_partition_number = 0
primary_network_interface_id = "eni-004bfc2194f717ff9"
private_dns = "ip-172-31-2-126.ap-northeast-2.compute.internal"
private_ip = "172.31.2.126"
public_dns = "ec2-43-203-182-137.ap-northeast-2.compute.amazonaws.com"
public_ip = "43.203.182.137"
secondary_private_ips = []
security_groups = [
"default",
]
source_dest_check = true
spot_instance_request_id = null
subnet_id = "subnet-06ef0432e1e25e0d4"
tags_all = {}
tenancy = "default"
user_data_replace_on_change = false
vpc_security_group_ids = [
"sg-0239c0a5e05197b25",
]
capacity_reservation_specification {
capacity_reservation_preference = "open"
}
cpu_options {
amd_sev_snp = null
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_protocol_ipv6 = "disabled"
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/xvda"
encrypted = false
iops = 100
kms_key_id = null
tags = {}
tags_all = {}
throughput = 0
volume_id = "vol-08a58616c9cc54c21"
volume_size = 8
volume_type = "gp2"
}
}
terraform show aws_instance.example
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
profile = "ejl-personal"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
tags = {
Name = "aews-study"
}
}
EOT
# plan 실행 시 아래와 같은 정보가 출력
terraform plan
aws_instance.example: Refreshing state... [id=i-02d7966d0aa05c36a]
Terraform used the selected providers to generate the following execution plan. Resource actions are
indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "i-02d7966d0aa05c36a"
~ tags = {
+ "Name" = "aews-study"
}
~ tags_all = {
+ "Name" = "aews-study"
}
# (38 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
...
# apply 실행
terraform apply
Enter a value: yes 입력
# 모니터링 : [터미널1]에 Name 확인
# 리소스 삭제
terraform destroy
Enter a value: yes 입력
혹은
terraform destroy -auto-approve
HCL : HashiCorp configuration language은 하시코프사에서 IaC와 구성 정보를 명시하기 위해 개발된 오픈 소스 도구
IaC는 수동 프로세스가 아닌 코드를 통해 인프라를 관리하고 프로비저닝 하는 것을 말함
테라폼에서 HCL이 코드의 영역을 담당한다. HCL은 쉽게 읽을 수 있고 빠르게 배울 수 있는 언어의 특징을 가진다.
인프라가 코드로 표현되고, 이 코드는 곧 인프라이기 때문에 선언적 특성을 갖게 되고 튜링 완전한 Turing-complete 언어적 특성을 갖는다.
즉, 일반적인 프로그래밍 언어의 조건문 처리 같은 동작이 가능하다. 자동화와 더불어, 쉽게 버저닝해 히스토리를 관리하고 함께 작업 할 수 있는 기반을 제공.
HCL 표현식
// 한줄 주석 방법1
# 한줄 주석 방법2
/*
라인
주석
*/
locals {
key1 = "value1" # = 를 기준으로 키와 값이 구분되며
myStr = "TF ♡ UTF-8" # UTF-8 문자를 지원한다.
multiStr = <<EOF
Multi
Line
String
with anytext
EOF
boolean1 = true # boolean true
boolean2 = false # boolean false를 지원한다.
deciaml = 123 # 기본적으로 숫자는 10진수,
octal = 0123 # 0으로 시작하는 숫자는 8진수,
hexadecimal = "0xD5" # 0x 값을 포함하는 스트링은 16진수,
scientific = 1e10 # 과학표기 법도 지원한다.
# funtion 호출 예
myprojectname = format("%s is myproject name", var.project)
# 3항 연산자 조건문을 지원한다.
credentials = var.credentials == "" ? file(var.credentials_file) : var.credentials
}
terraform {
required_version = "~> 1.3.0" # 테라폼 버전
required_providers { # 프로바이더 버전을 나열
random = {
version = ">= 3.0.0, < 3.1.0"
}
aws = {
version = "4.2.0"
}
}
cloud { # Cloud/Enterprise 같은 원격 실행을 위한 정보
organization = "<MY_ORG_NAME>"
workspaces {
name = "my-first-workspace"
}
}
backend "local" { # state를 보관하는 위치를 지정
path = "relative/path/to/terraform.tfstate"
}
}
# version = Major.Minor.Patch
version = 1.3.4
선언된 버전 | 의미 | 고려 사항 |
---|---|---|
1.0.0 | 테라폼 v1.0.0만을 허용한다 | 테라폼을 업그레이드하기 위해서는 선언된 버전을 변경해야만 한다 |
>= 1.0.0 | 테라폼 v1.0.0 이상의 모든 버전을 허용한다 | v1.0.0 버전을 포함해 그 이상의 모든 버전을 허용해 실행된다 |
~> 1.0.0. | 테라폼 v1.0.0을 포함한 v1.0.x 버전을 하용하고 v1.x는 허용하지 않는다 | 부버전에 대한 업데이트는 무중단으로 이루어진다 |
>= 1.0, < 2.0.0 | 테라폼 v1.0.0 이상 v2.0.0 미만인 버전을 허용한다 | 주버전에 대한 업데이트를 방지한다 |
cd ..
cd 03.start
# 현재 버전 정보 확인
terraform version
Terraform v1.5.1
terraform {
required_version = "< 1.0.0"
}
resource "local_file" "abc" {
content = "abc!"
filename = "${path.module}/abc.txt"
}
# 실행 결과는?
terraform init
...
terraform {
required_version = ">= 1.0.0"
}
resource "local_file" "abc" {
content = "abc!"
filename = "${path.module}/abc.txt"
}
# 실행 결과는?
terraform init
...
terraform {
required_version = ">= 1.0.0"
required_**providers** {
local = {
source = "hashicorp/local"
**version = ">=10000.0.0"**
}
}
}
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
# 실행 결과는 에러가 발생합니다.
terraform init -upgrade
terraform {
required_version = ">= 1.0.0"
required_providers {
local = {
source = "hashicorp/local"
version = ">= 2.0.0"
}
}
}
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
terraform init -upgrade
리소스 구성
mkdir resource
cd resource
touch main.tf
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
resource "local_file" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
resource "local_file" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
resource "aws_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}
# init 시 프로바이더 선언 없이도 리소스 추가로 자동 인식된 프로바이더 요구사항과 초기화
terraform init
tree .terraform
종속성
테라폼 종속성은 resource, module 선언으로 프로비저인되는 각 요소의 생성 순서를 구분짓는다.
기본적으로 다른 리소스에서 값을 참조해 불러올 경우 생성 선후 관계에 따라 작업자가 의도하지는 않았지만 자동으로 연관 관계가 정의되는 암시적 종속성을 갖게 되고, 강제로 리소스 간 명시적 종속성을 부여할 경우에는 메타인수인 depends_on을 활용한다.
코드 파일 수정
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
resource "local_file" "def" {
content = "456!"
filename = "${path.module}/def.txt"
}
#
terraform apply -auto-approve
...
Plan: 2 to add, 0 to change, 0 to destroy.
local_file.def: Creating...
local_file.abc: Creating...
local_file.abc: Creation complete after 0s [id=5f30576af23a25b7f44fa7f5fdf70325ee389155]
local_file.def: Creation complete after 0s [id=b9fbde4d33ab9c450a7ce303fb4788c9d2db9aed]
# 리소스 확인
ls *.txt
terraform state list
local_file.abc
local_file.def
# graph 확인 > graph-1.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph
terraform graph > graph-1.dot
# 모든 리소스 제거
terraform destroy -auto-approve
ls *.txt
terraform state list
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
resource "local_file" "def" {
content = local_file.abc.content
filename = "${path.module}/def.txt"
}
#
terraform apply -auto-approve
...
Plan: 2 to add, 0 to change, 0 to destroy.
local_file.abc: Creating... <- 먼저 만들고
local_file.abc: Creation complete after 0s [id=5f30576af23a25b7f44fa7f5fdf70325ee389155]
local_file.def: Creating... <- 그 다음 만듬
local_file.def: Creation complete after 0s [id=5f30576af23a25b7f44fa7f5fdf70325ee389155]
ls *.txt
terraform state list
cat abc.txt
123!%
cat def.txt
123!%
diff abc.txt def.txt
# graph 확인 > graph-2.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph
digraph G {
rankdir = "RL";
node [shape = rect, fontname = "sans-serif"];
"local_file.abc" [label="local_file.abc"];
"local_file.def" [label="local_file.def"];
"local_file.def" -> "local_file.abc";
}
terraform graph > graph-2.dot
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
resource "local_file" "def" {
depends_on = [
local_file.abc
]
content = "456!"
filename = "${path.module}/def.txt"
}
#
terraform destroy -auto-approve
terraform apply -auto-approve
...
# graph 확인 > graph-3.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph
digraph G {
rankdir = "RL";
node [shape = rect, fontname = "sans-serif"];
"local_file.abc" [label="local_file.abc"];
"local_file.def" [label="local_file.def"];
"local_file.def" -> "local_file.abc";
}
terraform graph > graph-3.dot
리소스 속성 참조
# Terraform Code
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 리소스 참조
<리소스 유형>.<이름>.<인수>
<리소스 유형>.<이름>.<속성>
resource "kubernetes_namespace" "example" {
metadata {
annotations = {
name = "example-annotation"
}
name = "terraform-example-namespace"
}
}
resource "kubernetes_secret" "example" {
metadata {
namespace = kubernetes_namespace.example.metadata.0.name # namespace 리소스 인수 참조
name = "terraform-example"
}
data = {
password = "P4ssw0rd"
}
}
데이터 소스 구성
데이터 소스는 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용
mkdir 3.5 && cd 3.5
data "local_file" "abc" {
filename = "${path.module}/abc.txt"
}
데이터 소스 속성 참조
# Terraform Code
data "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>
# Declare the data source
data "aws_availability_zones" "available" {
state = "available"
}
resource "aws_subnet" "primary" {
availability_zone = data.aws_availability_zones.available.names[0]
# e.g. ap-northeast-2a
}
resource "aws_subnet" "secondary" {
availability_zone = data.aws_availability_zones.available.names[1]
# e.g. ap-northeast-2b
}
resource "local_file" "abc" {
content = "123!"
filename = "${path.module}/abc.txt"
}
data "local_file" "abc" {
filename = local_file.abc.filename
}
resource "local_file" "def" {
content = data.local_file.abc.content
filename = "${path.module}/def.txt"
}
#
terraform apply -auto-approve
terraform state list
data.local_file.abc
local_file.abc
local_file.def
# 파일 확인
ls *.txt
diff abc.txt def.txt
# graph 확인
terraform graph > graph.dot
# 테라폼 콘솔 : 데이터 소스 참조 확인
terraform console
>
data.local_file.abc.content
...
exit
data "aws_availability_zones" "available" {
state = "available"
}
terraform init -upgrade && terraform apply -auto-approve
terraform state list
data.aws_availability_zones.available
terraform state show data.aws_availability_zones.available
# data.aws_availability_zones.available:
data "aws_availability_zones" "available" {
group_names = [
"ap-northeast-2",
]
id = "ap-northeast-2"
names = [
"ap-northeast-2a",
"ap-northeast-2b",
"ap-northeast-2c",
"ap-northeast-2d",
]
state = "available"
zone_ids = [
"apne2-az1",
"apne2-az2",
"apne2-az3",
"apne2-az4",
]
}
terraform console
> data.aws_availability_zones.available.names
tolist([
"ap-northeast-2a",
"ap-northeast-2b",
"ap-northeast-2c",
"ap-northeast-2d",
])
> data.aws_availability_zones.available.names[0]
"ap-northeast-2a"
> data.aws_availability_zones.available.zone_ids[1]
"apne2-az2"
> data.aws_availability_zones.available.zone_ids[0]
"apne2-az1"
Tip. echo "data.aws_availability_zones.available" | terraform console
{
"all_availability_zones" = tobool(null)
"exclude_names" = toset(null) /* of string */
"exclude_zone_ids" = toset(null) /* of string */
"filter" = toset(null) /* of object */
"group_names" = toset([
"ap-northeast-2",
])
"id" = "ap-northeast-2"
"names" = tolist([
"ap-northeast-2a",
"ap-northeast-2b",
"ap-northeast-2c",
"ap-northeast-2d",
])
"state" = "available"
"timeouts" = null /* object */
"zone_ids" = tolist([
"apne2-az1",
"apne2-az2",
"apne2-az3",
"apne2-az4",
])
}
변수 선언 방식
cd .. && rm -rf 3.5
mkdir 3.6 && cd 3.6
# variable 블록 선언의 예
variable "<이름>" {
<인수> = <값>
}
variable "image_id" {
type = string
}
테라폼 예약 변수 이름으로 사용 불가능 : source, version, providers, count, for_each, lifecycle, depends_on, locals
변수 정의 시 사용 가능한 메타인수
변수 유형
지원되는 변수의 범주와 형태
입력 변수 사용 예시
variable "number_example" {
description = "An example of a number variable in Terraform"
type = number
default = 42
}
variable "list_example" {
description = "An example of a list in Terraform"
type = list
default = ["a", "b", "c"]
}
variable "list_numeric_example" {
description = "An example of a numeric list in Terraform"
type = list(number)
default = [1, 2, 3]
}
variable "map_example" {
description = "An example of a map in Terraform"
type = map(string)
default = {
key1 = "value1"
key2 = "value2"
key3 = "value3"
}
}
variable "object_example" {
description = "An example of a structural type in Terraform"
type = object({
name = string
age = number
tags = list(string)
enabled = bool
})
default = {
name = "value1"
age = 42
tags = ["a", "b", "c"]
enabled = true
}
}
variable "string" {
type = string
description = "var String"
default = "myString"
}
variable "number" {
type = number
default = 123
}
variable "boolean" {
default = true
}
variable "list" {
default = [
"google",
"vmware",
"amazon",
"microsoft"
]
}
output "list_index_0" {
value = var.list.0
}
output "list_all" {
value = [
for name in var.list : upper(name)
]
}
variable "map" { # Sorting
default = {
aws = "amazon",
azure = "microsoft",
gcp = "google"
}
}
variable "set" { # Sorting
type = set(string)
default = [
"google",
"vmware",
"amazon",
"microsoft"
]
}
variable "object" {
type = object({ name = string, age = number })
default = {
name = "abc"
age = 12
}
}
variable "tuple" {
type = tuple([string, number, bool])
default = ["abc", 123, true]
}
variable "ingress_rules" { # optional ( >= terraform 1.3.0)
type = list(object({
port = number,
description = optional(string),
protocol = optional(string, "tcp"),
}))
default = [
{ port = 80, description = "web" },
{ port = 53, protocol = "udp" }]
}
#
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
#
terraform output
list_all = [
"GOOGLE",
"VMWARE",
"AMAZON",
"MICROSOFT",
]
list_index_0 = "google"
유효성 검사
입력되는 변수 타입 지징 이외, 사용자 지정 유효성 검사가 가능
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami
...
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami-
...
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami-12345678
...
변수 참조
mkdir 4
cd 4
variable "my_password" {}
resource "local_file" "abc" {
content = var.my_password
filename = "${path.module}/abc.txt"
}
#
terraform init -upgrade
terraform apply -auto-approve
var.my_password
Enter a value: qwe123
...
# 확인
terraform state list
terraform state show local_file.abc
cat abc.txt ; echo
# 해당 파일에 다른 내용으로 변경해보기
terraform apply -auto-approve
var.my_password
Enter a value: t101mypss
...
# 확인
cat abc.txt ; echo
민감한 변수 취급
입력 변수의 민감 여부 선언 가능
variable "my_password" {
default = "password"
sensitive = true
}
resource "local_file" "abc" {
content = var.my_password
filename = "${path.module}/abc.txt"
}
cat terraform.tfstate | grep '"content":'
"content": "password",
변수 입력 방식과 우선순위
variable "my_var" {}
resource "local_file" "abc" {
content = var.my_var
filename = "${path.module}/abc.txt"
}
[우선순위 수준 1] 실행 후 입력
# 실행
terraform apply -auto-approve
var.my_var
Enter a value: var1
...
# 확인
terraform state show local_file.abc
cat abc.txt ; echo
[우선순위 수준 2] variable 블록의 default 값
variable "my_var" {
default = "var2"
}
resource "local_file" "abc" {
content = var.my_var
filename = "${path.module}/abc.txt"
}
# 실행
terraform apply -auto-approve
# 확인
terraform state show local_file.abc
cat abc.txt ; echo
[우선순위 수준 3] 환경 변수 (TF_VAR 변수 이름)
# Linux/macOS
export TF_VAR_my_var=var3
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
[우선순위 수준 4] terraform.tfvars에 정의된 변수 선언
#
echo 'my_var="var4"' > terraform.tfvars
cat terraform.tfvars
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
[우선순위 수준 5] *.auto.tfvars에 정의된 변수 선언
# a.auto.tfvars 파일 생성
echo 'my_var="var5_a"' > a.auto.tfvars
ls *.tfvars
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
# b.auto.tfvars 파일 생성
echo 'my_var="var5_b"' > b.auto.tfvars
ls *.tfvars
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
[우선순위 수준 6] *.auto.tfvars.json에 정의된 변수 선언
# a.auto.tfvars.json 파일 생성
cat <<EOF > a.auto.tfvars.json
{
"my_var" : "var6_a"
}
EOF
ls *.tfvars ; ls *.json
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
# c.auto.tfvars.json 파일 생성
cat <<EOF > c.auto.tfvars.json
{
"my_var" : "var6_c"
}
EOF
ls *.tfvars ; ls *.json
#
terraform apply -auto-approve
# 확인
cat abc.txt ; echo
[우선순위 수준 7] CLI 실행 시 -var 인수에 지정 또는 -var-file로 파일 지정
#
terraform apply -auto-approve -var=my_var=var7
cat abc.txt ; echo
#
terraform apply -auto-approve -var=my_var=var7 -var=my_var=var8
cat abc.txt ; echo
# var9.txt 파일 생성
echo 'my_var="var9"' > var9.txt
#
terraform apply -auto-approve -var=my_var=var7 -var-file="var9.txt"
cat abc.txt ; echo
output 선언
출력 값은 주로 테라폼 코드의 프로비저닝 수행 후의 결과 속성 값을 확인하는 용도로 사용
output "instance_ip_addr" {
value = "http://${aws_instance.server.private_ip}"
}
output 활용
resource "local_file" "abc" {
content = "abc123"
filename = "${path.module}/abc.txt"
}
output "file_id" {
value = local_file.abc.id
}
output "file_abspath" {
value = abspath(local_file.abc.filename)
}
# plan 실행 시, 이미 정해진 속성은 출력을 예측하지만
terraform init && terraform plan
...
Changes to Outputs:
+ file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
+ file_id = (known after apply)
#
terraform apply -auto-approve
...
Outputs:
file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
file_id = "6367c48dd193d56ea7b0baad25b19455e529f5ee"
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
#
terraform state list
terraform outputfile_abspath = "/Users/leeeuijoo/Downloads/playground/euijoo/learn-terraform/6/abc.txt"
file_id = "6367c48dd193d56ea7b0baad25b19455e529f5ee"
> local_file.abc
{
"content" = "abc123"
"content_base64" = tostring(null)
"content_base64sha256" = "bKE9UspwyIPg8LsQHkJaiehiTeUdstI5JZOvaoQRgJA="
"content_base64sha512" = "xwtd2ev7b1HQnUEytxcMnSB1CnhS8AaA9lZY8DEOgQBW5nY8NMmgCw6UAHb1RJXBafwjAszrMSA5JxxDRpUH3A=="
"content_md5" = "e99a18c428cb38d5f260853678922e03"
"content_sha1" = "6367c48dd193d56ea7b0baad25b19455e529f5ee"
"content_sha256" = "6ca13d52ca70c883e0f0bb101e425a89e8624de51db2d2392593af6a84118090"
"content_sha512" = "c70b5dd9ebfb6f51d09d4132b7170c9d20750a7852f00680f65658f0310e810056e6763c34c9a00b0e940076f54495c169fc2302cceb312039271c43469507dc"
"directory_permission" = "0777"
"file_permission" = "0777"
"filename" = "./abc.txt"
"id" = "6367c48dd193d56ea7b0baad25b19455e529f5ee"
"sensitive_content" = (sensitive value)
"source" = tostring(null)
}
> local_file.abc.id
"6367c48dd193d56ea7b0baad25b19455e529f5ee"
# 신규 디렉터리 생성
mkdir my-vpc-ec2
cd my-vpc-ec2
provider "aws" {
region = "ap-northeast-2"
profile = "ejl-personal"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
tags = {
Name = "aews-study"
}
}
# 배포
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
aws_vpc.myvpc
terraform state show aws_vpc.myvpc
# aws_vpc.myvpc:
resource "aws_vpc" "myvpc" {
arn = "arn:aws:ec2:ap-northeast-2:236747833953:vpc/vpc-0449975439109dbbe"
assign_generated_ipv6_cidr_block = false
cidr_block = "10.10.0.0/16"
default_network_acl_id = "acl-0aaed6758221148c1"
default_route_table_id = "rtb-08199dc361c392daf"
default_security_group_id = "sg-0e10474b7c1852e87"
dhcp_options_id = "dopt-0d9ce164026238cd5"
enable_dns_hostnames = false
enable_dns_support = true
enable_network_address_usage_metrics = false
id = "vpc-0449975439109dbbe"
instance_tenancy = "default"
ipv6_association_id = null
ipv6_cidr_block = null
ipv6_cidr_block_network_border_group = null
ipv6_ipam_pool_id = null
ipv6_netmask_length = 0
main_route_table_id = "rtb-08199dc361c392daf"
owner_id = "236747833953"
tags = {
"Name" = "aews-study"
}
tags_all = {
"Name" = "aews-study"
}
}
# VPC 확인
export AWS_PAGER=""
aws ec2 describe-vpcs | jq
{
"Vpcs": [
{
"CidrBlock": "10.10.0.0/16",
"DhcpOptionsId": "dopt-0d9ce164026238cd5",
"State": "available",
"VpcId": "vpc-0449975439109dbbe",
"OwnerId": "236747833953",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-0c088ee35c010c86d",
"CidrBlock": "10.10.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": [
{
"Key": "Name",
"Value": "aews-study"
}
]
},
{
"CidrBlock": "172.31.0.0/16",
"DhcpOptionsId": "dopt-0d9ce164026238cd5",
"State": "available",
"VpcId": "vpc-05d7198a174e03c3b",
"OwnerId": "236747833953",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-093061c3a95ffa41a",
"CidrBlock": "172.31.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": true
},
{
"CidrBlock": "172.16.0.0/16",
"DhcpOptionsId": "dopt-0d9ce164026238cd5",
"State": "available",
"VpcId": "vpc-0fe3665371c65b1ad",
"OwnerId": "236747833953",
"InstanceTenancy": "default",
"CidrBlockAssociationSet": [
{
"AssociationId": "vpc-cidr-assoc-0ebe8b3ad61284dfb",
"CidrBlock": "172.16.0.0/16",
"CidrBlockState": {
"State": "associated"
}
}
],
"IsDefault": false,
"Tags": [
{
"Key": "Name",
"Value": "euijoo-vpc"
}
]
}
]
}
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true # Default true 임
enable_dns_hostnames = true
tags = {
Name = "aews-study"
}
}
terraform plan && terraform apply -auto-approve
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "aews-study"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "t101-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "aews-subnet2"
}
}
output "aws_vpc_id" {
value = aws_vpc.myvpc.id
}
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
terraform state show aws_subnet.mysubnet1
terraform output
aws_vpc_id = "vpc-0449975439109dbbe"
terraform output aws_vpc_id
terraform output -raw aws_vpc_id
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
# 서브넷 확인
aws ec2 describe-subnets --output text
# 참고 : aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-<자신의 VPC ID>"
VPCID=$(terraform output -raw aws_vpc_id)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" | jq
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$VPCID" --output table
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "t101-study"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "t101-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "t101-subnet2"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "t101-igw"
}
}
output "aws_vpc_id" {
value = aws_vpc.myvpc.id
}
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "t101-study"
}
}
resource "aws_subnet" "mysubnet1" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "t101-subnet1"
}
}
resource "aws_subnet" "mysubnet2" {
vpc_id = aws_vpc.myvpc.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "t101-subnet2"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "t101-igw"
}
}
resource "aws_route_table" "myrt" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "t101-rt"
}
}
resource "aws_route_table_association" "myrtassociation1" {
subnet_id = aws_subnet.mysubnet1.id
route_table_id = aws_route_table.myrt.id
}
resource "aws_route_table_association" "myrtassociation2" {
subnet_id = aws_subnet.mysubnet2.id
route_table_id = aws_route_table.myrt.id
}
resource "aws_route" "mydefaultroute" {
route_table_id = aws_route_table.myrt.id
destination_cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.myigw.id
}
output "aws_vpc_id" {
value = aws_vpc.myvpc.id
}
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_route.mydefaultroute
aws_route_table.myrt
aws_route_table_association.myrtassociation1
aws_route_table_association.myrtassociation2
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
terraform state show aws_route.mydefaultroute
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
# 라우팅 테이블 확인
#aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=t101-rt' --query 'RouteTables[].Associations[].SubnetId'
aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=t101-rt' --output table
SG, EC2 배포
resource "aws_security_group" "mysg" {
vpc_id = aws_vpc.myvpc.id
name = "T101 SG"
description = "T101 Study SG"
}
resource "aws_security_group_rule" "mysginbound" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg.id
}
resource "aws_security_group_rule" "mysgoutbound" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.mysg.id
}
# 배포
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_route.mydefaultroute
aws_route_table.myrt
aws_route_table_association.myrtassociation1
aws_route_table_association.myrtassociation2
aws_security_group.mysg
aws_security_group_rule.mysginbound
aws_security_group_rule.mysgoutbound
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
...
terraform state show aws_security_group.mysg
# aws_security_group.mysg:
resource "aws_security_group" "mysg" {
arn = "arn:aws:ec2:ap-northeast-2:236747833953:security-group/sg-04d1521da7cb57363"
description = "T101 Study SG"
egress = []
id = "sg-04d1521da7cb57363"
ingress = []
name = "T101 SG"
name_prefix = null
owner_id = "236747833953"
revoke_rules_on_delete = false
tags_all = {}
vpc_id = "vpc-0449975439109dbbe"
}
~/Dow/playground/euijoo/learn-terraform/my-vpc-ec2 terraform state show aws_security_group_rule.mysginbound ✔ ejl-personal ap-northeast-2 15:55:30
# aws_security_group_rule.mysginbound:
resource "aws_security_group_rule" "mysginbound" {
cidr_blocks = [
"0.0.0.0/0",
]
from_port = 80
id = "sgrule-3070248179"
protocol = "tcp"
security_group_id = "sg-04d1521da7cb57363"
security_group_rule_id = "sgr-0e64cf542b1ec3b4d"
self = false
to_port = 80
type = "ingress"
}
~/Dow/playground/euijoo/learn-terraform/my-vpc-ec2 terraform state show aws_security_group_rule.mysgoutbound ✔ ejl-personal ap-northeast-2 15:55:55
# aws_security_group_rule.mysgoutbound:
resource "aws_security_group_rule" "mysgoutbound" {
cidr_blocks = [
"0.0.0.0/0",
]
from_port = 0
id = "sgrule-2880335876"
protocol = "-1"
security_group_id = "sg-04d1521da7cb57363"
security_group_rule_id = "sgr-0faa376cedd84cc61"
self = false
to_port = 0
type = "egress"
}
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
data "aws_ami" "my_amazonlinux2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
owners = ["amazon"]
}
resource "aws_instance" "myec2" {
depends_on = [
aws_internet_gateway.myigw
]
ami = data.aws_ami.my_amazonlinux2.id
associate_public_ip_address = true
instance_type = "t2.micro"
vpc_security_group_ids = ["${aws_security_group.mysg.id}"]
subnet_id = aws_subnet.mysubnet1.id
user_data = <<-EOF
#!/bin/bash
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
mv busybox-x86_64 busybox
chmod +x busybox
echo "Web Server</h1>" > index.html
nohup ./busybox httpd -f -p 80 &
EOF
user_data_replace_on_change = true
tags = {
Name = "t101-myec2"
}
}
output "myec2_public_ip" {
value = aws_instance.myec2.public_ip
description = "The public IP of the Instance"
}
#
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
data.aws_ami.my_amazonlinux2
aws_instance.myec2
aws_internet_gateway.myigw
aws_route.mydefaultroute
aws_route_table.myrt
aws_route_table_association.myrtassociation1
aws_route_table_association.myrtassociation2
aws_security_group.mysg
aws_security_group_rule.mysginbound
aws_security_group_rule.mysgoutbound
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc
...
terraform state show data.aws_ami.my_amazonlinux2
terraform state show aws_instance.myec2
# 데이터소스 값 확인
terraform console
>
data.aws_ami.my_amazonlinux2.id
"ami-01c81850a6167bb81"
data.aws_ami.my_amazonlinux2.image_id
data.aws_ami.my_amazonlinux2.name
data.aws_ami.my_amazonlinux2.owners
data.aws_ami.my_amazonlinux2.platform_details
data.aws_ami.my_amazonlinux2.hypervisor
data.aws_ami.my_amazonlinux2.architecture
exit
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
# 출력된 EC2 퍼블릭IP로 cul 접속 확인
terraform output -raw myec2_public_ip
52.79.154.3
MYIP=$(terraform output -raw myec2_public_ip)
while true; do curl --connect-timeout 1 http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
terraform destroy -auto-approve
프로바이더 소개
테라폼 레지스트리의 프로바이더 목록에는 유지 보수 및 게시에 대한 권한에 따라 Tier 정보가 제공
로컬 이름과 프로바이더 지정
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"
}
}
단일 프로바이더의 다중 정의
동일한 프로바이더를 사용하지만 다른 조건을 갖는 경우, 사용되는 리소스마다 별도로 선언된 프로바이더를 지정해야 하는 경우가 있다. 예를 들면, AWS 프로바이더를 사용하는데 서로 다른 권한의 IAM을 갖는 Access ID 또는 대상 리전을 지정해야 하는 경우다. 이때는 프로바이더 선언에서 alias를 명시하고 사용하는 리소스와 데이터 소스에서는 provider 메타인수를 사용해 특정 프로바이더를 지정할 수 있다. provider 메타인수에 지정되지 않은 경우 alias가 없는 프로바이더가 기본 프로바이더로 동작한다.
실습을 위해서 4.1 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
mkdir 4.1 && cd 4.1
아래 예제는 리전을 다르게 구성한 AWS 프로바이더를 aws_instance에 지정하는 방법이다
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
echo "aws_instance.app_server1.public_ip" | terraform console
echo "aws_instance.app_server2.public_ip" | terraform console
aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table
aws ec2 describe-instances --filters Name=instance-state-name,Values=running --output table --region ap-southeast-1
# 삭제
terraform destroy -auto-approve
프로바이더 요구사항 정의
테라폼 실행 시 요구되는 프로바이더 요구사항은 terraform 블록의 required_providers 블록에 여러 개를 정의할 수 있다. source에는 프로바이더 다운로드 경로를 지정하고 version은 버전 제약을 명시한다.
프로바이더 요구사항 정의 블록
terraform {
required_providers {
<프로바이더 로컬 이름> = {
source = [<호스트 주소>/]<네임스페이스>/<유형>
version = <버전 제약>
}
...
}
}
프로바이더는 기능이나 조건이 시간이 지남에 따라 변경될 수 있다. 이 같은 변경에 특정 버전을 명시하거나 버전 호환성을 정의할 때, version에 명시할 수 있다. 이 값이 생략되는 경우 terraform init을 하는 당시의 가장 최신 버전으로 선택된다. 버전 상세 내용은 3.3장 버전 설정 확인
테라폼으로 인프라와 서비스를 관리하면 시간이 지날수록 구성이 복잡해지고 관리하는 리소스가 늘어나게 된다. 테라폼의 구성 파일과 디렉터리 구성에는 제약이 없기 때문에 단일 파일 구조상에서 지속적으로 업데이트할 수 있지만, 다음과 같은 문제가 발생한다.
모듈은 루트 모듈과 자식 모듈로 구분된다
모듈은 테라폼 구성의 집합이다. 테라폼으로 관리하는 대상의 규모가 커지고 복잡해져 생긴 문제를 보완하고 관리 작업을 수월하게 하기 위한 방안으로 활용
Module 작성 기본 원칙
모듈 작성 기본 원칙 : 모듈은 대부분의 프로그래밍 언어에서 쓰이는 라이브러리나 패키지와 역할이 비슷하다
아래와 같은 기본 작성 원칙을 제안함
모듈 디렉터리 형식을 terraform-<프로바이더 이름>-<모듈 이름>
형식을 제안한다. 이 형식은 Terraform Cloud, Terraform Enterprise에서도 사용되는 방식으로 1) 디렉터리 또는 레지스트리 이름이 테라폼을 위한 것이고, 2) 어떤 프로바이더의 리소스를 포함하고 있으며, 3) 부여된 이름이 무엇인지 판별할 수 있도록 한다.
테라폼 구성은 궁극적으로 모듈화가 가능한 구조로 작성할 것을 제안한다. 처음부터 모듈화를 가정하고 구성파일을 작성하면 단일 루트 모듈이라도 후에 다른 모듈이 호출할 것을 예상하고 구조화할 수 있다. 또한 작성자는 의도한 리소스 묶음을 구상한 대로 논리적인 구조로 그룹화할 수 있다.
각각의 모듈을 독립적으로 관리하기를 제안한다. 리모트 모듈을 사용하지 않더라도 처음부터 모듈화가 진행된 구성들은 떄로 루트 모듈의 하위 파일 시스템에 존재하는 경우가 있다. 하위 모듈 또한 독립적인 모듈이므로 루트 모듈 하위에 두기보다는 동일한 파일 시스템 레벨에 위치하거나 별도 모듈만을 위한 공간에서 불러오는 것을 권장한다. 이렇게 하면 VCS를 통해 관리하기가 더 수월하다.
공개된 테라폼 레지스트리의 모듈을 참고하기를 제안한다. 대다수의 테라폼 모듈은 공개된 모듈이 존재하고 거의 모든 인수에 대한 변수 처리, 반복문 적용 리소스, 조건에 따른 리소스 활성/비활성 등을 모범 사례로 공개해두었다. 물론 그대로 가져다 사용하는 것보다는 프로비저닝하려는 상황에 맞게 참고하는 것을 권장한다.
작성된 모듈은 공개 또는 비공개로 게시해 팀 또는 커뮤니티와 공유하기를 제안한다. 모듈의 사용성을 높이고 피드백을 통해 더 발전된 모듈을 구성할 수 있는 자극이 된다.
모듈을 독립적으로 관리하기 위해 디렉터리 구조를 생성할 때 모듈을 위한 별도 공간을 생성하는 방식으로 진행한다. 특정 루트 모듈 하위에 자식 모듈을 구성하는 경우 단순히 복잡한 코드를 분리하는 요도로 명시되며 종속성이 발생하므로 루트 모듈 사이에 모듈 디렉터리를 지정한다. 아래 구성의 예.
모듈화
모듈의 기본적 구조는 테라폼 구성으로 입력 변수를 구성하고 결과를 출력하기 위한 구조로 구성한다.
‘모듈화’라는 용어는 이런 구조를 재활용하기 위한 템플릿 작업을 말한다.
애플리케이션 개발시에도 자주 사용되는 용어로 테라폼은 작성된 모듈을 다른 루트 모듈에서 가져다 사용하며 이를 통해 재사용성과 표준화 구조를 구성할 수 있다.
기존에 작성된 모듈은 다른 모듈에서 참조해 사용할 수 있다. 사용 방식은 리소스와 비슷하다.
모듈에서 필요한 값은 variable로 선언해 설정하고, 모듈에서 생성된 값 중 외부 모듈에서 참조하고 싶은 값은 output으로 설정한다. 마치 자바 개발 시 getter, setter로 캡슐화된 클래스를 활용하는 것과 비슷한다.
모듈 작성 실습
자식 모듈 작성
mkdir -p 06-module-traning/modules/terraform-random-pwgen
# main.tf
resource "random_pet" "name" {
keepers = {
ami_id = timestamp()
}
}
resource "random_password" "password" {
length = var.isDB ? 16 : 10
special = var.isDB ? true : false
override_special = "!#$%*?"
}
# variable.tf
variable "isDB" {
type = bool
default = false
description = "패스워드 대상의 DB 여부"
}
# output.tf
output "id" {
value = random_pet.name.id
}
output "pw" {
value = nonsensitive(random_password.password.result)
}
ls
main.tf output.tf variable.tf
#
cd 06-module-traning/modules/terraform-random-pwgen
#
ls *.tf
terraform init && terraform plan
# 테스트를 위해 apply 시 변수 지정
terraform apply -auto-approve -var=isDB=true
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
id = "knowing-aardvark"
pw = "Y5eeP0i2KLLE9gBa"
# 확인
terraform state list
terraform state show random_pet.name
terraform state show random_password.password
# tfstate에 모듈 정보 확인
cat terraform.tfstate | grep module
# graph 확인
terraform graph > graph.dot
자식 모듈 호출 실습
mkdir -p 06-module-traning/06-01-basic
touch main.tf
module "mypw1" {
source = "../modules/terraform-random-pwgen"
}
module "mypw2" {
source = "../modules/terraform-random-pwgen"
isDB = true
}
output "mypw1" {
value = module.mypw1
}
output "mypw2" {
value = module.mypw2
}
#
cd 06-module-traning/06-01-basic
#
terraform init && terraform plan && terraform apply -auto-approve
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
mypw1 = {
"id" = "equipped-mustang"
"pw" = "OXST1EYqQc"
}
mypw2 = {
"id" = "diverse-impala"
"pw" = "y8mEbOJhS6dCTiK#"
}
# 확인
terraform state list
# tfstate에 모듈 정보 확인
cat terraform.tfstate | grep module
# terraform init 시 생성되는 modules.json 파일 확인
tree .terraform
.terraform
├── modules
│ └── modules.json
...
## 모듈로 묶여진 리소스는 module이라는 정의를 통해 단순하게 재활용하고 반복 사용할 수 있다.
## 모듈의 결과 참조 형식은 module.<모듈 이름>.<output 이름>으로 정의된다.
cat .terraform/modules/modules.json | jq
{
"Modules": [
{
"Key": "",
"Source": "",
"Dir": "."
},
{
"Key": "mypw1",
"Source": "../modules/terraform-random-pwgen",
"Dir": "../modules/terraform-random-pwgen"
},
{
"Key": "mypw2",
"Source": "../modules/terraform-random-pwgen",
"Dir": "../modules/terraform-random-pwgen"
}
]
}
# graph 확인
terraform graph > graph.dot
모듈 소스 관리
로컬 디렉터리 경로
module "local_module" {
source = "../modules/my_local_module"
}
테라폼 레지스트리
<네임스페이스>/<이름>/<프로바이더>
형태로 설정한다.module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.1.0"
}
aws vpc 모듈 실습
aws_vpc : https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest
Github : https://github.com/terraform-aws-modules/terraform-aws-vpc
Simple VPC : https://github.com/terraform-aws-modules/terraform-aws-vpc/tree/master/examples/simple
모듈 다운로드
#
git clone https://github.com/terraform-aws-modules/terraform-aws-vpc/
tree terraform-aws-vpc/examples -L 1
terraform-aws-vpc/examples
├── complete
├── ipam
├── ipv6-dualstack
├── ipv6-only
├── issues
├── manage-default-vpc
├── network-acls
├── outpost
├── secondary-cidr-blocks
├── separate-route-tables
├── simple
└── vpc-flow-logs
tree terraform-aws-vpc/examples -L 2
terraform-aws-vpc/examples
├── complete
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── ipam
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── ipv6-dualstack
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── ipv6-only
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── issues
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── manage-default-vpc
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── network-acls
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── outpost
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── secondary-cidr-blocks
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── separate-route-tables
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
├── simple
│ ├── README.md
│ ├── main.tf
│ ├── outputs.tf
│ ├── variables.tf
│ └── versions.tf
└── vpc-flow-logs
├── README.md
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
cd terraform-aws-vpc/examples/simple
ls *.tf
cat main.tf
provider "aws" {
region = local.region
}
data "aws_availability_zones" "available" {}
locals {
name = "ex-${basename(path.cwd)}"
region = "eu-west-1"
vpc_cidr = "10.0.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)
tags = {
Example = local.name
GithubRepo = "terraform-aws-vpc"
GithubOrg = "terraform-aws-modules"
}
}
################################################################################
# VPC Module
################################################################################
module "vpc" {
source = "../../"
name = local.name
cidr = local.vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
tags = local.tags
}
# 서울 리전 변경
grep 'eu-west-1' main.tf
sed -i -e 's/eu-west-1/ap-northeast-2/g' main.tf
# VPC CIDR 변경
grep '10.0.0.0' main.tf
sed -i -e 's/10.0.0.0/10.10.0.0/g' main.tf
# main.tf 파일 내용 확인
cat main.tf
provider "aws" {
region = local.region
}
data "aws_availability_zones" "available" {}
locals {
name = "ex-${basename(path.cwd)}"
region = "ap-northeast-2"
vpc_cidr = "10.10.0.0/16"
azs = slice(data.aws_availability_zones.available.names, 0, 3)
tags = {
Example = local.name
GithubRepo = "terraform-aws-vpc"
GithubOrg = "terraform-aws-modules"
}
}
################################################################################
# VPC Module
################################################################################
module "vpc" {
source = "../../"
name = local.name
cidr = local.vpc_cidr
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
tags = local.tags
}
# 모듈 확인
tree ../../modules
../../modules
└── vpc-endpoints
├── README.md
├── main.tf
├── outputs.tf
├── variables.tf
└── versions.tf
2 directories, 5 files
#
terraform init
cat .terraform/modules/modules.json| jq
{
"Modules": [
{
"Key": "",
"Source": "",
"Dir": "."
},
{
"Key": "vpc",
"Source": "../..",
"Dir": "../.."
}
]
}
# 생성된 리소스들 확인해보자!
terraform apply -auto-approve
terraform output
terraform state list
data.aws_availability_zones.available
module.vpc.aws_default_network_acl.this[0]
module.vpc.aws_default_route_table.default[0]
module.vpc.aws_default_security_group.this[0]
module.vpc.aws_route_table.private[0]
module.vpc.aws_route_table.private[1]
module.vpc.aws_route_table.private[2]
module.vpc.aws_route_table_association.private[0]
module.vpc.aws_route_table_association.private[1]
module.vpc.aws_route_table_association.private[2]
module.vpc.aws_subnet.private[0]
module.vpc.aws_subnet.private[1]
module.vpc.aws_subnet.private[2]
module.vpc.aws_vpc.this[0]
# 실습 완료 후 리소스 삭제
terraform destroy -auto-approve
첫 번째 EKS 클러스터 배포
# 코드 가져오기
git clone https://github.com/gasida/aews-cicd.git
cd aews-cicd/4
# terraform 환경 변수 저장
export TF_VAR_KeyName=[각자 ssh keypair]
export TF_VAR_KeyName='ejl-eks'
echo $TF_VAR_KeyName
#
terraform init
terraform plan
# 10분 후 배포 완료
terraform apply -auto-approve
terraform state list
data.aws_caller_identity.current
data.aws_eks_cluster.cluster
data.aws_eks_cluster_auth.cluster
aws_iam_policy.external_dns_policy
aws_iam_role_policy_attachment.external_dns_policy_attach
aws_security_group.node_group_sg
kubernetes_service_account.aws_lb_controller
module.aws_load_balancer_controller_irsa_role.data.aws_caller_identity.current
module.aws_load_balancer_controller_irsa_role.data.aws_iam_policy_document.load_balancer_controller[0]
module.aws_load_balancer_controller_irsa_role.data.aws_iam_policy_document.this[0]
module.aws_load_balancer_controller_irsa_role.data.aws_partition.current
module.aws_load_balancer_controller_irsa_role.aws_iam_policy.load_balancer_controller[0]
module.aws_load_balancer_controller_irsa_role.aws_iam_role.this[0]
module.aws_load_balancer_controller_irsa_role.aws_iam_role_policy_attachment.load_balancer_controller[0]
module.eks.data.aws_caller_identity.current
module.eks.data.aws_eks_addon_version.this["coredns"]
module.eks.data.aws_eks_addon_version.this["kube-proxy"]
module.eks.data.aws_eks_addon_version.this["vpc-cni"]
module.eks.data.aws_iam_policy_document.assume_role_policy[0]
module.eks.data.aws_iam_session_context.current
module.eks.data.aws_partition.current
module.eks.data.tls_certificate.this[0]
module.eks.aws_cloudwatch_log_group.this[0]
module.eks.aws_ec2_tag.cluster_primary_security_group["Environment"]
module.eks.aws_ec2_tag.cluster_primary_security_group["Terraform"]
module.eks.aws_eks_access_entry.this["admin"]
module.eks.aws_eks_access_policy_association.this["admin_myeks"]
module.eks.aws_eks_addon.this["coredns"]
module.eks.aws_eks_addon.this["kube-proxy"]
module.eks.aws_eks_addon.this["vpc-cni"]
module.eks.aws_eks_cluster.this[0]
module.eks.aws_iam_openid_connect_provider.oidc_provider[0]
module.eks.aws_iam_policy.cluster_encryption[0]
module.eks.aws_iam_role.this[0]
module.eks.aws_iam_role_policy_attachment.cluster_encryption[0]
module.eks.aws_iam_role_policy_attachment.this["AmazonEKSClusterPolicy"]
module.eks.aws_iam_role_policy_attachment.this["AmazonEKSVPCResourceController"]
module.eks.aws_security_group.cluster[0]
module.eks.aws_security_group.node[0]
module.eks.aws_security_group_rule.cluster["ingress_nodes_443"]
module.eks.aws_security_group_rule.node["egress_all"]
module.eks.aws_security_group_rule.node["ingress_cluster_443"]
module.eks.aws_security_group_rule.node["ingress_cluster_4443_webhook"]
module.eks.aws_security_group_rule.node["ingress_cluster_6443_webhook"]
module.eks.aws_security_group_rule.node["ingress_cluster_8443_webhook"]
module.eks.aws_security_group_rule.node["ingress_cluster_9443_webhook"]
module.eks.aws_security_group_rule.node["ingress_cluster_kubelet"]
module.eks.aws_security_group_rule.node["ingress_nodes_ephemeral"]
module.eks.aws_security_group_rule.node["ingress_self_coredns_tcp"]
module.eks.aws_security_group_rule.node["ingress_self_coredns_udp"]
module.eks.time_sleep.this[0]
module.eks-external-dns.data.aws_region.current
module.vpc.aws_default_network_acl.this[0]
module.vpc.aws_default_route_table.default[0]
module.vpc.aws_default_security_group.this[0]
module.vpc.aws_eip.nat[0]
module.vpc.aws_internet_gateway.this[0]
module.vpc.aws_nat_gateway.this[0]
module.vpc.aws_route.private_nat_gateway[0]
module.vpc.aws_route.public_internet_gateway[0]
module.vpc.aws_route_table.private[0]
module.vpc.aws_route_table.public[0]
module.vpc.aws_route_table_association.private[0]
module.vpc.aws_route_table_association.private[1]
module.vpc.aws_route_table_association.private[2]
module.vpc.aws_route_table_association.public[0]
module.vpc.aws_route_table_association.public[1]
module.vpc.aws_route_table_association.public[2]
module.vpc.aws_subnet.private[0]
module.vpc.aws_subnet.private[1]
module.vpc.aws_subnet.private[2]
module.vpc.aws_subnet.public[0]
module.vpc.aws_subnet.public[1]
module.vpc.aws_subnet.public[2]
module.vpc.aws_vpc.this[0]
module.eks.module.eks_managed_node_group["default"].data.aws_caller_identity.current
module.eks.module.eks_managed_node_group["default"].data.aws_iam_policy_document.assume_role_policy[0]
module.eks.module.eks_managed_node_group["default"].data.aws_partition.current
module.eks.module.eks_managed_node_group["default"].aws_eks_node_group.this[0]
module.eks.module.eks_managed_node_group["default"].aws_iam_role.this[0]
module.eks.module.eks_managed_node_group["default"].aws_iam_role_policy_attachment.additional["myeksExternalDNSPolicy"]
module.eks.module.eks_managed_node_group["default"].aws_iam_role_policy_attachment.this["AmazonEC2ContainerRegistryReadOnly"]
module.eks.module.eks_managed_node_group["default"].aws_iam_role_policy_attachment.this["AmazonEKSWorkerNodePolicy"]
module.eks.module.eks_managed_node_group["default"].aws_iam_role_policy_attachment.this["AmazonEKS_CNI_Policy"]
module.eks.module.eks_managed_node_group["default"].aws_launch_template.this[0]
module.eks.module.kms.data.aws_caller_identity.current[0]
module.eks.module.kms.data.aws_iam_policy_document.this[0]
module.eks.module.kms.data.aws_partition.current[0]
module.eks.module.kms.aws_kms_alias.this["cluster"]
module.eks.module.kms.aws_kms_key.this[0]
module.eks.module.eks_managed_node_group["default"].module.user_data.null_resource.validate_cluster_service_cidr
terraform console
-----------------
data.aws_caller_identity.current
data.aws_caller_identity.current.arn
data.aws_eks_cluster.cluster
data.aws_eks_cluster.cluster.name
data.aws_eks_cluster.cluster.version
data.aws_eks_cluster.cluster.access_config
data.aws_eks_cluster_auth.cluster
kubernetes_service_account.aws_lb_controller
-----------------
cat terraform.tfstate | jq
more terraform.tfstate
# Context 변경
aws eks --region ap-northeast-2 update-kubeconfig --name myeks
Added new context arn:aws:eks:ap-northeast-2:236747833953:cluster/myeks to /Users/leeeuijoo/.kube/config
#
kubectl get node -v=6
I0424 17:20:33.518495 74436 loader.go:395] Config loaded from file: /Users/leeeuijoo/.kube/config
I0424 17:20:34.376202 74436 round_trippers.go:553] GET https://BC66A6F469EDD8D6B70917EF36F12730.gr7.ap-northeast-2.eks.amazonaws.com/api?timeout=32s 200 OK in 853 milliseconds
I0424 17:20:34.400062 74436 round_trippers.go:553] GET https://BC66A6F469EDD8D6B70917EF36F12730.gr7.ap-northeast-2.eks.amazonaws.com/apis?timeout=32s 200 OK in 20 milliseconds
I0424 17:20:34.434645 74436 round_trippers.go:553] GET https://BC66A6F469EDD8D6B70917EF36F12730.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/nodes?limit=500 200 OK in 24 milliseconds
NAME STATUS ROLES AGE VERSION
ip-192-168-1-20.ap-northeast-2.compute.internal Ready <none> 7m42s v1.29.0-eks-5e0fdde
ip-192-168-2-32.ap-northeast-2.compute.internal Ready <none> 7m44s v1.29.0-eks-5e0fdde
ip-192-168-3-187.ap-northeast-2.compute.internal Ready <none> 7m45s v1.29.0-eks-5e0fdde
# EKS 클러스터 인증 정보 업데이트
CLUSTER_NAME=myeks
aws eks update-kubeconfig --region ap-northeast-2 --name $CLUSTER_NAME
kubectl config rename-context "arn:aws:eks:ap-northeast-2:$(aws sts get-caller-identity --query 'Account' --output text):cluster/$CLUSTER_NAME" "Aews-Labs"
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* Aews-Labs arn:aws:eks:ap-northeast-2:236747833953:cluster/myeks arn:aws:eks:ap-northeast-2:236747833953:cluster/myeks
docker-desktop docker-desktop docker-desktop
#
kubectl cluster-info
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
kubectl get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-node-2dptb 2/2 Running 0 8m32s
kube-system aws-node-8m5xd 2/2 Running 0 8m13s
kube-system aws-node-98qqh 2/2 Running 0 8m23s
kube-system coredns-67dc5c9f9d-8w7w5 1/1 Running 0 8m32s
kube-system coredns-67dc5c9f9d-v8h8j 1/1 Running 0 8m33s
kube-system kube-proxy-kmlcb 1/1 Running 0 8m23s
kube-system kube-proxy-vzf26 1/1 Running 0 8m32s
kube-system kube-proxy-zbb4j 1/1 Running 0 8m26s
# ExternalDNS
MyDomain=<자신의 도메인>
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
--set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller
# gp3 스토리지 클래스 생성
kubectl apply -f https://raw.githubusercontent.com/gasida/PKOS/main/aews/gp3-sc.yaml
# CLB는 terraform으로 배포하지 않고 직접 수동으로 배포되었으니, 수동으로 삭제를 해주어야 함
helm uninstall kube-ops-view --namespace kube-system
# 클러스터 삭제
terraform destroy -auto-approve
두 번째 EKS 클러스터 베포 : 코드 재사용 검증
# 디렉토리 이동
cd ..
mkdir 5
cd 5
cp ../4/*.tf .
ls
# 배포
terraform init
terraform apply -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"
# EKS 클러스터 인증 정보 가져오기
CLUSTER_NAME2=myeks2
aws eks update-kubeconfig --region ap-northeast-2 --name $CLUSTER_NAME2 --kubeconfig ./myeks2config
Added new context arn:aws:eks:ap-northeast-2:236747833953:cluster/myeks2 to /Users/leeeuijoo/Downloads/playground/euijoo/learn-terraform/aews-cicd/5/myeks2config
# EKS 클러스터 정보 확인
kubectl --kubeconfig ./myeks2config get node
NAME STATUS ROLES AGE VERSION
ip-192-168-1-163.ap-northeast-2.compute.internal Ready <none> 2m32s v1.28.5-eks-5e0fdde
ip-192-168-2-118.ap-northeast-2.compute.internal Ready <none> 2m31s v1.28.5-eks-5e0fdde
ip-192-168-3-169.ap-northeast-2.compute.internal Ready <none> 2m32s v1.28.5-eks-5e0fdde
kubectl --kubeconfig ./myeks2config get pod -A
NAMESPACE NAME READY STATUS RESTARTS AGE
kube-system aws-node-2zlpz 2/2 Running 0 2m34s
kube-system aws-node-b6c5q 2/2 Running 0 2m43s
kube-system aws-node-h4kxf 2/2 Running 0 2m27s
kube-system coredns-55474bf7b9-4x4zz 1/1 Running 0 2m44s
kube-system coredns-55474bf7b9-fm8lb 1/1 Running 0 2m44s
kube-system kube-proxy-5f6zz 1/1 Running 0 2m36s
kube-system kube-proxy-7vtww 1/1 Running 0 2m40s
kube-system kube-proxy-xk9c7 1/1 Running 0 2m44s
삭제 : 디렉터리(폴더) 진입 후 각각 삭제
# CLB는 terraform으로 배포하지 않고 직접 수동으로 배포되었으니, 수동으로 삭제를 해주어야 함
helm uninstall kube-ops-view --namespace kube-system
# 클러스터 삭제
terraform destroy -auto-approve
# 클러스터 삭제
terraform destroy -auto-approve -var=ClusterBaseName=myeks2 -var=KubernetesVersion="1.28"