이번 게시글은 가시다님의 AEWS [2기] 스터디 내용을 정리한 포스트 입니다.
이번 게시글은 8 주차의 스터디 내용인 EKS IaC에 대해 살펴봅니다.
Terraform
테라폼은 하시코프사에서 공개한 IaC 도구이다. Terraform Provider는 AWS, GCP, Azure등 다양한 벤더에 대한 배포를 지원한다. 사용자는 테라폼 1개의 문법으로 다양한 프로바이더를 사용하여 여러 벤더사의 클라우드 리소스 배포를 관리할 수 있다.
실습환경 준비
macOS 기준으로 설명합니다.
# 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 설치
brew install awscli
#EKSCTL
brew install eksctl
#KUBECTL
brew install kubernetes-cli
#HELM
brew install helm
VSCode의 Extentions을 설치합니다.
`aws configure`를 구성한 뒤 아래 명령으로 default VPC가 존재하는지 확인합니다.
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq
만약 기본 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
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
터미널 1개를 추가로 열고, 아래 명령으로 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 이라고 부르며, Block 단위로 구성되어 있습니다. 아래는 2개의 블록으로 이루어 져 있습니다. Provider는 배포할 타겟의 클라우드 환경을 설정한다고 이해하면 됩니다, resource는 실제 배포할 인프라 자원을 의미합니다.
cat <<EOT > main.tf
provider "aws" {
region = "ap-northeast-2"
}
resource "aws_instance" "example" {
ami = "$AL2ID"
instance_type = "t2.micro"
}
EOT
Terraform 배포를 진행합니다.
# 초기화
terraform init
ls -al
tree .terraform
# plan 확인
terraform plan
# apply 실행
terraform apply
Enter a value: yes 입력
AWS cli에서 아래처럼 EC2 list를 확인해볼 수 있습니다. 테라폼을 통해서 EC2 인스턴스를 배포해 보았습니다.
`terraform show` 명령을 통해서도 테라폼에 의해 배포된 리소스 정보를 확인해볼 수 있습니다.
현재 배포되어있는 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
`terraform plan`후 `terraform apply`를 통해 배포를 실행 해 봅니다. terraform plan의 결과를 보면 In-place 방식으로 업데이트 된다고 안내되어 있어, 배포된 리소스의 중지 없이 배포할 수 있음을 알 수 있습니다.
erraform plan
aws_instance.example: Refreshing state... [id=i-0fe9e466fca30f1cc]
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-0fe9e466fca30f1cc"
~ tags = {
+ "Name" = "aews-study"
}
~ tags_all = {
+ "Name" = "aews-study"
}
# (30 unchanged attributes hidden)
# (8 unchanged blocks hidden)
}
Plan: 0 to add, 1 to change, 0 to destroy.
───────────────────────────────────────────────
Note: You didn't use the -out option to save
this plan, so Terraform can't guarantee to take
exactly these actions if you run "terraform
apply" now.
`terraform apply`에 성공하면 아래처럼 EC2 리소스에 name이 확인되는것을 볼 수 있습니다.
`terraform destoy`를 통해 삭제합니다.
[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 Version
테라폼 내에서 버전이 명시되는 terraform, module에서 사용 가능하며 버전에 대한 제약을 둠으로써 언제 실행하든 항상 의도한 정의대로 실행되는 것을 목적으로 합니다.
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"
}
}
프로바이더의 버전도 각 벤더 별 상이하게 적용할 수 있습니다. 테라폼 0.13 버전 이전에서는 provider 블록에 함께 버전을 명시했지만, 해당 버전 이후 provider 버전은 terraform 블록에서 required_providers에 정의합니다.
리소스 구성 실습을 진행합니다. 신규 디렉터리를 생성하고 main.tf 파일을 생성합니다.
cd ..
mkdir 2
cd 2
리소스 블록은 resource로 시작합니다. 이후 리소스 블록이 생성할 리소스 유형을 정의합니다. 리소스 유형에는 "프로바이더 이름_제공 리소스 유형" 으로 선언할 수 있습니다.
resource "<리소스 유형>" "<이름>" {
<인수> = <값>
}
resource "local_file" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
main.tf 파일에 아래 내용을 붙여넣고 `terraform init`을 입력합니다.
resource "local_file" "abc" {
content = "123"
filename = "${path.module}/abc.txt"
}
resource "aws_instance" "web" {
ami = "ami-a1b2c3d4"
instance_type = "t2.micro"
}
현재 디렉터리에 숨김 폴더로 .terraform 폴더가 생성되고, 하위에 local과 aws에 관한 provider 다운이 자동으로 된 것을 확인해볼 수 있습니다.
테라폼의 종속성은 resource, module 선언으로 프로비저닝 되는 각 요소의 생성 순서를 자동으로 구분 짓습니다. 관리자가 별도의 종속성을 적용해야 하는 경우 depends_on을 활용해야 합니다.
아래 코드로 main.tf를 수정합니다.
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`를 진행합니다. abc.txt와 def.txt가 생성된 것을 볼 수 있습니다.
`terraform graph > graph-1.dot`명령을 통해 종속성 확인을 해봅니다.
현재는 두개의 resource에 별다른 종속성이 없습니다.
모든 리소스를 제거해 봅니다.
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`를 통해 배포합니다. 그 후 `terraform graph > graph-2.dot` 명령으로 종속성을 확인해 봅니다.
위와 동일하게 terraform 삭제 후 depends_on으로 명시적인 종속성을 적용해도 동일한 결과가 나옵니다. 원래는 def 리소스가 병렬로 실행되어 별도 종속성이 없어야 하지만 depends_on의 옵션으로 인해 abc가 생성된 뒤 def가 생성되게 됩니다.
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"
}
리소스 속성 참조는 배포된 리소스의 속성 SG의 ID 같은 정보를 참조할 수 있는 기능입니다. 아래 예시는 namesapce를 생성하고, secret을 배포할 때, Terraform에 의해 배포된 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"
}
}
데이터 소스 구성은 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용합니다.
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`하면 아래와 같은 종속관계를 가집니다.
아래 data 블록을 신규 추가하고 `terraform apply`를 재실행 합니다.
data "aws_availability_zones" "available" {
state = "available"
}
`terraform console`로 외부 data의 정보를 확인해볼 수 있습니다.
변수 선언 방식으로는 입력 변수의 경우 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적이 있습니다. 테라폼에서는 이것을 입력 변수로 정의합니다.
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" }]
}
생성한 변수의 참조는 var."name" 방식으로 참조할 수 있습니다.
variable "my_password" {}
resource "local_file" "abc" {
content = var.my_password
filename = "${path.module}/abc.txt"
}
VPC 배포 실습
신규 디렉터리 생성
# 신규 디렉터리 생성
mkdir my-vpc-ec2
cd my-vpc-ec2
vpc.tf
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
보안그룹과 EC2를 배포해 봅니다. sg.tf, ec2.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
}
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
...
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
3.35.214.63
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`
단일 프로바이더의 다중 정의도 적용할 수 있습니다. 아래 예제는 리전을 다르게 구성한 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"
}
모듈의 경우 테라폼으로 관리하는데 시간이 지날수록 구성이 복잡해지고, 관리하는 리소스가 늘어가게 되는데 모듈은 테라폼 구성의 집합으로 구성해 두어 관리 및 재사용에 용이하도록 합니다. 새로운 폴더를 생성하고 main.tf를 작성합니다.
# 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)
}
실행
#
terraform init && terraform plan
# 테스트를 위해 apply 시 변수 지정
terraform apply -auto-approve -var=isDB=true
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
id = "pleased-ghost"
pw = "5Iq3*liXG*DWGkOp"
# 확인
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
자식 모듈 호출을 확인해 봅니다. module을 통해 위에서 정의한 리소스를 재사용합니다. 새로운 폴더를 만들고 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" = "sure-coral"
"pw" = "W4zEVZ1wXK"
}
mypw2 = {
"id" = "adapted-troll"
"pw" = "4WXyjq*o3M6xIg5l"
}
# 확인
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": "../4",
"Dir": "../4"
},
{
"Key": "mypw2",
"Source": "../4",
"Dir": "../4"
}
]
}
# graph 확인
terraform graph > graph.dot
Terraform 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='hj42700eks'
echo $TF_VAR_KeyName
#
terraform init
terraform plan
# 10분 후 배포 완료
terraform apply -auto-approve
vpc.tf
provider "aws" {
region = var.TargetRegion
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~>5.7"
name = "${var.ClusterBaseName}-VPC"
cidr = var.VpcBlock
azs = var.availability_zones
enable_dns_support = true
enable_dns_hostnames = true
public_subnets = var.public_subnet_blocks
private_subnets = var.private_subnet_blocks
enable_nat_gateway = true
single_nat_gateway = true
one_nat_gateway_per_az = false
map_public_ip_on_launch = true
igw_tags = {
"Name" = "${var.ClusterBaseName}-IGW"
}
nat_gateway_tags = {
"Name" = "${var.ClusterBaseName}-NAT"
}
public_subnet_tags = {
"Name" = "${var.ClusterBaseName}-PublicSubnet"
"kubernetes.io/role/elb" = "1"
}
private_subnet_tags = {
"Name" = "${var.ClusterBaseName}-PrivateSubnet"
"kubernetes.io/role/internal-elb" = "1"
}
tags = {
"Environment" = "${var.ClusterBaseName}-lab"
}
}
var.tf
variable "KeyName" {
description = "Name of an existing EC2 KeyPair to enable SSH access to the instances."
type = string
}
variable "ClusterBaseName" {
description = "Base name of the cluster."
type = string
default = "myeks"
}
variable "KubernetesVersion" {
description = "Kubernetes version for the EKS cluster."
type = string
default = "1.29"
}
variable "WorkerNodeInstanceType" {
description = "EC2 instance type for the worker nodes."
type = string
default = "t3.medium"
}
variable "WorkerNodeCount" {
description = "Number of worker nodes."
type = number
default = 3
}
variable "WorkerNodeVolumesize" {
description = "Volume size for worker nodes (in GiB)."
type = number
default = 30
}
variable "TargetRegion" {
description = "AWS region where the resources will be created."
type = string
default = "ap-northeast-2"
}
variable "availability_zones" {
description = "List of availability zones."
type = list(string)
default = ["ap-northeast-2a", "ap-northeast-2b", "ap-northeast-2c"]
}
variable "VpcBlock" {
description = "CIDR block for the VPC."
type = string
default = "192.168.0.0/16"
}
variable "public_subnet_blocks" {
description = "List of CIDR blocks for the public subnets."
type = list(string)
default = ["192.168.1.0/24", "192.168.2.0/24", "192.168.3.0/24"]
}
variable "private_subnet_blocks" {
description = "List of CIDR blocks for the private subnets."
type = list(string)
default = ["192.168.11.0/24", "192.168.12.0/24", "192.168.13.0/24"]
}
eks.tf
data "aws_caller_identity" "current" {}
resource "aws_iam_policy" "external_dns_policy" {
name = "${var.ClusterBaseName}ExternalDNSPolicy"
description = "Policy for allowing ExternalDNS to modify Route 53 records"
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"route53:ChangeResourceRecordSets"
],
"Resource": [
"arn:aws:route53:::hostedzone/*"
]
},
{
"Effect": "Allow",
"Action": [
"route53:ListHostedZones",
"route53:ListResourceRecordSets"
],
"Resource": [
"*"
]
}
]
})
}
resource "aws_iam_role_policy_attachment" "external_dns_policy_attach" {
role = "${var.ClusterBaseName}-node-group-eks-node-group"
policy_arn = aws_iam_policy.external_dns_policy.arn
depends_on = [module.eks]
}
resource "aws_security_group" "node_group_sg" {
name = "${var.ClusterBaseName}-node-group-sg"
description = "Security group for EKS Node Group"
vpc_id = module.vpc.vpc_id
tags = {
Name = "${var.ClusterBaseName}-node-group-sg"
}
}
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~>20.0"
cluster_name = var.ClusterBaseName
cluster_version = var.KubernetesVersion
cluster_endpoint_private_access = false
cluster_endpoint_public_access = true
cluster_addons = {
coredns = {
most_recent = true
}
kube-proxy = {
most_recent = true
}
vpc-cni = {
most_recent = true
}
}
vpc_id = module.vpc.vpc_id
enable_irsa = true
subnet_ids = module.vpc.public_subnets
eks_managed_node_groups = {
default = {
name = "${var.ClusterBaseName}-node-group"
use_name_prefix = false
instance_type = var.WorkerNodeInstanceType
desired_size = var.WorkerNodeCount
max_size = var.WorkerNodeCount + 2
min_size = var.WorkerNodeCount - 1
disk_size = var.WorkerNodeVolumesize
subnets = module.vpc.public_subnets
key_name = var.KeyName
vpc_security_group_ids = [aws_security_group.node_group_sg.id]
iam_role_name = "${var.ClusterBaseName}-node-group-eks-node-group"
iam_role_use_name_prefix = false
iam_role_additional_policies = {
"${var.ClusterBaseName}ExternalDNSPolicy" = aws_iam_policy.external_dns_policy.arn
}
}
}
access_entries = {
admin = {
kubernetes_groups = []
principal_arn = "${data.aws_caller_identity.current.arn}"
policy_associations = {
myeks = {
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
access_scope = {
namespaces = []
type = "cluster"
}
}
}
}
}
tags = {
Environment = "${var.ClusterBaseName}-lab"
Terraform = "true"
}
}
irsa.tf
locals {
cluster_oidc_issuer_arn = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:oidc-provider/${replace(module.eks.cluster_oidc_issuer_url, "https://", "")}"
}
module "eks-external-dns" {
source = "DNXLabs/eks-external-dns/aws"
version = "0.2.0"
cluster_name = var.ClusterBaseName
cluster_identity_oidc_issuer = module.eks.cluster_oidc_issuer_url
cluster_identity_oidc_issuer_arn = local.cluster_oidc_issuer_arn
enabled = false
}
module "aws_load_balancer_controller_irsa_role" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "5.3.1"
role_name = "${var.ClusterBaseName}-aws-load-balancer-controller"
attach_load_balancer_controller_policy = true
oidc_providers = {
ex = {
provider_arn = module.eks.oidc_provider_arn
namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
}
}
}
provider "kubernetes" {
host = module.eks.cluster_endpoint
token = data.aws_eks_cluster_auth.cluster.token
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
}
data "aws_eks_cluster" "cluster" {
name = module.eks.cluster_name
depends_on = [module.eks]
}
data "aws_eks_cluster_auth" "cluster" {
name = module.eks.cluster_name
depends_on = [module.eks]
}
resource "kubernetes_service_account" "aws_lb_controller" {
metadata {
name = "aws-load-balancer-controller"
namespace = "kube-system"
annotations = {
"eks.amazonaws.com/role-arn" = module.aws_load_balancer_controller_irsa_role.iam_role_arn
}
}
}
배포 정보 확인
#
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=junkmm.site
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
삭제
# CLB는 terraform으로 배포하지 않고 직접 수동으로 배포되었으니, 수동으로 삭제를 해주어야 함
helm uninstall kube-ops-view --namespace kube-system
# 클러스터 삭제
terraform destroy -auto-approve
'클라우드' 카테고리의 다른 글
[cks][killershell] NodeRestriction 실습 (0) | 2024.07.30 |
---|---|
[cks][killershell] Apiserver Crash 실습 (0) | 2024.07.29 |
[k8s] Pod의 전략적 배치 - Node Affinity (0) | 2024.04.17 |
[EKS] CI/CD (0) | 2024.04.16 |
[EKS] Autoscaling (0) | 2024.04.03 |