로컬 IaC(Infrastructure as Code)테스트 환경 구성하기

Paul
28 min readAug 20, 2023

--

OpenSource Tools for IaC(Infrastructure as Code)

AWS Multi-Account Multi-Region 환경에서 Terraform을 활용한 IaC 작업을 거의 4년간 수행해왔습니다. 처음에는 Terraform 프로젝트 폴더 구성부터 모듈 생성까지 수많은 시행착오를 겪었던 것 같습니다. (2019년 하시코프 한국 사용자 모임 발표를 참고해주세요) 그러나 이제는 저희만의 체계가 갖춰진 뒤로는 어려움 없이 작업할 수 있었습니다. 그 결과로 IaC 관련 작업을 체계적으로 정리하고, 더 나아가 LocalStack을 활용하여 로컬 테스트 환경을 구축해 보기로 했습니다.

로컬 IaC 테스트 환경은 다음과 같이 구성됩니다:

  • Vagrant : HashiCorp에서 개발한 가상화 소프트웨어로, 개발 환경의 생성 및 유지보수를 위한 오픈 소스 도구입니다. Windows OS 환경에서 Vagrant를 사용하여 VM(Ubuntu) 환경을 구성하였습니다. 단, Docker 환경만 있다면 Vagrant 없이도 로컬 테스트 환경을 구성할 수 있습니다.
  • Docker, Docker Compose : Terragrunt 및 Terraform 버전 관리(tfswitch, tgswitch) 및 환경 변수 자동화(direnv)를 위해 Docker로 환경을 구성하였습니다.
  • Terragrunt : Terraform 구성을 DRY (Don’t Repeat Yourself) 원칙에 따라 유지하기 위한 래퍼 도구입니다. 특히 AWS Multi-Account Multi-Region에서 Terraform 환경 변수 값을 관리하는 데 매우 유용합니다.
  • Terraform : HashiCorp에서 개발한 오픈 소스 코드형 인프라(IaC) 도구입니다.
  • Consul : HashiCorp에서 개발한 서비스 디스커버리 오픈 소스 도구입니다. 또한 Terraform의 Remote State의 백엔드로 사용이 가능합니다.
  • LocalStack: 클라우드 및 서버리스 앱을 오프라인에서 개발하고 테스트할 수 있게 해주는 로컬 AWS 클라우드 스택입니다. 비용을 들이지 않고 클라우드 애플리케이션을 테스트할 수 있는 AWS 서비스 모의를 위한 훌륭한 도구입니다. 일부 기능은 유료로 제공됩니다.
  • 기타(InfraCost, Checkov) : Infracost는 Terraform을 위한 클라우드 비용 리포트 기능을 제공하는 도구입니다. 또한 Checkov는 인프라스트럭처 코드를 검사하고 보안 및 권장 사항을 검토하는 오픈 소스 도구입니다.

1. 로컬 테스트 환경 구성

로컬 테스트 환경 구성

전체 소스는 아래에서 다운로드를 받습니다. 데모 환경은 Windwos 환경에 Vagrant 활용하여 가상 머신에 구성했습니다. 그러나 가상 환경 없어도 Docker 와 Docker Compose 설치되어 있다면 데모를 실행하는 데 문제가 없습니다.

  • 가상 환경 실행, 데모 소스 다운로드 및 실행
PS C:\Private\local-terraform-tutorial>
PS C:\Private\local-terraform-tutorial> vagrant up
==> vagrant: A new version of Vagrant is available: 2.3.7 (installed version: 2.3.4)!
==> vagrant: To upgrade visit: https://www.vagrantup.com/downloads.html

Bringing machine 'terraform' up with 'virtualbox' provider...
==> terraform: Importing base box 'bento/ubuntu-18.04'...
==> terraform: Matching MAC address for NAT networking...
==> terraform: Checking if box 'bento/ubuntu-18.04' version '202212.11.0' is up to date...
==> terraform: A newer version of the box 'bento/ubuntu-18.04' for provider 'virtualbox' is
==> terraform: available! You currently have version '202212.11.0'. The latest is version
==> terraform: '202303.13.0'. Run `vagrant box update` to update.
==> terraform: Setting the name of the VM: local-terraform-tutorial_terraform_1692356987293_80090
==> terraform: Clearing any previously set network interfaces...
==> terraform: Preparing network interfaces based on configuration...
terraform: Adapter 1: nat
...
  • Docker 활용한 로컬 Terraform 환경 구성

docker-compose를 실행하면 기본적으로 Terraform 환경을 구성하기 위한 Docker build가 실행됩니다. 이 과정에서 Terraform 테스트를 위한 LocalStack 및 Terraform Remote State를 위한 Consul 환경도 함께 구성됩니다.

# Dockerfile
# Terraform, Terrgrunt 환경 구성
FROM amazonlinux:2

USER root
LABEL "purpose"="local"
# Default Time Zone Change
# install required yum package (wget unzip tar gzip git vim jq)
# install direnv
# install terraform 버전관리를 위한 tfswitch 유틸
# install terragrunt 버전관리를 위한 tgswitch 유틸
# install infracost 비용 시뮬레이션을 위한 유틸
..
# 가상환경 접속
PS C:\Private\local-terraform-tutorial> vagrant ssh
Welcome to Ubuntu 18.04.6 LTS (GNU/Linux 4.15.0-200-generic x86_64)

* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage

System information as of Fri Aug 18 11:13:13 UTC 2023
...

# /vagrant 폴더로 이동합니다.
vagrant@terraform:~$ cd /vagrant/
vagrant@terraform:/vagrant$ ls
docker-compose.yml Dockerfile README.md scripts terraform Vagrantfile
vagrant@terraform:/vagrant$
# docker-compose 실행
vagrant@terraform:/vagrant$ docker-compose up -d
[+] Building 130.2s (6/6) FINISHED
=> [internal] load build definition from Dockerfile 0.2s
=> => transferring dockerfile: 1.87kB
...

docker-compose 실행이 완료되면, docker ps 명령어를 사용하여 3개의 컨테이너가 정상적으로 실행되었는지 확인합니다.

vagrant@terraform:/vagrant$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03a5082f93d6 vagrant-iac "/bin/bash" 7 seconds ago Up 2 seconds iac
6507d2cb3b9b localstack/localstack:2.2 "docker-entrypoint.sh" 7 seconds ago Up 3 seconds (health: starting) 0.0.0.0:4510-4559->4510-4559/tcp, 0.0.0.0:4566->4566/tcp, 5678/tcp localstack
  • Terraform 및 Terragrunt 버전 다운로드, 환경 변수 실행

Terraform 버전을 다운로드하고 관리하기 위해 tfswitch를 사용하며, Terragrunt 버전을 다운로드하고 관리하기 위해 tgswitch를 사용했습니다.

terraform tfswitch tools (https://github.com/warrensbox/terraform-switcher)
terragrunt tgswitch (https://github.com/warrensbox/tgswitch)

.envrc.example 파일을 .envrc로 이름을 변경하고, 그 안에 Terraform과 Terragrunt 버전 정보 및 AWS_ACCESS_KEY_ID와 AWS_SECRET_ACCESS_KEY 값을 설정합니다. direnv allow 명령어를 실행하여 환경 설정을 적용합니다


vagrant@terraform:/vagrant$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
03a5082f93d6 vagrant-iac "/bin/bash" 7 seconds ago Up 2 seconds iac
6507d2cb3b9b localstack/localstack:2.2 "docker-entrypoint.sh" 7 seconds ago Up 3 seconds (health: starting) 0.0.0.0:4510-4559->4510-4559/tcp, 0.0.0.0:4566->4566/tcp, 5678/tcp localstack
...
# Docker Container bash 접속
vagrant@terraform:/vagrant$
vagrant@terraform:/vagrant$ docker exec -it 03a5082f93d6 /bin/bash
bash-4.2# cd /terraform/
...

# Terraform 프로젝트 폴더로 이동
bash-4.2# cd local-terraform/
direnv: error /terraform/local-terraform/.envrc is blocked. Run `direnv allow` to approve its content
# direnv allow 명령어 실행하여 Terraform & Terragrunt 버전 다운로드
bash-4.2# direnv allow
direnv: loading /terraform/local-terraform/.envrc
Downloading to: /root/.terraform.versions
18082446 bytes downloaded
Switched terraform to version "1.0.11"
Downloading https://github.com/gruntwork-io/terragrunt/releases/download/v0.35.10/terragrunt_linux_amd64 to terragrunt_linux_amd64
Downloading ...
direnv: ([/usr/local/bin/direnv export bash]) is taking a while to execute. Use CTRL-C to give up.
36208735 bytes downloaded.
Switched terragrunt to version "0.35.10"
# .envrc 에서 환경 변수 실행
direnv: export +AWS_ACCESS_KEY_ID +AWS_SECRET_ACCESS_KEY

2. Terragrunt 와 Terraform 설정

Terragrunt 환경 변수 및 Terraform 서비스 모듈

AWS Multi-Account Multi-Region 환경에서 Terraform을 이용한 인프라스트럭처 코드(IaC) 구성의 핵심은 Account별 환경 변수, Region별 환경 변수, 그리고 각 서비스별로 사용되는 환경 변수 값을 DRY (Don’t Repeat Yourself) 원칙에 따라 효율적으로 구성하는 것이었습니다. 이를 위해 아래와 같은 규칙을 정하고 환경 변수 값을 관리하였습니다.

  • globals.tfvars : Account 전체에서 공통 환경 변수 값을 관리합니다.
  • locals.tfvars : Region에서 공통 환경 변수 값을 관리합니다.
  • common_vars.yaml : 각 서비스에서 사용되는 환경 변수 값 또는 Terraform으로 구성되어 있지 않은 리소스를 참조할 경우에 사용합니다
Terraform 모듈 서비스

처음에 Terraform을 사용하여 IaC를 구성할 때는 모듈을 직접 작성하거나 오픈 소스 모듈을 참고하여 다시 작성하는 방식을 채용했습니다. 그러나 이러한 방식은 유지 보수와 관리가 어려웠습니다. 그래서 Terraform 모듈은 외부 Terraform 레지스트리 또는 GitHub에 공개된 모듈을 사용하고, 각 버전을 명시하여 사용하는 방식으로 전환했습니다. 더 자세한 내용은 아래 내용을 참고하시기 바랍니다.

3. 로컬 Terraform Remote State 설정

Terraform에서 생성한 리소스와 해당 현재 상태 정보는 terraform.tfstate 파일로 관리됩니다. 여러 팀원이 동일한 인프라 코드를 작업하고 동일한 상태를 공유하여 일관성을 유지하고 충돌을 방지하기 위해 terraform.tfstate 파일을 외부 저장소에서 관리할 수 있습니다. 로컬 개발 환경에서는 HashiCorp Consul을 활용하여 Terraform 원격 상태(Remote State) 환경을 구성하겠습니다.

# docekr-compose.yaml 파일
service:
...
consul:
#image: consul:
image: hashicorp/consul:1.11.2
container_name: consul
restart: always
volumes:
# Vagrant 환경이 아니라면 아래의 Volumes binds 경로를 변경해야합니다.
- /opt/consul/config:/consul/config
- /opt/consul/data:/consul/data
- /opt/consul/logs:/consul/logs
command: consul agent -server -bootstrap-expect=1 -client=0.0.0.0 -ui -bind='{{ GetPrivateIP }}' -data-dir=/consul/data -node=consul-node -datacenter=dc1 -log-level=debug -enable-script-checks=true -config-dir=/consul/config
ports:
- "8500:8500" # Web UI
- "8600:8600/tcp"
- "8600:8600/udp"
logging: *logging

Terraform 에서 사용할 Terraform Remote State 정보를 설정합니다. 여기에서는 Consul 정보를 입력합니다.

# terragrunt.hcl
# consul
remote_state {
backend = "consul"
config = {
path = "dev/${path_relative_to_include()}/terraform.tfstate"
# access_token = "e33eweqeq"
address = "consul:8500"
scheme = "http"
datacenter = "dc1"
}
}

Terraform 서비스 모듈에서의 백엔드(backend) 정보를 Consul로 설정합니다.


# github.com/aws-alb/main.tf
provider "aws" {
region = var.aws_region
...
}

terraform {
# The configuration for this backend will be filled in by Terragrunt
backend "consul" {}
}

locals {
type = {
"application" = "alb"
"network" = "nlb"
}
}

terragrunt apply 명령어 실행을 완료한 후, Consul 서버의 키/값을 확인하시면 아래와 같이 tfstate 정보를 확인하실 수 있습니다. (http://192.168.56.122:8500 또는 http://localhost:8500)

Consul terraform.tfstate 정보

4. Terraform LocalStack 연동하기

Docker 활용하여 LocalStack 환경 구성을 하였습니다. LocalStack 설치 및 튜토리얼에 대한 자세한 내용은 LocalStack공식 웹사이트 또는 GitHub 페이지에서 확인하실 수 있습니다.

# docker-compose.yaml
service:
...
localstack:
image: localstack/localstack:2.2
container_name: localstack
ports:
- "0.0.0.0:4566:4566" # LocalStack Gateway
- "0.0.0.0:4510-4559:4510-4559" # external services port range
environment:
- DEBUG=0
- DOCKER_HOST=unix:///var/run/docker.sock
- AWS_ACCESS_KEY_ID="test"
- AWS_SECRET_ACCESS_KEY="test"
volumes:
- "/opt/localstack:/var/lib/localstack"
- "/var/run/docker.sock:/var/run/docker.sock"

Terraform AWS Provider 설정에서는 AWS 서비스 엔드포인트 정보를 사용자 지정 서비스로 설정하는 것이 가능합니다. 이를 위해 AWS 서비스 엔드포인트 정보를 LocalStack의 Gateway 포트 정보로 설정합니다. 자세한 사용자 지정 가능한 엔드 포인트 정보는 여기를 참고하세요.

# github.com/aws-alb/main.tf
provider "aws" {
region = var.aws_region

# LocalStack 설정
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true

# https://registry.terraform.io/providers/hashicorp/aws/5.13.1/docs/guides/custom-service-endpoints.html#available-endpoint-customizations
endpoints {
iam = "http://localstack:4566"
kinesis = "http://localstack:4566"
lambda = "http://localstack:4566"
s3 = "http://localstack:4566"
secretsmanager = "http://localstack:4566"
sns = "http://localstack:4566"
sqs = "http://localstack:4566"
ec2 = "http://localstack:4566"
elb = "http://localstack:4566"
elbv2 = "http://localstack:4566"
...
}
}

5. Terragrunt & Terraform 실행하기

지금까지 로컬 IaC 테스트 환경을 구성하는 방법에 대해 살펴보았습니다. 이제는 Terraform을 사용하여 AWS 리소스를 생성해보겠습니다. 데모에서는 ALB, EC2, Security Group, IAM Role, S3 리소스를 생성하기 위한 Terraform 코드를 작성하였습니다.

terragrunt graph-dependencies | dot -Tsvg > graph.svg

Terragrunt에서는 의존성 관계가 있기 때문에 S3 -> Security Group (ALB, 인스턴스) -> IAM 정책 -> IAM 역할 -> EC2 -> ALB의 순서로 생성하시면 됩니다. 아래는 AWS S3 리소스를 생성하고 삭제하는 예시입니다.

Terragrunt S3 apply

ALB 생성은 Terraform plan까지는 에러 없이 작동하지만, Terraform apply 시에는 오류가 발생합니다. 이 오류는 미구현 상태일 수도 있거나, pro 버전에서만 제공되는 기능일 수도 있습니다.

ALB Terraform apply 에러 화면

LocalStack Coverage 웹사이트에서 해당 기능을 검색하면 해당 기능의 지원 여부를 확인할 수 있습니다. 예를 들어, elbv2는 유료 버전에서만 제공되는 것으로 나와 있습니다.

LocalStack Coverage (https://docs.localstack.cloud/references/coverage/coverage_elbv2/)

LocalStack에서는 특정 기능이 유료 버전에서만 지원됩니다. 그러나 Terraform Plan까지만 테스트하면, 로컬 환경에서는 문제가 없을 것으로 생각이 됩니다.

5. InfraCost 연동하기

Infracost는 클라우드 인프라스트럭처를 코드로 관리하면서 발생하는 비용을 예측하고 관리하는 오픈 소스 도구입니다. 자세한 내용 및 설치, 튜토리얼은 Infracost의 공식 웹사이트GitHub 페이지에서 확인하실 수 있습니다.

InfraCost Pricing API 키 등록

인프라스트럭처 비용을 검색(예: 인스턴스 유형에 대한 가격)하기 위해 InfraCost CLI에서 사용하는 무료 API 키를 등록합니다. 이를 위해서는 InfraCost 회원가입 및 로그인이 필요합니다. (가상 환경에서는 아래의 방법으로는 등록되지 않습니다. InfraCost CLI 명령어를 사용하여 등록하셔야 합니다.)

bash-4.2# infracost auth login
We're redirecting you to our log in page, please complete that,
and return here to continue using Infracost.

If the redirect doesn't work, either:
- Use this URL:
https://dashboard.infracost.io/login?cli_port=32849&....

- Or log in/sign up at https://dashboard.infracost.io, copy your API key
from Org Settings and run `infracost configure set api_key MY_KEY`

Waiting...

아래와 같이 API 키를 직접 InfraCost CLI에 등록할 수 있습니다.

infracost configure set api_key <your-infracost-api-key>

Terraform plan 정보 저장하기

Infracost는 terraform plan 정보를 기반으로 예상 비용을 계산합니다. 따라서 terraform plan 실행 시 --out 파라미터를 사용하여 plan 정보를 저장할 수 있습니다

bash-4.2# terragrunt plan -out=plan.cache

...

Saved the plan to: plan.cache #plan.cache 파일이 생성이 됨.
To perform exactly these actions, run the following command to apply:
terraform apply "plan.cache"
bash-4.2#

AWS 리소스 비용 Report 생성하기

terraform plan 정보를 terraform show 명령어로 JSON 형식으로 변환한 후, InfraCost CLI를 사용하여 비용 리포트를 생성하는 스크립트 내용(infracost.sh)입니다.

#!/bin/bash

# Find the plan files
plans=($(find . -name plan.cache | tr '\n' ' '))

# Generate plan JSON files by running terragrunt show for each plan file
planjsons=()
for plan in "${plans[@]}"; do
# Find the Terraform working directory for running terragrunt show
# We want to take the dir of the plan file and strip off anything after the .terraform-cache dir
# to find the location of the Terraform working directory that contains the Terraform code
dir=$(dirname $plan)
working_dir=$(echo "$dir" | sed 's/\(.*\)\/\.terragrunt-cache\/.*/\1/')

echo "Running terragrunt show for $(basename $plan) for $working_dir";
terragrunt show -json $(basename $plan) --terragrunt-working-dir $working_dir --terragrunt-no-auto-init > $working_dir/plan.json
planjsons=(${planjsons[@]} "$working_dir/plan.json")
done

# Sort the plan JSONs so we get consistent project ordering in the config file
IFS=$'\n' planjsons=($(sort <<<"${planjsons[*]}"))

# Generate Infracost config file
echo -e "version: 0.1\n\nprojects:\n" > infracost.yml
for planjson in "${planjsons[@]}"; do
echo -e " - path: $planjson" >> infracost.yml
done

# Generate report
infracost breakdown --config-file=infracost.yml --format=json --out-file=infracost-base.json
infracost output --path infracost-base.json --format html --out-file report.html

InfraCost CLI의 breakdownoutput 명령어를 활용하면 현재 구성된 AWS 리소스에 대한 비용 리포트를 HTML 형식으로 생성할 수 있습니다. 또한 output에서는 다양한 형식(json, diff, table, html, github-comment, gitlab-comment, azure-repos-comment, bitbucket-comment, slack-message)으로 생성하고 저장할 수 있습니다.

AWS ALB, EC2 비용 리포트 정보

6. Checkov 보안 취약점 체크하기

Checkov는 주로 Terraform, CloudFormation과 같은 인프라스트럭처 코드를 검사하고 보안 및 권장 사항을 확인하는 오픈 소스 도구입니다. 이 도구를 사용하여 Terraform 코드를 체크할 때는 terraform plan 정보를 활용하여 보안 및 권장 사항을 확인할 수 있습니다.

# Example checkov
terragrunt init
terragrunt plan -out tf.plan
terragrunt show -json tf.plan > tf.json
checkov -f tf.json

이전에 InfraCost를 위해 생성한 terraform plan 정보를 사용하여 터미널에서 checkov 명령어를 실행합니다.

마무리하며

로컬 환경에서 Terraform을 테스트하기 위해 다양한 오픈 소스 도구를 활용하여 Terraform 코드의 테스트부터 비용, 보안까지를 체크할 수 있는 환경을 구축했습니다. 특히, 로컬 환경에서 Terraform 코드를 직접 테스트하고 모듈을 개발한 후에도 테스트가 가능합니다. 더욱이, AWS 계정을 생성하여 Terraform 테스트를 진행하는 것이 가장 좋은 방법입니다. 하지만 AWS의 비용에 대한 이해가 부족하거나 Terraform을 처음 접하는 분들을 위해 도움이 되었으면 합니다.

참고사이트

--

--