2024. 6. 23. 00:46ㆍEKS@Terraform
- CloudNet에서 주관하는 Terraform 스터디 내용입니다
- 내용은 위 책 테라폼으로 시작하는 IaC 를 기준으로 정리하였습니다.
- 실습은 M1 macbook air 에서 진행했습니다.
- 매번 좋은 스터디를 진행해 주시는 CloudNet 팀 감사드립니다
- 잘못된 점, 업데이트된 지식, 다른 코멘트 언제나 환영입니다!
목차
1. Terraform 문법
2. 실습 1: VPC + 보안그룹 + EC2 배포
3. 실습 2: AWS IAM user 배포
4. 실습 3: 반복문 사용
5. 실습 4: 반복문 사용+
Terraform 문법
데이터 소스
테라폼의 '데이터 소스' 는 테라폼으로 정의되지 않은 외부 리소스 또는 정보를 가져올 때 사용한다. 사용 사례는
- 기존 인프라 참조
- 동적 데이터 조회
- 계정 정보 조회
- 외부 DB에서 정보 참조
- 다른 테라폼 리소스의 속성 값 참조
등이 있다.
data "local_file" "abc" {
filename = "${path.module}/abc.txt"
}
이 예시를 보면서 간단히 문법을 살펴보자.
1. 우선 data 로 시작해서 프로바이더명_리소스명 으로 데이터 소스 유형 정의: 여기서는 로컬의 .txt를 사용, "local_file"이 유형이 된다.
2. 데이터 소스 이름 정의: 여기서는 "abc" 가 된다.
아래 코드를 이용해서 실습에 필요한 텍스트 파일을 생성하고 읽어올 수 있다.
# 참조할 파일 생성
echo "t101 study - 2week" > abc.txt
# 테라폼에서 불러오기
terraform init
terraform plan
terraform apply -auto-approve
terraform state list
그래프로 그리면 depency 는 없이 단독이다.
이번에는 aws provider 로 서울리전에서 사용 가능한 zone을 가져와보자.
data "aws_availability_zones" "seoul" {
state = "available"
}
위와 마찬가지로 init -> plan -> apply 후, show 명령어로 결과를 텍스트로 확인할 수 있다.
terraform state show data.aws_availability_zones.seoul
서울리전은 a,b,c,d 로 나뉘어있고 모두 활용가능하다.
입력변수
테라폼의 '입력 변수' 는 인프라 구성에 필요한 속성값을 정의하고 코드를 변경하지 않기 위해 사용된다.
- 환경별 구성: 같은 인프라 구성에서 개발서버에는 저성능 인스턴스, 실서버에는 고성능 인스턴스로 유형만 변경할 수 있다.
- secrets 별도 관리: 키를 코드에 직접 포함시키지 않고 필요할 때마다 읽어서 쓸 수 있다(데이터 소스는 읽어서 저장하는 것과 다른 듯)
- 리소스 수량 조절: '환경별 구성' 과 유사
- 태그, 지역 관리: 리전별로 다른 AMI ID나 버전관리에 필요한 태그 등에 사용
변수 유형: string, number, bool, any
집합 유형: list, map, set, object, tuple
메타 인수: default, type, description, validation, sensitive, nullable
이 있으니 아래 예시를 보고 필요한 걸 검색해서 쓰도록 하자.
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" }]
}
실행시에는 마찬가지로 init -> plan -> apply 순서로 하는데, terraform output 명령으로 결과를 프린트할 수 있다.
terraform init
terraform plan
terraform apply -auto-approve
# 확인
terraform output
변수 우선순위가 지정되어 있어서 같은 변수에 다른 내용이 지정되어 있으면 우선순위가 높은 게 사용된다.
1. 실행 후 터미널에 입력
2. variable 블록의 디폴트 값
3. 환경변수
4. terraform.tfvars에 정의된 변수
5. *.auto.tfvars 에 저장된 변수
6. *.auto.tfvars.json 에 저장된 변수
7. cli 실행시 -var 나 -var-file 로 지정
실습 1: VPC + 보안그룹 + EC2 배포
- default VPC 대신 직접 만든 VPC 에 EC2 1대를 배포해본다
vpc 를 배포하는 테라폼 코드는 아래와 같다
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_vpc" "myvpc" {
cidr_block = "10.10.0.0/16"
tags = {
Name = "t101-study"
}
}
- 아래와 같이 init -> plan -> apply 순서로 적용하여 vpc 를 배포한다
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
- aws cli를 이용해서 jq로 검색해서 확인할 수도 있다.
export AWS_PAGER=""
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false'** --output yaml
- vpc 가 배포되었으나, 이제 우리는 ec2 로 직접 접근을 시도할 것이기 때문에 게이트웨이와 라우팅 설정이 추가로 필요하다. 아래 코드를 적용하고 plan -> apply 해서 게이트웨이를 열고 디폴트 라우팅 경로를 지정한다
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
}
- plan-apply 로 배포하면 vpc 는 수정되고, list 로 gateway, default route, route table, subnet 2개, Table association 2개 총 7개의 리소스가 추가로 배포되었음을 볼 수 있다.
- 이번에는 실습을 위한 보안그룹을 배포해보자. 마찬가지로 my-vpc-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
}
output "aws_security_group_id" {
value = aws_security_group.mysg.id
}
security group, inbound rule, outbound rule 3개가 잘 배포되었다.
아래 jq 명령어로도 배포된 보안그룹을 검색할 수 있다
aws ec2 describe-security-groups --group-ids $(terraform output -raw aws_security_group_id)
- 마지막으로 ec2 를 배포한다.
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
RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : 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"
}
- plan-apply 로 배포를 성공했다. 최종적으로 curl 로 인스턴스에 직접 접근해보도록 하겠다
while true; do curl --connect-timeout 1 \
http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
- terraform graph 명령어로 서로의 의존관계를 시각화했다. vpc 내부에 보안그룹과 인스턴스가 있으며 이 인스턴스는 다시 게이트웨이와 라우팅 테이블을 통해 외부와 통신함을 알 수 있다.
그리고 terraform destory 로 이 모든 것을 쉽고 빠르고 간편하게 삭제할 수 있다.
실습 2: AWS IAM user 배포
두번째로 IAM user 를 생성해보았다. 새 디렉토리를 생성하고 iamuser.tf 파일을 작성한다
provider "aws" {
region = "ap-northeast-2"
}
locals {
name = "mytest"
team = {
group = "dev"
}
}
resource "aws_iam_user" "myiamuser1" {
name = "${local.name}1"
tags = local.team
}
resource "aws_iam_user" "myiamuser2" {
name = "${local.name}2"
tags = local.team
}
aws 콘솔과 terraform terminal 에서 유저가 잘 생성되었음을 확인할 수 있다
- aws console 에서도 생성된 iam user를 확인할 수 있다. mytest1 과 mytest2 라는 유저가 새로 생겼음을 볼 수 있따.
aws iam list-users | jq
- terraform graph 명령어로 dependency 를 생성했을 때, 두 user는 완전히 독립 관계임을 확인할 수 있다.
- 이번에는 선택 삭제를 해보겠다. destroy 에서 -target 옵션을 주면 원하는 리소스만 골라 삭제할 수 있다
terraform destroy -auto-approve -target=aws_iam_user.myiamuser1
mytest1 만 골라서 삭제된 것을 확인할 수 있다
실습 3: 반복문 사용
테라폼의 꽃🌸 이라는 반복문. 동일한 내용을 반복 하드코딩 하지 않을 수 있다는 점이 매력적이다. 별도 모듈로 빼서 관리하기도 편할 것 같다. 반복문에는 count 와 for_each가 있다. for_each 가 인덱스에서 자유로워서 좀더 강건하므로 사용하는 것이 좋겠지만 우선 count 를 사용해보고, 실습 4에서 for_each 를 사용해보았다.
현재 주언어를 python으로 쓰는 사람의 관점에서 설명하면 list를 순회할 때 range로 인덱스를 사용하는 건 count, 그냥 바로 변수로 순회하는 건 for_each에 가깝다고 생각된다.
alphabet = ['a', 'b', 'c', 'd', 'e']
# count와 비슷한 list 순회
for i in range(len(alphabet)):
print(alphabet[i])
# for_each 와 비슷한 list 순회
for character in alphabet:
print(character)
# 출력결과는 둘다 같다
우선 variables 가 필요하다. list 형태로 되어 있어서 순회할 수 있다. 그리고 리소스에서는 이 선언한 variables 의 길이를 가져와서 그만큼 루프를 돌면서 인덱스로 사용하고, 최종적으로 그 인덱스를 사용하는 형식이다.
variable "names" {
type = list(string)
default = ["a", "b", "c"]
}
resource "local_file" "abc" {
count = length(var.names)
content = "abc"
filename = "${path.module}/abc-${var.names[count.index]}.txt"
}
resource "local_file" "def" {
count = length(var.names)
content = local_file.abc[count.index].content
filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}
이 테라폼은 별도의 리소스를 생성하는 것은 아니고 반복문을 돌면서 .txt 파일만 여러 개 생성한다. 결과물은 아래 사진과 같은 이름에 내용은 모두 abc 이다.
코드의 두번째 블럭을 설명해보면 local_file.abc 에서 인덱스가 같은 파일의 컨텐츠를 그대로 가져와서 제목을 def-x.txt 로 바꿔서 저장하는 것이다.
실습 4: 반복문 사용+
악분일상 님의 반복문 실습 영상을 보고 실습했다. 감사합니다! https://www.youtube.com/watch?v=enhSdIJ9xxQ
이 실습은 위 실습과는 분리되어 있기 때문에 따로 디렉토리를 파서 악분님 레포를 클론하여 환경을 구성했다.
git clone https://github.com/sungwook-practice/t101-study.git
실습 환경을 구성하기 위해 template 폴더의 terraform 을 apply 해서 vpc를 생성하고 정보를 확인한다.
생성이 잘 되었다!
이제 템플릿에서 하나씩 요구사항을 추가해나갈 것이다.
그 전에 template 을 보면 아래와 같이 구성되어 있다.
실질적으로 우리가 건드려야 할 테라폼 파일은 main.tf, terraform.tfvars, variables.tf 이 3가지 이다
미션1: Subnet 을 추가하고, subnet cidr 을 변수로 입력
--BEFORE--
# main.tf
resource "aws_vpc" "main" {
cidr_block = var.vpc_cidr
tags = {
Name = "terraform VPC"
}
}
# variables.tf
variable "vpc_cidr" {
type = string
}
# terraform.tfvars
vpc_cidr = "192.168.0.0/16"
--AFTER--
## main.tf 밑에 아래 내용 추가
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
cidr_block = var.subnet_cidr
} #subnet 생성
output "myvpc_id" {
value = aws_vpc.main.id
} #실습 편의를 위한 출력
## variables.tf 밑에 아래 내용 추가
variable "subnet_cidr" {
type = string
}
## terraform.tfvars 밑에 아래 내용 추가
subnet_cidr = "192.168.1.0/24"
[!DEBUG] creating EC2 Subnet: InvalidSubnet.Range: The CIDR '10.0.1.0/24' is invalid.
-> variables.tf 를 수정하고 저장하지 않았다
subnet이 잘 생성되었고 콘솔에서 output으로 subnet id 를 출력해준다..
콘솔의 subnets 페이지에서 VPC로 ID를 검색하면 생성된 subnet을 쉽게 찾을 수 있다.
미션2: count 를 사용해서 subnet cidr 변수 값을 여러 개 입력받는다
--BEFORE--
## main.tf 의 subnet resource 부분
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
count = length(var.subnet_cidr)
cidr_block = element(var.subnet_cidr, count.index)
}
## variables.tf 의 subnet 부분
variable "subnet_cidr" {
type = string
}
## terraform.tfvars 의 subnet_cidr 부분
subnet_cidr = "192.168.1.0/24"
--AFTER--
## main.tf 의 subnet resource 부분: *count* 를 사용해 List 를 돌면서 subnet 배포
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
count = length(var.subnet_cidr)
cidr_block = element(var.subnet_cidr, count.index)
}
## variables.tf 의 subnet 부분: string 을 List 로 감싸줌
variable "subnet_cidr" {
type = list(string)
}
## terraform.tfvars 의 subnet_cidr 부분: list 형태로 subnet 주소 제공
subnet_cidr = ["192.168.1.0/24", "192.168.2.0/24"]
마찬가지로 배포가 잘 되었다. 미션1과 동일하게 aws 콘솔에서도 확인할 수 있다
그러나 count는 인덱스를 참조하기 때문에 구성 인스턴스 중 하나가 삭제된다던지 하면 인덱스가 모두 변경되므로 더는 인덱스로 접근할 수 없게 된다. 따라서 각 인스턴스에 id를 부여하는 for_each 메소드를 사용하는 것이 좀 더 안정적이다. 자세한 내용은 공식문서: When to Use for_each Instead of count 를 참고하자
다른 실습시나리오에서 for_each를 사용해서 장애상황을 지연한 부분이 있었는데, 이것을 참조하여 count 대신 for_each를 사용해서 미션2의 subnet 여러 개 생성하기를 구현하고 하나씩 설명해보려 한다.
우선 for_each 문법은 map 과 set 형태의 집합 유형만 지원하므로, variables.tf 에서 list 형태를 map 형태로 바꿔줘야 한다. map 형태로 만들때는 키값도 같이 명시해 줘야 한다. 덤으로 태그도 같이 달거니까 키에 name 변수도 추가해주자.
--variables.tf--
## BEFORE
variable "subnet_cidr" {
type = list(string)
}
## AFTER
variable "subnet_cidr" {
type = map(object({
cidr_block = string
name = string
})
)
}
두번째로 terraform.tfvars 에서 현재 list 형태로 묶여있는 cidr_block 을 map 형태로 바꿔줘야 한다.새로 생긴 name 도 잊지말고 채운다.
--terraform.tfvars--
## BEFORE
subnet_cidr = ["192.168.1.0/24", "192.168.2.0/24"]
## AFTER
subnet_cidr = {
subnet1 = {
cidr_block = "192.168.1.0/24"
name = "Subnet 1"
},
subnet2 = {
cidr_block = "192.168.2.0/24"
name = "Subnet 2"
}
}
마지막으로 variable 이 적용될 main.tf 를 수정한다. count 와 마찬가지로 선언을 해줘야 하지만, 인덱스랑 묶어서 돌아가는 count 와는 달리 each 로 지정하게 되므로 좀더 robust 하다.
## BEFORE
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
count = length(var.subnet_cidr)
cidr_block = element(var.subnet_cidr, count.index)
}
## AFTER
resource "aws_subnet" "main" {
vpc_id = aws_vpc.main.id
for_each = var.subnet_cidr
cidr_block = each.value.cidr_block
tags = {
Name = each.value.name
}
}
output "subnet_ids" {
value = { for k, v in aws_subnet.main : k => v.id }
}
여기서도 배포가 잘 되었다
마지막으로
잊지 말아요 리소스 삭제
모든 폴더에서 삭제하는 것도 잊지 말아요
terraform destroy -auto-approve
정리하고 실습할 때는 엄청 오래 걸리는데 읽으니 정말 금방이네요
읽으시는 분들과 미래의 테라폼이 필요한 나에게 도움이 되었으면!
'EKS@Terraform' 카테고리의 다른 글
[Terraform@CloudNet] Terraform Runner: Atlantis (0) | 2024.07.13 |
---|---|
[Terraform@CloudNet] Terraform Module (0) | 2024.07.13 |
[Terraform@CloudNet] Terraform State (0) | 2024.07.07 |
[Terraform@CloudNet] Terraform Provider (0) | 2024.07.06 |
[Terraform@CloudNet] Terraform 기본 사용 (1) (2) | 2024.06.15 |