Azure Portal에서 수동으로 리소스를 생성하는 대신, Terraform을 사용하여 인프라를 코드로 정의하고(IaC) 자동화된 방식으로 배포했습니다.
Terraform을 사용하는 이유
VM을 생성하고, 네트워크를 설정하고, 방화벽 규칙을 추가하는 등 모든 과정을 Azure Portal에서 수동으로 진행하면 몇 가지 문제가 발생합니다.
- Human Error: 사람이 직접 작업하다 보면 실수가 발생하기 쉽습니다.
- 재사용성 및 확장성 부족: 동일한 환경을 다시 구축하려면 모든 과정을 반복해야 합니다.
- 변경 이력 관리의 어려움: 누가, 언제, 무엇을 변경했는지 추적하기 어렵습니다. (일명 'Infrastructure Drift')
이러한 문제들을 해결하기 위해 Terraform을 도입하여 인프라를 코드로 관리(IaC)하기로 했습니다.
Tech Stack
- Cloud: Azure
- Infrastructure as Code: Terraform
- CI/CD: Github Actions
- Application Environment:
- Azure VM (Standard_B2s)
- Docker & Docker Compose
- Services on VM:
- Node.js Application
- MongoDB (Cosmos DB 대체)
- Redis (Azure Cache for Redis 대체)
- Data Storage: Azure Blob Storage (파일 등 정적 데이터 저장용
인프라 구축을 위한 준비
Terraform으로 인프라를 생성하기 전, 몇 가지 준비가 필요합니다.
1) Azure CLI 설치 및 로그인
Terraform이 Azure와 통신하려면 Azure CLI를 통해 인증을 받아야 합니다.
## azure cli 설치 후 진행
## 아래 명령어 실행 후 azure subscription 선택
$ az login
2) ssh 키 생성
Terraform이 VM 생성할 때 등록할 ssh키를 생성합니다.
## SSH 키 생성
$ ssh-keygen -t rsa -b 4096 -C "{azure 가입 이메일}" -f ~/.ssh/azure_vm
## 공개 키 확인
# 이 키 값은 나중에 Terraform 변수로 사용됩니다.
$ cat ~/.ssh/azure_vm.pub
3) Terraform으로 인프라 코드 작성하기
프로젝트 구조
terraform/
├── main.tf # 전체 모듈을 호출하고 프로바이더를 설정하는 메인 파일
├── variables.tf # 사용할 변수들을 정의하는 파일
├── outputs.tf # 배포 후 출력할 값(VM IP 주소 등)을 정의하는 파일
├── terraform.tfvars # 변수에 실제 값을 할당하는 파일
└── modules/
├── acr/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── networking/ # 가상 네트워크, 서브넷 등 네트워크 관련 리소스
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
├── storage/
│ ├── main.tf
│ ├── variables.tf
│ └── outputs.tf
└── vm/ # 가상 머신, 보안 그룹 등 VM 관련 리소스
├── main.tf
├── variables.tf
├── outputs.tf
└── cloud-init.yaml # VM 생성 시 실행할 초기화 스크립트
핵심 코드 살펴보기
1. VM 모듈 (modules/vm/main.tf) 가상 머신을 생성하는 핵심 코드입니다. 여기서는 VM의 크기, 이미지, 네트워크 인터페이스, 그리고 SSH 공개 키를 지정합니다.
# Public IP Address
resource "azurerm_public_ip" "main" {
name = "${var.project_name}-${var.environment}-pip"
location = var.location
resource_group_name = var.resource_group_name
allocation_method = "Static"
sku = "Standard"
domain_name_label = "${var.project_name}-${var.environment}"
tags = var.tags
}
# Network Interface
resource "azurerm_network_interface" "main" {
name = "${var.project_name}-${var.environment}-nic"
location = var.location
resource_group_name = var.resource_group_name
ip_configuration {
name = "internal"
subnet_id = var.subnet_id
private_ip_address_allocation = "Dynamic"
public_ip_address_id = azurerm_public_ip.main.id
}
tags = var.tags
}
# Virtual Machine
resource "azurerm_linux_virtual_machine" "main" {
name = "${var.project_name}-${var.environment}-vm"
location = var.location
resource_group_name = var.resource_group_name
size = var.vm_size
admin_username = var.admin_username
network_interface_ids = [
azurerm_network_interface.main.id,
]
# SSH 키 인증 (비밀번호보다 안전)
admin_ssh_key {
username = var.admin_username
public_key = var.ssh_public_key
}
# OS 디스크 설정
os_disk {
name = "${var.project_name}-${var.environment}-osdisk"
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
disk_size_gb = 30
}
# Ubuntu 22.04 LTS 이미지
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
# VM 생성 시 실행할 스크립트
custom_data = base64encode(templatefile("${path.module}/cloud-init.yaml", {
jwt_secret = var.jwt_secret
storage_account_name = var.storage_account_name
storage_account_key = var.storage_account_key
storage_connection_string = var.storage_connection_string
}))
tags = var.tags
}
# Data Disk
resource "azurerm_managed_disk" "data" {
name = "${var.project_name}-${var.environment}-datadisk"
location = var.location
resource_group_name = var.resource_group_name
storage_account_type = "Premium_LRS"
create_option = "Empty"
disk_size_gb = 64
tags = var.tags
}
# Data Disk를 VM에 연결
resource "azurerm_virtual_machine_data_disk_attachment" "data" {
managed_disk_id = azurerm_managed_disk.data.id
virtual_machine_id = azurerm_linux_virtual_machine.main.id
lun = 0
caching = "ReadWrite"
}
2. cloud-init.yaml - cloud-init은 VM이 처음 부팅될 때 단 한 번 실행되는 스크립트로, 수동으로 서버에 접속해서 Docker나 Node.js를 설치할 필요가 없게 해줍니다.
#cloud-config
# 패키지 업데이트
package_update: true
package_upgrade: true
# 설치할 패키지
packages:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
- git
- make
# 실행할 명령어들
runcmd:
# Docker 설치
- curl -fsSL https://get.docker.com -o get-docker.sh
- sh get-docker.sh
- usermod -aG docker azureuser
# Docker Compose 설치
- curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
- chmod +x /usr/local/bin/docker-compose
# 데이터 디스크 마운트 (MongoDB 데이터 저장용)
- mkdir -p /data
- |
if [ -b /dev/sdc ]; then
mkfs.ext4 /dev/sdc
echo '/dev/sdc /data ext4 defaults,nofail 0 2' >> /etc/fstab
mount /data
fi
# 애플리케이션 디렉토리 생성
- mkdir -p /opt/photo-storage
- chown -R azureuser:azureuser /opt/photo-storage
# 환경 변수 파일 생성
- |
cat > /opt/photo-storage/.env << 'EOF'
NODE_ENV=production
PORT=3000
# MongoDB
MONGO_URI=mongodb://mongodb:27017/photo_storage
# Redis
REDIS_HOST=redis
REDIS_PORT=6379
# Azure Blob Storage (MinIO 대체)
AZURE_STORAGE_ACCOUNT_NAME=${storage_account_name}
AZURE_STORAGE_ACCOUNT_KEY=${storage_account_key}
AZURE_STORAGE_CONNECTION_STRING=${storage_connection_string}
AZURE_STORAGE_CONTAINER=photos
# JWT
JWT_SECRET=${jwt_secret}
EOF
# Docker Compose 파일은 GitHub Actions에서 배포됨
# 여기서는 기본 설정만 준비
# Docker 서비스 시작
- systemctl enable docker
- systemctl start docker
# 로그 디렉토리 생성
- mkdir -p /var/log/photo-storage
- chown -R azureuser:azureuser /var/log/photo-storage
# 재부팅 (Docker 그룹 적용)
power_state:
mode: reboot
timeout: 300
condition: True
3) 배포 및 실행
### terraform 디렉토리에서 초기화
$ terraform init
### terraform 실행계획 확인
$ terraform plan
### terraform 인프라 생성
$ terraform apply
인프라 생성 성공시 아래와 같은 명령어 수행 결과와 azure portal에서 생성된 인프라를 확인할 수 있습니다.


'Infra' 카테고리의 다른 글
| 방화벽 이용해서 폐쇄망 구축하기 (1) | 2025.08.20 |
|---|---|
| LVM 다뤄보기 (0) | 2025.02.16 |
| terraform으로 aws 인프라 구축하기 (3) (상태 관리) (0) | 2024.12.04 |
| terraform으로 aws 인프라 구축하기 (2) (Auto Scaling) (0) | 2024.12.04 |
| terraform으로 aws 인프라 구축하기 (1) (0) | 2024.12.04 |
