개요
이 시리즈는 코드로 인프라를 구축하는 IaC(Infrastructure as Code)를 이해하기 위해 작성했다.
AWS의 EKS 클러스터를 코드로 구축하고 ArgoCD를 이용해 클러스터 내 어플리케이션을 배포한다.
인프라 구성 방법을 개개인의 노하우와 숙련도가 아닌 자산으로 코드로 남길 수 있도록 한다.
목표
- IaC 도구인 Terraform의 구조와 동작방식을 이해한다.
- Terraform으로 기본적인 AWS 네트워크를 생성해본다.
여정
인프라를 코드로 작성하는 Terraform
사전 준비물
- Terraform 설치(Install | Terraform | HashiCorp Developer)
- AWS CLI 설치(AWS Command Line Interface (amazon.com))
- AWS CLI로 로그인
aws configure # AWS EKS 자격증명 설정
테라폼은 HCL(HashiCorp configuration language) 언어로 개발이 되었는데
상태 선언형으로 서술되므로 이해하기 쉽다(로직이 거의 없다!)
쉬운 예시로 일반적인 프로그래밍 언어와 비슷하게
variable, local이라는 변수를 지정할 수 있다
variable "kubernetes_version" {} // 기본 선언 형태(비권장)
variable "region" {
description = "AWS 리전"
type = string // 문자열 형식의 변수
default = "ap-northeast-1"
validation {
condition = length(var.region) > 0
error_message = "AWS 리전을 확인해 주세요."
}
}
variable "tags" {
description = "메타 태그"
type = map(string) // Map 형식의 변수
default = {
environment = "dev"
department = "test"
}
}
locals {
prefix = "my-test-"
cluster_name = "my-test-"
}
기본 선언형태는 어떤 변수든 수용할 수 있지만,
어떤 값이 올지 모르는 상태가 되므로 좋지 않다.
따라서 variable에 description과 validation 체크는 권장되는 사항이다.
한편, resource라는 키워드는 미리 선언된 클래스를 불러오는 것과 비슷하다.
(정확히는 Terraform Registry에서 불러온다!)
resource "random_string" "suffix" { // 난수를 생성하는 리소스
length = 8
special = false
}
locals {
// 변수를 문자열에서 넣는 방법이 많이 활용된다.
prefix = "${var.tags.department}-${var.tags.environment}-"
cluster_name = "${local.prefix}${random_string.suffix.result}"
}
이 resource의 종류가 굉장히 많기 때문에
적재적소에 필요한 것을 가져다쓰는 것이 핵심이다.
(다행히도 aws, google 등 다양한 기업과 단체에서 공식 예제를 지원하고 있다!)
수정된 locals 블럭을 보면 알겠지만
중복되지 않는 이름들을 생성하거나,
어떤 팀의 리소스인지 구분하기 위해
변수를 문자열에 넣는 방법이 많이 활용된다.
그리고 이렇게 생성된 변수들을 output 키워드를 통해 외부로 노출시킬 수 있다.
output "region" {
value = var.region
}
output "tags" {
value = var.tags
}
output "prefix" {
value = local.prefix
}
output "cluster_name" {
value = local.cluster_name
}
잠깐! 여기서 외부란 무엇인가?
테라폼은 현재 실행 디렉토리를 기준으로 .tf 파일을 모두 읽어들인다.
다만, 상위나 하위 디렉토리는 무시하게 되고,
variable과 local도 같은 디렉토리 내에서만 유효하다.
따라서 다른 디렉토리의 코드를 사용하려면
다음과 같이 module을 만들어 가져오는 수 밖에 없다.
module "envs" {
source = "./envs"
}
module "vpc" {
source = "./modules/vpc"
depends_on = [module.envs]
region = modue.envs.region
...
}
이렇게 하면 서로 variable이나 local은 사용할 순 없지만
output으로 모듈 바깥으로 변수를 노출시킬 수 있고,
이를 다시 다른 모듈에서 재사용할 수 있게 된다.
이 때, 주의할 점은 생성순서가 중요해진다는 점인데
이는 depends_on 옵션으로 해결할 수 있다.
일반적으로 테라폼은 코드 작성 순서에 상관없이 resource가 생성되기 때문에
이렇게 순서에 의존하는 경우 depends_on 키워드를 적어줘야 한다.
(일반적인 resource에서도 사용할 수 있다.)
이제 지난 시간에 배웠던 네트워크 개념들을 실제로 생성해보도록 하자.
VPC와 서브넷 생성
일부 해설이 필요한 부분이 있으나
코드 내용 자체는 로직이 거의 없기 때문에 어렵지 않다.
# VPC 생성
resource "aws_vpc" "vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
}
data "aws_availability_zones" "available" {
state = "available" # 사용 가능한 가용 영역 필터링
}
locals {
azs = slice(data.aws_availability_zones.available.names, 0, 3) # 3개만 가져오기
private_azs = [local.azs[0], local.azs[1]] # 2개만 할당
}
# Subnet 생성
resource "aws_subnet" "private" {
count = length(local.private_azs)
vpc_id = aws_vpc.vpc.id
availability_zone = element(local.private_azs, count.index)
cidr_block = "10.0.${count.index + 1}.0/24"
map_public_ip_on_launch = false # Public IP 자동 할당 하지 않도록 함
}
VPC를 생성하는 코드는 Region과 CIDR이 필요한데,
Region은 AWS CLI로 로그인(aws configure)하면서 지정된다.
다음으로 data라는 키워드는 이미 생성된 리소스의 정보를 받아온다.
대표적으로 아마존이 미리 지정해놓은 가용영역(Availability Zone)이나 AMI 등이 있다.
여기서는 현재 Region의 가용영역을 받아와서 local 변수에 할당하고 있다.
하단의 Subnet을 생성하는 코드를 보면 두가지 체크할 것이 있다.
첫 번쨰는 count라는 속성의 등장이다.
count는 for문과 유사한 방식으로 동작한다.
local.private_azs의 갯수가 2개이므로 총 2개의 서브넷이 생성된다.
count는 index라는 속성도 가지고 있는데 이를 이용해서 cidr 블럭의 일부를 할당하고 있다.
다른 하나는 첫번째로 생성한 vpc를 변수로 호출해서 지정한다는 점이다.
HCL은 상태 선언형 방식이기 때문에
실제 id가 생성되어 있지 않더라도 생성 후 주입하는 방식으로 동작한다.
자, 이제 이 서브넷을 이용해서 다른 리소스도 생성해보자.
Route Table과 NAT Gateway
이름짓기
수 많은 리소스를 생성하면서 이름 짓기에 굉장한 공을 들이게 된다.
정보의 중복을 피하기 위해서 resource 종류가 이름에 들어가지 않도록 하는 것이 좋다!
# Route Table 생성 (Private)
resource "aws_route_table" "private" {
vpc_id = aws_vpc.vpc.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.nat.id // NAT Gateway를 통해 외부와 통신
}
}
resource "aws_route_table_association" "private" {
count = length(local.private_azs)
subnet_id = element(aws_subnet.private.*.id, count.index)
route_table_id = aws_route_table.private.id
}
먼저 Route Table을 보면 VPC와 CIDR을 이용해서 생성하고,
Association이라는 리소스를 통해 그 규칙을 서브넷과 이어주고 있다.
사실, Association이 아니라 직접 값을 리소스 안에 작성할 수도 있다.
한 리소스의 코드가 너무 길어지게 되면 실패했을 때 그 영향도 커지게 된다.
따라서, 이렇게 분리하는 방법을 선택했다.
(이렇게 Association과 같이 분리 작성된 코드는 종종 마주치게 될 것이다!)
중간에 Route Table의 Route 규칙을 보면 NAT Gateway를 설정하는 부분이 있다.
이 부분을 작성하면 아래와 같다.
resource "aws_eip" "nat" { # 고정 IP 부여
vpc = true
}
resource "aws_nat_gateway" "nat" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public[0].id
depends_on = [aws_internet_gateway.igw]
}
먼저 Elastic IP를 통해 고정 IP를 부여하고
NAT Gateway는 이를 사용해서 사설 IP주소를 외부 인터넷과 이어주기 위해 변환한다.
잠깐!
지정된 서브넷은 public에다가,
생성을 대기하는 순서는 Internet Gateway다!
그렇다. 바로 외부와 연결하기 위해서 공개된 서브넷과 게이트웨이가 필요하다.
resource "aws_subnet" "public" {
...
cidr_block = "10.0.${100 + count.index + 1}.0/24"
map_public_ip_on_launch = true # Public IP를 자동으로 할당
}
# Internet Gateway 생성
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.vpc.id
}
Private 서브넷과 동일한 방식으로 선언하되 CIDR 블럭만 일부 다르게 조정했다.
그리고 Public IP를 사용할 수 있도록 옵션을 활성화했다.
Internet Gateway도 작성해주면 기본적인 VPC 작성이 완료된다.
근데 어떻게 실행하죠?
aws configure가 실행되었다는 가정하에파일이 작성된 디렉토리로 이동해서 실행해야 한다.
실행 명령어는 크게 4가지로 아래와 같다.
terraform init # 로컬에 의존성 설치
terraform plan # 의존성 준비 확인
terraform apply -auto-approve # 원격지에 인프라 생성 (yes 입력 생략)
terraform destroy -auto-approve # 원격지에 생성한 리소스 삭제 (yes 입력 생략)
보통 일반적으로 init → plan → apply (→ destroy) 순으로 실행하면 된다.
실행 결과는 보통 아래와 같은 형태로 표시된다.
Q & A
- apply를 실행했더니 terraform.tfstate 파일이 생겼어요! 지워도 되나요?
실행 결과로 생성되는 terraform.tfstate 파일은 현재 인프라의 상태를 나타내므로
삭제한다면, 그 인프라와 연결이 끊기게 된다.
즉, 변경사항을 apply하거나 destroy로 삭제할 수 없게 된다.
이러한 상태 저장 문제를 해결하는 방법은
S3와 같은 원격 Bucket에 저장하는 방법이다.
(물론 S3 버켓을 생성하고 난 뒤에 사용할 수 있다.)
terraform {
backend "s3" {
bucket = "apnorthease2-tfstate" # 버킷 명
key = "terraform/IAM/terraform.tfstate" # 파일 경로
region = "ap-northeast-2" # 지역
encrypt = true # state 파일 암호화 여부
dynamodb_table = "TerraformStateLock" # DB table 명
}
}
마무리
이번 시간에는 테라폼의 기본 문법과 이를 활용한 VPC 생성을 진행해보았다.다음 시간에는 본격적으로 클러스터를 생성하는데 필요한 요소와 다양한 방법을 알아보자
'DevOps' 카테고리의 다른 글
코드로 클러스터 구축하기 - (4) GitOps 적용 (0) | 2023.08.12 |
---|---|
코드로 클러스터 구축하기 - (3) 클러스터 생성 (0) | 2023.08.10 |
코드로 클러스터 구축하기 - (1) AWS 네트워크 기본 개념 (0) | 2023.08.10 |
Kubeflow 라이징 - Charmed Kubeflow (Optional) (0) | 2023.04.19 |
IaC로 쿠버네티스 환경 구축하기 (Ansible + Kubespray) (0) | 2023.04.15 |