인프라 구조마저 코드로 조작하고 있는 현재, 가장 많이 쓰이는 도구는 테라폼과 앤서블이 있습니다. DevOps Loadmap에서도 두 가지 방법을 가장 권장하고 있네요!
그도 그럴 것이, 클라우드 인프라의 경우 GUI로 클릭하면서 조작하거나 혹은 각각이 제공하는 sdk를 사용해 인프라를 설정하지만, 이렇게 하면 서로 어떻게 인프라를 설정했는지 공유가 어렵고 숙련도에 따라 설정이 확연히 달라지는 경우가 종종 발생합니다.
이런 문제들을 해결하고자 HashiCorp에서 Terraform이라는 IaC 도구를 만들어 세상에 공개했습니다. Terraform과 성격이 비슷한 도구가 Ansible인데, Terraform과 Ansible의 차이는 간단하게 생각해서 아예 뜯어 고칠 것인지, 일부 수정할 것인지의 차이가 있어서 비슷하면서도 목적이 다릅니다.
그렇지만 요즘은 또, 앤서블과 테라폼을 혼용해서 사용하는 방법론이 괜찮아 보이는 것 같습니다. 테라폼으로 전반적인 프로비저닝을 진행하고, 앤서블을 통해 자동화 프로세스를 진행하는 식으로 응용할 수 있다고 합니다.
그래서 한 번 테라폼을 슥 훑어보면서 사용해봤을 때, 선언적으로 인프라를 구성할 수 있고 편리하게 수정이 가능하며 공유에 용이하다는 점이 가장 큰 장점으로 다가왔습니다. 이번에 그 경험과 느낀점을 자세히 공유해보고자 합니다.
테라폼 언어
yaml을 사용하는 앤서블과 달리 HCL이라는 언어를 사용합니다. 사실 이런 점때문에 단점으로 느껴질 수 있을 것 같았는데, 문법의 총량이 많지 않고 유연하게 재사용하며 인프라를 구성할 수 있다는 점이 장점으로도 다가왔습니다.
언어 자체도 복잡하지 않아서 자바스크립트와 코틀린을 섞어서 사용하는 그런 느낌도 들었습니다.
provider "aws" {
region = var.region
default_tags {
tags = {
Project = "Coffee-Mug-Cake"
Owner = "jerry & tom"
}
}
}
resource "aws_vpc" "hashicat" {
cidr_block = var.address_space
enable_dns_hostnames = true
tags = {
name = "${var.prefix}-vpc-${var.region}"
environment = "Production"
}
}
대강 봐도 '아 AWS를 사용하는구나~ 리전은 객체같은 변수에서 꺼내서 쓰고, vpc를 생성하고 태그도 같이 넣어줬구나' 하는 생각이 들 수 있을 것 같아요.
함수 & 조건 & 반복
함수를 직접 생성할 수는 없지만, 내장 함수들이 있어서 간단하게 카운팅을 하거나 자료형을 변환하는 등의 기능을 편하게 사용할 수 있습니다.
resource "null_resource" "configure-cat-app" {
depends_on = [aws_eip_association.hashicat]
triggers = {
build_number = timestamp() # timestamp 함수
}
provisioner "file" {
source = "files/"
destination = "/home/ubuntu/"
...
조건문의 경우도 타 언어에도 존재하는 삼항 연산자를 사용해 동적으로 인프라를 조작할 수 있습니다.
아래 블럭에서 count 속성에 값을 넣게 되는데, 워크스페이스에 따라서 aws 인스턴스의 개수를 다르게 하는 것입니다. 만약 기본 default 워크스페이스라면 인스턴스가 5개가 생기고 아니라면 1개만 생성됩니다.
또한, count 속성에 값을 부여해서 반복을 수행할 수 있습니다.
resource "aws_instance" "web" {
count = "${terraform.workspace == "default" ? 5 : 1}"
ami = data.aws_ami.amzn2.id
subnet_id = "subnet-0cf877129235db9c0"
instance_type = "t2.micro"
tags = {
Name = "HelloWorld"
}
}
사실 반복문이 블럭으로 존재하는 것은 아니고 속성에 부여하거나, 변수에 반복할 수 있는 자료형을 사용하여 for_each 속성에 부여하는 등으로 사용하기에 타 프로그래밍 언어처럼 명확하게 반복을 한다는 것이 느껴지지는 않았습니다.
당장 위의 count 만 보더라도 그렇네요!
혹은 리스트를 초기화할 때도 파이썬의 리스트 컴프리헨션과 같이 응용할 수도 있습니다. 아래 코드는 테라폼으로 인프라를 조작한 후에 출력할 값들을 의미하며, ec2 seoul이라는 테라폼의 모듈 단위의 행위에서 private_ip를 리스트로 담은 것입니다.
output "module_output" {
value = [
for k in module.module.ec2-seoul: k.private_ip
]
}
테라폼 CLI
terraform validate
CLI가 부가적인 기능이 많아서 편리하게 느껴졌습니다. 예를 들어 아래처럼 코드 상에 실수가 있거나 하면, terraform validate를 통해서 디렉터리 내 모든 테라폼 파일에 유효성 검사가 가능합니다.
terraform plan
terraform plan은 인프라가 생성되는 과정을 확인하거나, 파일을 생성해 바로 적용할 수 있도록 준비해주는 등의 역할을 합니다.
사실 다른 것보다 terraform plan과 terraform apply 명령어가 가장 중요한데, terraform plan이 개발하면서 도움이 많이 될 것 같아요. 우리가 인프라를 생성하거나 수정할 때, 어떤 과정을 거치게 될지와 내가 설정한 변수가 어떻게 설정될지 혹은 output으로 받을 값들이 어떻게 초기화될지를 파악하기 좋습니다. 물론 생성이 되어야지 알 수 있는 것들은 당장 알 수 없습니다!
아래 터미널 출력처럼 생성될 인프라 리소스들을 확인할 수 있습니다.
다양한 클라우드 사용 가능
클라우드 벤더사로 가장 유명하고 많이 쓰이는 곳이 AWS인데, 이외에도 다른 목적으로 사용하거나 혹은 처음부터 타 플랫폼으로 사용하는 경우도 많습니다. 모든 클라우드를 추상화할 수는 없겠지만, 많은 클라우드 플랫폼에 대해서 IaC를 지원한다는 게 매우 좋았습니다.
AWS나 GCP, Azure 등의 글로벌 플랫폼 외에도 국내 네이버 NCP도 지원하는데, 이렇게 지원할 수 있는 이유는 프로바이더 레지스트리를 사용하기 때문입니다.
모듈화
테라폼으로 선언한 인프라 설정을 모듈처럼 재사용할 수 있습니다. 라이브러리나 패키지를 설치해서 필요한 기능을 편하게 재사용하는 것처럼, AWS 등에서 같은 설정인데 리전만 다르게 해야 하는 사소한 경우라도 손쉽게 처리할 수 있습니다.
내가 생성할 인프라 구조를 간단하게 설정하면 이것을 모듈로서 구비할 수 있고, 향후 다른 파일에서 해당 모듈을 끌어와 사용하고 region의 값으로 다른 값을 넣어서 융통성있게 변화를 줄 수 있는 것이죠.
provider "aws" {
region = "us-west-1"
}
provider "aws" {
alias = "seoul"
region = "ap-northeast-2"
}
module "ec2_california" {
source = "../modules/terraform-aws-ec2"
}
module "ec2_seoul" {
source = "../modules/terraform-aws-ec2"
providers = {
aws = aws.seoul
}
instance_type = "m5.large"
}
위 코드는 aws 인스턴스를 사용하는 웹 애플리케이션을 두 개 설정하는 코드입니다. 여기서 aws provider를 두 개 설정해서 각각 리전을 다르게 두고, 동일한 모듈을 사용하지만 설정을 다르게 해서 한 개의 애플리케이션은 캘리포니아에, 나머지 한 개의 애플리케이션은 서울 리전에 비치합니다.
전반적인 인프라는 동일하지만 일부 설정만 바꿔야 할 때, 코드의 중복을 줄이며 조금 더 가독성 있게 선언할 수 있다는 강점이 있습니다.
상태 (State)
테라폼의 아이덴티티 중에 하나는 바로 상태입니다. 말 그래도 인프라가 어떤 상태인지를 나타내죠.
여기서 얻을 수 있는 이점이 저는 두 가지 정도 있다고 생각하는데, 첫 번째로는 명확성이라고 생각합니다. 예를 들어서, 아래 코드는 AWS로 인프라를 구성했을 때, 테라폼에서 생성/사용하는 AWS 인프라의 상태(state)입니다.
{
"version": 4,
"terraform_version": "1.6.5",
"serial": 8,
"lineage": "e6cf4b62-26f0-5152-72e7-2711318d69b7",
"outputs": {},
"resources": [
{
"mode": "data",
"type": "aws_ami",
"name": "amzn2",
"provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
"instances": [
{
"schema_version": 0,
"attributes": {
"architecture": "x86_64",
"arn": "arn:aws:ec2:ap-northeast-2::image/ami-XXXXXXXXXXXXX",
"block_device_mappings": [
{
"device_name": "/dev/xvda",
"ebs": {
"delete_on_termination": "true",
"encrypted": "false",
"iops": "0",
...
당장에 확인했을 때 이게 무엇인지 유추할 수는 없겠지만, 인프라의 명확한 상태를 정의하기에 조금 더 확실하게 사태를 파악할 수 있다고 생각합니다.
두 번째로 이 상태를 다른 사람과 공유하며 작업할 수 있다는 것이 장점으로 느껴졌습니다. 조금 더 다른 사람과 협업하기에 쉽게 같은 상태를 사용하므로 더 확실하게 해당 인프라에 대해서만 작업할 수도 있습니다. 물론, 서로 동시에 상태를 수정하는 등의 문제가 발생할 수도 있는데, 테라폼은 이런 문제를 공유 state 백엔드 저장소(Terraform Cloud, 이하 TFC)를 통해 해결했습니다.
즉, 상태에 관해서 동시성을 제어할 수 있는 것이죠. 실제로, terraform의 설정을 두 터미널에서 동시에 적용하고자 했을 때, state에 lock이 걸려서 수정이 불가했습니다.
위 사진은 해당 상황을 재현하기 위해 설정한 것인데, 이처럼 동시에 인프라 상태를 수정할 수 없게하여 협업에 있어서 노이즈가 발생하지 않게 됩니다.
정리하며
당장에 떠오르는 장점들을 나열해봤는데, 명확함이라는 게 제가 생각하는 테라폼의 가장 큰 장점이라고 느껴집니다. 아직 테라폼을 공부한지 얼마되지 않아 모든 테라폼의 특징/장점을 나열하지는 못했지만, 앞서 언급한 것들만 해도 강력한 도구 중 하나라는 생각이 듭니다.
조금 더 이론을 숙달하고 향후에 실전에서 활용해보게 되었을 때의 느낀점과 인사이트를 공유하도록 하겠습니다. :)