⭐ 가시다(gasida) 님이 진행하는 AWS EKS Workshop 실습 스터디 게시글입니다.
이번 게시글은 Terraform 에 관한 내용을 설명합니다.
Terraform
테라폼 소개: Hashicorp에서 공개한 Iac(Infrastructure as code) 도구
- 홈페이지 : https://www.terraform.io/
Terraform 의 Workflow 는 총 3단계로 구성됨
실습환경 준비
Terraform
#mac os(m1)
#terraform 1.5.1 version
brew install tfenv
tfenv list-remote
tfenv install 1.8.1
tfenv use 1.8.1
#설치한 버전확인
tfenv list
#테라폼 버전 정보 확인
terraform version
#자동완성
terraform -install-autocomplete
AWSCLI
brew install awscli
#자격증명
aws configure
EKSCTL
brew install eksctl
KUBECTL
brew install kubernetes-cli
HELM
brew install helm
기타
brew install tree jq watch
Default VPC 확인
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq '.Vpcs[0].VpcId'
#위에서 아무것도 안나온다면 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
Get Started AWS
기본 환경 준비
mkdir learn-terraform
cd learn-terraform
touch main.tf
Amazon Linux 2 최신 AMI id 찾기
#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
EC2생성 모니터링
# [터미널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
테라폼 코드 작성파일 : HCL(Hashicorp Configuration Language, BLOCK으로 구성)
- provider: Terraform으로 정의할 Infrastructure Provider를 의미함
- resource : 실제로 생성할 인프라 자원을 의미함
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
terraform show
terraform show aws_instance.example
결과 확인
EC2 태그 정보 수정 : 코드파일 수정
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
tags = {
Name = "aews-study"
}
}
EOT
# plan 실행 시 아래와 같은 정보가 출력
terraform plan
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
id = "ami-0a0064415cdedc552"
~ tags = {
+ "Name" = "t101-study"
}
~ tags_all = {
+ "Name" = "t101-study"
}
# (29 unchanged attributes hidden)
# (7 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
# apply 실행
terraform apply
Enter a value: yes 입력
# 모니터링 : [터미널1]에 Name 확인
결과확인
실습 삭제
#리소스 삭제
terraform destroy
#yes
HCL (HashiCorp Configuration LAnguage)
- IaC 는 수동 프로세스가 아닌, 코드를 통해 인프라를 관리하고 프로비저닝하는것을 의미한다.
- 테라폼에서 HCL 이 코드의 영역을 담당한다.
- HCL은 쉽게 읽을 수 있고, 빠르게 배울수 있다.
- 인프라는 코드로 표현되기떄문에, 선언적 특성을 갖게된다.
- 일반적인 프로그램이 언어의 조건문 처리 같은 동작이 가능하다.
HCL에서 변수와 문자열 값을 함께 사용하는 Interpolation 표현방식을 사용가능하다.
HCL을 이용한 테라폼 구성 | JSON을 이용한 CloudFormation 구성 |
---|---|
name = “{$var.PilotServerName}-vm” | “name”:”{”Fn::Join”:[”-”,[PilotServerName,vm]]}” |
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
}
BLOCK
Block : 테라폼 구성을 명시하는데 사용한다.
테라폼 버전, Provider 버전 등은 자동으로 설정되나, 함께 작업할때에는 버전을 명시적으로 선언하는것을 권장한다.
- 오늘 실행하던, 3년 후에 실행하던 동일한 결과를 얻을 수 있어야한다.
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"
}
}
- 테라폼 내에서 버전이 명시되는 terraform, module에서 사용 가능하며 버전에 대한 제약을 둠으로써 테라폼, 프로바이더, 모듈이 항상 의도한 정의대로 실행되는 것을 목적으로 한다.
- 버전 체계는 시맨틱 버전 관리 Semantic Versioning 방식을 따른다
# version = Major.Minor.Patch
version = 1.3.4
- 시맨틱 버전 관리 방식
- Major 버전 : 내부 동작의 API가 변경 또는 삭제되거나 하위 호환이 되지 않는 버전
- Minor 버전 : 신규 기능이 추가되거나 개선되고 하위 호환이 가능한 버전
- Patch 버전 : 버그 및 일부 기능이 개선된 하위 호환이 가능한 버전
프로바이더 버전
테라폼 v0.13 버전 이후부터, 프로바이더 버전은 required_providers에 정의
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.2.0"
}
azurerm = {
source = "hashicorp/azurerm"
version = ">= 2.99.0"
}
}
}
프로바이더의 이름에 소스경로, 버전을 명시하며
테라폼 레지스트리 공식 페이지에서 원하는 프로바이더 선택 → 우측 상단 → USER PROVIDER 클릭 → 테라폼 코드에 대응하는 샘플코드가 표기됨.
코드 파일 생성
mkdir 2
touch main.tf
vi main.tf
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
결과 : 버전이 너무 높아서 안된다고 에러남.
리소스 구성
resource: 리소스 블록은 선언된 항목을 생성하는 동작을 수행
- 실습을 위해서 03.end 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
cd ..
rm -rf 03.start
mkdir 03.end
cd 03.end
- 리소스 블록은 resource로 시작한다. 이후 리소스 블록이 생성할 ‘리소스 유형’을 정의한다.
- 리소스 선언 : 리소스 유형(
프로바이더이름
_제공리소스유형
), 동일한 유형에 대한 식별자 역할로 고유한 이름, 구성 인수들이 이름 뒤에 중괄호 내에 선언됨
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
resource "**local**_**file**" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
- 리소스에서 사용되는 유형들은 프로바이더에 종속성을 갖는다. 특정 프로바이더의 유형만 추가해도 init 수행 시 해당 프로바이더를 설치한다.
resource "**local**_file" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
resource "**aws**_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}
- init
# init 시 프로바이더 선언 없이도 리소스 추가로 자동 인식된 프로바이더 요구사항과 초기화
terraform init
tree .terraform
버전을 설정한지 않은 경우엔, latest의 버전을 가져온다.
- 리소스 동작 보조 추가 메타인수를 정의 할 수 있다 → 뒤에서 자세히 설명
- depends_on : 종속성을 선언하며, 선언된 구성요소와의 생성 시점에 대해 정의
- count : 선언된 개수에 따라 여러 리소스를 생성
- for_each : map 또는 set 타입의 데이터 배열의 값을 기준으로 여러 리소스를 생성
- provider : 동일한 프로바이더가 다수 정의되어 있는 경우 지정
- lifecycle : 리소스의 수명주기 관리
- provisioner : 리소스 생성 후 추가 작업 정의
- timeouts : 프로바이더에서 정의한 일부 리소스 유형에서는 create, update, delete에 대한 허용 시간 정의 가능
종속성
종속성 : 배포되는 요소의 순서를 구분지음
기본적으로 선후관계에 따라 요소간 연관관계가 정의되는 암시적 종속성을 갖게되는데, 이를 테라폼에서는 자동으로 처리해준다.
코드파일 수정 : 종속성 없음
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
Vscode 확장프로그램 : graphviz 설치
#
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 #출력 내용을 .dot파일로 떨구기
# 모든 리소스 제거
terraform destroy -auto-approve
ls *.txt
terraform state list
코드파일 수정 : 종속성 존재(abc → def)
- 리소스 참조값을 설정해 두 개의 리소스 간 암시적 종속성 부여
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"
}
- apply : 커맨드 생성에 순서가 발생한 종속성 있는 두 개의 리소스
#
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
cat def.txt
diff abc.txt def.txt
# graph 확인 > graph-2.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph
terraform graph > graph-2.dot
결과
- 리소스의 속성을 주입하지 않아도 두 리소스 간에 종속성이 필요한 경우에, depends_on 선언으로 적용 가능. dn
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"
}
- apply
#
terraform destroy -auto-approve
terraform apply -auto-approve
...
# graph 확인 > graph-3.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph
terraform graph > graph-3.dot
결과
리소스 속성 참조
리소스 구성과정에서 다른 리소스의 인수, 속성을 참조 가능하다.
# Terraform Code
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 리소스 참조
<리소스 유형>.<이름>.<인수>
<리소스 유형>.<이름>.<속성>
- 아래 코드는 쿠버네티스 프로바이더의 Namespace 리소스를 생성하고 이후 Secret을 해당 Namespace에 생성하는 종속성을 리소스 인수 값 값으로 생성하는 예이다. Namespace의 이름만 변경해도, 해당 Namespace를 참조하는 모든 리소스가 업데이트되어 영향을 받는다.
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"
}
}
데이터 소스 구성, 데이터 소스 속성 참조
데이터 소스 : 테라폼으로 정의 되지 않은 외부리소스, 저장된 정보를 테라폼 내에서 참조 할 때 사용
e.g. 특정한 리전의 가용영역 항목을 가져온다거나 할때에 사용함.
- 데이터 소스 블록은 data로 시작, 이후 ‘데이터 소스 유형’을 정의 ← Resource 블록 정의와 유사
- 데이터 소스 유형은 첫 번째 _를 기준으로 앞은 프로바이더 이름, 뒤는 프로바이더에서 제공하는 리소스 유형을 의미한다.
- 데이터 소스 유형을 선언한 뒤에는 고유한 이름을 붙인다. 리소스의 이름과 마찬가지로 이름은 동일한 유형에 대한 식별자 역할을 하므로 중복될 수 없다.
- 이름 뒤에는 데이터 소스 유형에 대한 구성 인수들은 { } 안에 선언한다. 인수가 필요하지 않은 유형도 있지만, 그때에도 { } 는 입력한다
**data** "**local**_file" "abc" {
filename = "${path.module}/abc.txt"
}
- 데이터 소스를 정의할 때 사용 가능한 메타인수는 다음과 같다.
- depends_on : 종속성을 선언하며, 선언된 구성요소와의 생성 시점에 대해 정의
- count : 선언된 개수에 따라 여러 리소스를 생성
- for_each : map 또는 set 타입의 데이터 배열의 값을 기준으로 여러 리소스를 생성
- lifecycle : 리소스의 수명주기 관리
- 데이터 소스로 읽은 대상을 참조하는 방식은 리소스와 구별되게 data가 앞에 붙는다. 속성 값은 다음과 같이 접근할 수 있다.
# Terraform Code
data "<리소스 유형>" "<이름>" {
<인수> = <값>
}
# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>
- main.tf 파일 코드수정
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"
}
→ resource(local_file def)는 의존성이 data(local_file abc)에 존재한다.
#
terraform apply -auto-approve
terraform state list
# 파일 확인
ls *.txt
diff abc.txt def.txt
# graph 확인
terraform graph > graph.dot
# 테라폼 콘솔 : 데이터 소스 참조 확인
terraform console
>
data.local_file.abc.content
...
exit
# 생성된 파일 권한이 다름??? 왜지???
ls -l
(추가 실습) az.tf 파일 생성 - az datasource - 링크
data "aws_availability_zones" "available" {
state = "available"
}
확인
- 위에서 AZ 만 명세해줬을뿐인데, 현재 AWS 에서 사용가능한 정보를 가져온다? ~= 내부에서 코드만 작성해도, 외부(AWS)의 현재 데이터를 가져올 수 있다는 것을 뜻함.
#
terraform init -upgrade && terraform apply -auto-approve
terraform state list
#
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
data.aws_availability_zones.available.names
data.aws_availability_zones.available.names[0]
data.aws_availability_zones.available.names[1]
data.aws_availability_zones.available.zone_ids[0]
data.aws_availability_zones.available.zone_ids[1]
exit
-----------------
변수 선언방식, 변수유형
입력변수(Input Variable)을 사용하여 코드별로 유연하게 작동할 수 있도록 한다.
cd .. && rm -rf 3.5
mkdir 3.6 && cd 3.6
- 변수는 variable로 시작되는 블록으로 구성된다. 변수 블록 뒤의 이름 값은 동일 모듈 내 모든 변수 선언에서 고유해야 하며, 이 이름으로 다른 코드 내에서 참조된다.
# variable 블록 선언의 예
variable "<이름>" {
<인수> = <값>
}
variable "image_id" {
type = string
}
- 테라폼 예약 변수 이름으로 사용 불가능 : source, version, providers, count, for_each, lifecycle, depends_on, locals
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" }]
}
유효성 검사
변수블록 내에서 validation . 검증블록을 사용할 수 있다.
condition에 지정되는 규칙이 true,false 를 반환해야하며
error_message는 condition값의 결과가 false 인 경우 출력되는 메시지를 정의한다.
variable "image_id" {
type = string
description = "The id of the machine image (AMI) to use for the server."
validation {
condition = length(var.image_id) > 4
error_message = "The image_id value must exceed 4."
}
validation {
# regex(...) fails if it cannot find a match
condition = can(regex("^ami-", var.image_id))
error_message = "The image_id value must starting with \"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-**
...
#
terraform apply -auto-approve
var.image_id
The id of the machine image (AMI) to use for the server.
Enter a value: ami-12345678
...
VPC 배포
# 신규 디렉터리 생성
mkdir my-vpc-ec2
cd my-vpc-ec2
- vpc.tf 파일 생성
- resource(aws_vpc myvpc) 만 있어도, AWS관련 데이터는 다운로드 받을 것 같으나,
AWS provider를 직접 명세해줌으로 region 설정을 명시
provider "aws" {
region = "ap-northeast-2"
}
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
terraform state show aws_vpc.myvpc
# VPC 확인
export AWS_PAGER=""
aws ec2 describe-vpcs | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml
- AWS 관리콘솔에서 VPC 생성 정보 확인 : DNS 옵션값 확인
DNS호스트 이름 에 비활성화됨 확인.
- vpc.tf 코드 내용 수정 : VPC DNS 옵션 수정
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"
}
}
# 배포
terraform plan && terraform apply -auto-approve
- AWS 관리콘솔에서 VPC 생성 정보 확인 : DNS 옵션값 확인
DNS 호스트 이름 에 활성화됨 확인
- vpc.tf 코드 내용 수정 : 서브넷 2개 생성 추가
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 = "aews-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
}
aws_subnet은 aws_vpc 에서 데이터를 가져와서 사용한다.
# 배포
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
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
기존 존재하는 서브넷 4개 제외한, 직접 지정한 2개의 서브넷이 생성된 것 확인.
- vpc.tf 코드 내용 수정 : IGW 인터넷 게이트웨이 추가
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 = "aews-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"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "aews-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
vpc id 값을 갖는 인터넷게이트웨이 생성
- vpc.tf 코드 내용 수정 : IGW 인터넷 게이트웨이로 전달하는 디폴트 라우팅 정보 추가
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 = "aews-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"
}
}
resource "aws_internet_gateway" "myigw" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "aews-igw"
}
}
resource "aws_route_table" "myrt" {
vpc_id = aws_vpc.myvpc.id
tags = {
Name = "aews-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=aews-rt' --query 'RouteTables[].Associations[].SubnetId'
aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=aews-rt' --output table
인터넷 게이트웨이 → 서브넷 연결 에서 VPC내용 설정됨 확인.
보안그룹/EC2 배포 : sg.tf, ec2.tf
sg.tf 파일 생성 : 보안그룹 생성
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_security_group.mysg
aws_security_group_rule.mysginbound
aws_security_group_rule.mysgoutbound
...
terraform state show aws_security_group.mysg
terraform state show aws_security_group_rule.mysginbound
# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
- ec2.tf 파일 생성 : 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
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"
}
- 배포 실행 후 EC2 확인
#
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
data.aws_ami.my_amazonlinux2
aws_instance.myec2
...
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
모듈
여러개의 .tf 파일을 하나씩 관리하기에 어려움이 있으므로, 모듈로 저장해서 사용한다.
첫 번째 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=jjongkey
echo $TF_VAR_KeyName
#
terraform init
terraform plan
# 10분 후 배포 완료
terraform apply -auto-approve
배포정보 확인
#
kubectl get node -v=6
# 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"
#
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
배포완료!
- 기타 설치
# ExternalDNS
#MyDomain=<자신의 도메인>
MyDomain=jjongguet.com
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
두 번째 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
# EKS 클러스터 정보 확인
kubectl --kubeconfig ./myeks2config get node
kubectl --kubeconfig ./myeks2config get pod -A
첫 번째 클러스터 배포와의 다른점 :
terraform apply 할때 var 변수로 ClusterBaseName, KubernetesVersion 으로 직접 명세해주었음
→ 모듈화를 그대로 사용하여 배포완료
마찬가지로 배포완료!
삭제 : 디렉터리(폴더) 진입 후 각각 삭제
- 첫 번째 클러스터 삭제
# 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"
'외부활동' 카테고리의 다른 글
[T101] 4기 스터디: Module (3) | 2024.07.14 |
---|---|
[T101] 4기 스터디: Confluent Cloud with Terraform (0) | 2024.06.29 |
[AEWS] 2기 스터디: CI (1) | 2024.04.21 |
[AEWS] 2기 스터디: EKS의 인증/인가 절차2 (0) | 2024.04.12 |
[AEWS] 2기 스터디: EKS의 인증/인가 절차1 (0) | 2024.04.11 |