DevOpsDevOps 학습 로드맵 · 4/9

Terraform과 Ansible로 클러스터를 코드에서 다시 세울 수 있게 만드는 절차

클라우드 리소스는 Terraform, 온프레미스 노드 프로비저닝은 Ansible로 코드화하고, Day-0부터 전체를 재현 가능한지 검증하는 절차 정리.

2026-08-018 min read
#Terraform#Ansible#IaC#Kubernetes

목표

시리즈 1에서 손으로 실행한 절차(VM 준비, kubeadm init/join, CNI/MetalLB/스토리지 설치)를 코드로 옮긴다. 클라우드 리소스는 Terraform, 온프레미스 노드의 OS 레벨 준비는 Ansible로 나눈다.

개념: IaC(Infrastructure as Code)란 무엇인가

콘솔에 로그인해 클릭으로 서버를 만들거나, SSH로 접속해 손으로 패키지를 설치하면 "지금 이 서버가 왜 이런 상태인지"는 그 작업을 한 사람의 기억에만 남는다. 시간이 지나거나 담당자가 바뀌면 같은 환경을 다시 만들 방법이 없다 - IaC는 인프라의 상태를 코드로 선언해두고, 그 코드를 실행하면 실제 인프라가 그 상태가 되도록 만드는 방식이다. 코드 자체가 곧 "지금 인프라가 어떤 모습이어야 하는지"에 대한 살아있는 문서가 된다.

IaC 도구는 크게 두 방식으로 나뉜다.

  • 선언적(declarative): "최종적으로 이런 모습이어야 한다"만 적으면, 도구가 현재 상태와 비교해서 차이만 계산해 적용한다. Terraform이 이 방식이다.
  • 절차적(procedural): "이 순서대로 이 작업들을 해라"를 적어두면 그대로 실행한다. Ansible이 이 방식이다.

두 방식 모두에서 **멱등성(idempotency)**이 핵심이다 - 같은 코드를 몇 번을 실행해도 결과가 항상 같아야 한다. "이미 존재하면 건너뛰고, 없으면 만든다"는 판단을 도구가 대신 해주기 때문에, 사람이 "이거 이미 실행했었나?"를 기억할 필요가 없어진다.

역할 분담: 왜 두 도구를 같이 쓰는가

TerraformAnsible
대상클라우드 리소스 (VPC, VM 인스턴스, 로드밸런서 등)이미 존재하는 온프레미스 서버의 OS 상태
모델선언적, 상태 파일(state)로 리소스 존재 여부를 추적절차적, 멱등성 있는 태스크를 순서대로 실행
적합한 이유클라우드 리소스는 "생성/삭제"가 명확한 단위온프레미스 서버는 이미 물리적으로 존재 - "생성"이 아니라 "구성" 대상

실제 구축 절차

1. Terraform - 클라우드 리소스 코드화

# terraform/main.tf
terraform {
  required_providers {
    aws = { source = "hashicorp/aws", version = "~> 5.0" }
  }
}

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

resource "aws_subnet" "public" {
  vpc_id     = aws_vpc.main.id
  cidr_block = "10.0.1.0/24"
}

resource "aws_instance" "cloud_worker" {
  ami           = "ami-0c02fb55956c7d316"
  instance_type = "t3.medium"
  subnet_id     = aws_subnet.public.id
  tags          = { Name = "hybrid-cloud-worker" }
}

output "cloud_worker_public_ip" {
  value = aws_instance.cloud_worker.public_ip
}
terraform init
terraform plan -out=tfplan     # 무엇이 바뀌는지 먼저 확인
terraform apply tfplan

state 파일이 하는 역할: Terraform은 .tf 코드만 보고는 "이 리소스가 실제 클라우드에 이미 있는지"를 알 수 없다. terraform.tfstate라는 파일에 "내가 마지막으로 만든 리소스가 무엇이었는지"를 기록해두고, 다음 실행 때 이 state와 코드를 비교해 차이(diff)만 적용한다. state 파일이 사라지거나 실제 상태와 어긋나면 Terraform은 잘못된 판단을 내릴 수 있다.

state 파일은 로컬에 두지 않는다 - 여러 사람/여러 세션에서 같은 인프라를 다루려면 원격 백엔드(S3+DynamoDB 락 등)로 옮겨야 한다. DynamoDB 락은 두 사람이 동시에 apply를 실행해 state가 꼬이는 것을 막는 역할이다.

terraform {
  backend "s3" {
    bucket         = "tfstate-bucket"
    key            = "cluster/terraform.tfstate"
    region         = "ap-northeast-2"
    dynamodb_table = "tfstate-lock"
  }
}

2. Ansible - 온프레미스 노드 프로비저닝

시리즈 1에서 손으로 했던 사전 준비(스왑 비활성화, 커널 모듈, containerd, kubeadm 설치)를 플레이북으로 옮긴다.

# ansible/inventory.ini
[control_plane]
node1 ansible_host=192.168.1.10

[workers]
node2 ansible_host=192.168.1.11
node3 ansible_host=192.168.1.12
# ansible/playbook.yml
- hosts: all
  become: true
  tasks:
    - name: 스왑 비활성화
      command: swapoff -a
    - name: fstab에서 스왑 항목 주석 처리
      replace:
        path: /etc/fstab
        regexp: '^([^#].*\sswap\s.*)$'
        replace: '# \1'
    - name: 커널 모듈 로드
      modprobe:
        name: "{{ item }}"
      loop: [overlay, br_netfilter]
    - name: containerd 설치
      apt:
        name: containerd
        state: present
    - name: kubeadm/kubelet/kubectl 설치
      apt:
        name: [kubelet, kubeadm, kubectl]
        state: present

- hosts: control_plane
  become: true
  tasks:
    - name: kubeadm init 실행 여부 확인
      stat: path=/etc/kubernetes/admin.conf
      register: kubeadm_done
    - name: kubeadm init
      command: >
        kubeadm init --pod-network-cidr=192.168.0.0/16
        --control-plane-endpoint={{ control_plane_vip }}:6443
      when: not kubeadm_done.stat.exists
ansible-playbook -i inventory.ini playbook.yml

워커 조인은 컨트롤플레인에서 토큰을 뽑아 워커 그룹에 전달하는 태스크를 추가로 작성한다 - kubeadm token create --print-join-command의 출력을 register로 잡아 워커 호스트에 delegate_to 없이 전달하려면, Ansible의 set_fact + hostvars를 조합하거나 별도 스크립트로 토큰을 파일에 저장해 공유하는 방식을 쓴다.

3. Day-0 재구축 검증

코드화가 끝났다고 선언하기 전에, 실제로 전체를 처음부터 재현할 수 있는지 검증하는 단계가 필요하다.

# 1. 기존 클러스터 완전 파괴
ansible all -i inventory.ini -m command -a "kubeadm reset -f" --become
terraform destroy

# 2. 코드만으로 처음부터 재구축
terraform apply
ansible-playbook -i inventory.ini playbook.yml

# 3. 소요 시간 측정
# destroy 시작 ~ 클러스터가 다시 Ready 상태가 될 때까지 걸린 시간을 기록

이 재구축 중 스크립트로 처리되지 않고 사람이 개입해야 했던 지점(예: 토큰 재생성, 순서 의존성, 수동 확인이 필요했던 단계)을 전부 목록화한다 - 이게 "아직 코드화되지 않은 부분"이다.

검증 방법

terraform plan     # apply 없이 실행해도 diff가 없어야("No changes") IaC가 실제 상태를 정확히 반영하는 것
ansible-playbook -i inventory.ini playbook.yml --check   # dry-run으로 멱등성 확인 - 두 번째 실행부터는 변경사항이 없어야 함

체크리스트

  • Terraform으로 클라우드 리소스 코드화 (원격 state 백엔드 포함)
  • Ansible로 온프레미스 노드 프로비저닝 (kubeadm 사전 준비까지 자동화)
  • Day-0 재구축 실험: 전체 파괴 후 코드만으로 재현
  • 재구축 중 사람이 개입해야 했던 지점 목록화