Ansible vs Terraform 차이점과 선택 가이드
Terraform은 인프라 프로비저닝을, Ansible은 서버 내부 설정을 담당합니다. 두 도구의 상태 관리 방식 차이와 혼용 시 발생하는 문제점, 그리고 상황별 적합한 도구 선택 기준을 명확하게 정리해 드립니다.
Ansible과 Terraform의 차이점 한 문장 요약
Ansible과 Terraform은 동일한 작업을 수행하는 두 도구 사이의 선택 문제가 아닙니다. Terraform은 서버, 디스크, 네트워크, DNS 레코드와 같은 인프라의 상태를 선언합니다. 반면 Ansible은 이미 존재하는 머신 내부의 패키지, 사용자, 설정 파일, 실행 중인 서비스와 같은 상태를 선언합니다. Terraform이 VPS를 생성한다면, Ansible은 그 VPS를 웹 서버로 변환합니다.
두 도구 모두 선언적이며 인프라를 코드로 관리하는 IaC(Infrastructure as Code)로 분류됩니다. 실제 차이점은 각 도구가 무엇을 기억하는지에 있습니다. Terraform은 코드의 모든 리소스를 API를 통해 생성된 실제 객체와 매핑하는 상태 파일을 작성하므로, 코드에서 5줄을 삭제하면 서버 1대를 제거해야 한다는 사실을 파악할 수 있습니다. Ansible은 실행 사이에 아무것도 기억하지 않습니다. SSH로 접속하여 머신을 검사한 뒤, 플레이북과 일치하지 않는 부분만 변경합니다.
이러한 차이점 하나가 이 가이드의 나머지 내용을 설명하며, 왜 두 작업을 하나의 도구로 혼용하면 문제가 발생하는지도 보여줍니다.
Terraform이 실제로 수행하는 작업
Terraform은 provider 플러그인을 통해 API와 통신합니다. 사용 중인 provider의 레지스트리 페이지에서 작성 가능한 리소스 유형을 정의하므로, 서로 다른 호스트의 서버는 서로 다른 인수(argument)를 가진 별개의 리소스 이름으로 취급됩니다.
resource "cloud_server" "web" {
name = "web1"
image = "ubuntu-24.04"
type = "small"
}
output "web_ip" {
value = cloud_server.web.ipv4_address
}cloud_server를 provider 문서에 명시된 리소스 유형으로 교체하십시오. output 블록은 이 가이드에서 가장 중요한 부분인데, Terraform에서 주소가 나가는 방식이기 때문입니다.
terraform init
terraform fmt -check
terraform validate
terraform plan -out=tfplan
terraform apply tfplanterraform init는 provider를 다운로드하고 lock 파일을 작성합니다. terraform plan는 코드와 state 파일 간의 차이점을 출력하며, 마지막에는 Plan: 1 to add, 0 to change, 0 to destroy.과 같은 줄이 표시됩니다. 이 줄을 매번 확인하십시오. 일부 인수는 제자리에서 변경할 수 없으며, plan 결과에는 해당 속성 옆에 # forces replacement이 표시되고 이어서 1 to add, 0 to change, 1 to destroy이 나타납니다. 해당 plan을 적용하면 서버가 삭제되고 새로운 빈 서버가 생성되는데, 이것이 바로 안전하다고 생각했던 데이터를 잃게 되는 원인입니다.
단순히 terraform apply를 실행하는 대신 plan을 파일로 저장하고 해당 파일을 적용하면, 검토한 내용이 그대로 실행됨을 보장할 수 있습니다. 두 명령어 사이에 다른 사람이 인프라를 변경했을 수도 있기 때문입니다.
terraform.tfstate은 메모리 역할을 합니다. 이 파일을 잃어버리면 Terraform은 해당 서버가 사용자의 것임을 인식하지 못하며, 다음 apply 시 중복 생성을 시도하게 됩니다. 두 명 이상의 사용자가 명령어를 실행하는 환경이라면 즉시 원격 백엔드(remote backend)에 저장하십시오. 두 사람이 동시에 apply를 실행하면 다음과 같은 결과가 발생합니다.
Error: Error acquiring the state lockOpenTofu는 Terraform의 포크 버전으로, 동일한 명령어와 파일 형식을 사용합니다. 2026년 7월 기준으로, terraform 대신 tofu을 입력해도 이 가이드의 모든 내용은 동일하게 작동합니다.
Ansible의 실제 동작 방식
Ansible은 에이전트나 API가 필요하지 않습니다. SSH 연결을 열고 작은 Python 모듈을 대상 서버로 복사한 뒤, 이를 실행하고 삭제하는 방식으로 동작합니다. SSH와 sudo 비밀번호로 접근할 수 있는 모든 환경을 Ansible로 구성할 수 있습니다.
- name: Base web server
hosts: web
become: true
tasks:
- name: Install nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
- name: Ensure nginx is running at boot
ansible.builtin.service:
name: nginx
state: started
enabled: trueansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.yml --check --diff
ansible-playbook -i inventory.ini site.ymlping 모듈은 플레이북 디버깅을 시작하기 전에 SSH, Python, sudo가 정상인지 확인합니다. 정상적인 결과는 web1 | SUCCESS => {"ping": "pong"}입니다. --check --diff 실행은 Ansible에서 계획과 가장 유사한 기능입니다. 실제 변경을 수행하지 않고 무엇이 변경될지 보고하지만, 이전 작업에 의존하는 태스크는 실제 변경이 일어나지 않았기 때문에 체크 모드에서 잘못된 결과를 보고할 수 있습니다.
모든 실행은 ok=6 changed=2 unreachable=0 failed=0과 같은 요약으로 끝납니다. 동일한 플레이북을 두 번 실행하십시오. 두 번째 실행에서는 changed=0이 보고되어야 합니다. 매번 changed를 보고하는 태스크는 멱등성(idempotent)을 갖추지 못한 것이며, 이는 보통 command이나 shell 태스크로서 실제 모듈로 대체되어야 합니다. 이 분야가 처음이라면 단일 VPS에서 첫 Ansible 플레이북 작성하기부터 시작하여 점진적으로 확장하십시오.
두 도구의 기능이 겹치는 영역과 충돌하는 지점
Terraform은 remote-exec 프로비저너를 사용하여 새 서버에서 명령을 실행할 수 있습니다. HashiCorp의 공식 문서에서도 프로비저너는 최후의 수단으로 사용할 것을 권장합니다. 그럴 만한 타당한 이유가 있습니다.
프로비저너는 리소스가 생성될 때만 실행됩니다. 스크립트를 수정해도 기존 서버에는 아무런 변화가 없습니다. Terraform의 관점에서는 리소스가 이미 코드와 일치한다고 판단하기 때문입니다. 프로비저너 단계는 terraform plan에 나타나지 않으므로 코드 리뷰 시 해당 내용을 확인할 수 없습니다. 스크립트가 실패하면 Terraform은 해당 리소스를 tainted 상태로 표시하며, 다음 apply 실행 시 정상 작동하던 서버를 삭제하고 다시 생성해 버립니다.
실패가 발생하는 시점도 적절하지 않습니다. 프로바이더는 API가 서버 생성을 알리는 즉시 서버가 생성되었다고 보고하지만, 실제 운영체제는 아직 부팅 중이며 sshd는 아직 수신 대기 상태가 아닐 수 있습니다.
Error: remote-exec provisioner error
timeout - last error: dial tcp 203.0.113.10:22: connect: connection refusedAnsible은 정반대의 유혹을 제공합니다. 클라우드 모듈로 서버를 생성할 수 있으며, 소수의 머신을 다룰 때는 이 방식이 효과적입니다. 하지만 이 경우 의존성 그래프와 상태 파일을 포기해야 합니다. Ansible은 리소스를 생성하지만, 플레이북에서 해당 작업을 삭제해도 리소스는 계속 실행되며 비용이 청구됩니다. 해당 리소스가 사용자의 소유라는 기록이 어디에도 남지 않기 때문입니다.
이로부터 도출되는 규칙은 다음과 같습니다. API를 통해 생성하고 삭제하는 객체는 Terraform이 관리하게 하고, 부팅된 운영체제 내부의 모든 설정은 Ansible이 관리하게 하십시오.
핸드오프, 성공
핸드오프는 통합이 아닌 경계입니다. Terraform은 작업을 마치고 주소를 게시한 뒤 종료됩니다. Ansible은 그 주소에서 시작합니다.
terraform apply -auto-approve
terraform output -raw web_ip
printf '[web]\n%s ansible_user=root\n' "$(terraform output -raw web_ip)" > inventory.ini
ansible -i inventory.ini web -m ansible.builtin.ping
ansible-playbook -i inventory.ini site.ymlterraform output -raw은 따옴표나 JSON 래퍼 없이 하나의 값을 출력하며, 이는 셸 치환 내부에서 사용하기에 적합합니다. 여러 서버의 경우 terraform output -json를 사용하여 인벤토리를 구성하십시오. -raw는 단일 문자열, 숫자 또는 불리언 값만 처리하기 때문입니다.
두 도구 사이의 ping 단계는 유지할 가치가 있습니다. 이 단계는 "Terraform이 잘못된 주소를 제공함"과 "플레이북에 버그가 있음"이라는 문제를 분리해 줍니다. 플레이북이 새로운 서버에 처음으로 접근하는 도구라면, 이 두 가지 문제는 동일한 현상으로 나타나기 때문입니다.
Terraform 상태를 Ansible 인벤토리로 읽기
인벤토리 파일을 전혀 작성하고 싶지 않다면 cloud.terraform 컬렉션을 사용하여 상태를 직접 읽을 수 있습니다.
ansible-galaxy collection install cloud.terraform플레이북 옆에 terraform.yml을 작성하십시오:
plugin: cloud.terraform.terraform_provider
project_path: /home/deploy/infraansible-inventory -i terraform.yml --graph
ansible-playbook -i terraform.yml site.yml이 방법을 사용하기 전에 알아두어야 할 두 가지 사항이 있습니다. 이 플러그인은 project_path에 대해 terraform show를 실행하므로, 해당 디렉터리가 이미 초기화되어 있어야 하며 그렇지 않으면 플러그인이 실패합니다. 또한 이 플러그인은 서버 리소스에서 호스트를 자동으로 생성하지 않습니다. 대신 Terraform 코드에서 Ansible 프로바이더를 사용하여 선언한 ansible_host 및 ansible_group 리소스를 읽습니다. 이를 추가하기 전까지는 ansible-inventory --graph에 아무것도 나타나지 않습니다.
일반적으로 생성된 인벤토리 파일은 디버깅이 더 쉽고 모든 프로바이더에서 작동합니다. 이 플러그인은 인벤토리가 몇 대 이상의 머신으로 늘어나고 수동 편집으로 인해 오타가 발생하기 시작할 때 유용합니다. 이는 한 대의 제어 머신에서 여러 Linux 서버 관리하기가 단순한 습관을 넘어 실제 워크플로우가 되는 시점과 동일합니다.
Terraform이 정말로 필요한가?
이 글을 읽는 대부분의 사용자는 아직 Terraform이 필요하지 않습니다. 인프라를 생성하고 삭제하는 작업이 반복될 때 비로소 Terraform의 가치가 드러납니다. 제어판을 통해 VPS를 한 대 주문하고 2년 동안 유지할 계획이라면, Terraform은 단 한 번 일어날 일을 기술하는 도구일 뿐이며, 절대 잃어버려서는 안 되는 상태 파일(state file)만 하나 더 늘어날 뿐입니다.
환경을 자주 재구축해야 하거나, 스테이징 환경이 운영 환경과 정확히 일치해야 할 때, 혹은 여러 사람이 인프라를 변경하여 삭제 작업 전 검토 가능한 계획이 필요할 때 Terraform을 사용하십시오. 관리 대상이 서버를 넘어 공급자 API에 존재하는 DNS 레코드, 로드 밸런서, 방화벽 규칙까지 확장될 때도 마찬가지입니다.
서버의 수명이 길고 대수가 적으며, 매일 하는 고민이 "이 서버가 존재하는가"가 아니라 "이 서버가 올바르게 설정되었는가"라면 Ansible만 사용하는 것이 좋습니다. 새로운 서버를 강화하는 단일 플레이북은 새 VPS에서 처음 10분 동안 수행하는 작업과 동일한 범위를 다루며, 다음 서버에서도 동일하게 실행할 수 있다는 장점이 있습니다.
학습 순서는 이 기준을 따릅니다. Ansible은 소유한 첫 번째 서버부터 즉각적인 이득을 줍니다. 반면 Terraform은 세 번째로 환경을 재구축할 때부터 투자 대비 효과가 나타납니다.
핸드오프 과정에서 발생하는 문제
서버가 준비되지 않았습니다. Terraform은 성공하지만 Ansible은 즉시 실패합니다.
fatal: [web1]: UNREACHABLE! => {"changed": false, "msg": "Failed to connect to the host via ssh: ssh: connect to host 203.0.113.10 port 22: Connection refused", "unreachable": true}API가 sshd가 리스닝 상태가 되기 전에 주소를 반환했습니다. 고정된 sleep 시간을 추가하는 대신 포트가 열릴 때까지 대기하십시오. Ansible에는 이를 위한 ansible.builtin.wait_for_connection가 있으며, 플레이의 첫 번째 작업으로 실행하면 됩니다. 동일한 플레이북이 단일 신규 서버가 아닌 그룹을 대상으로 할 경우, 한 호스트에 연결할 수 없을 때 어떻게 처리할지 미리 결정하십시오. Ansible은 해당 호스트를 나머지 실행 과정에서 제외하며, 결과 요약 줄에서만 이를 알리기 때문입니다.
호스트 키가 변경되었습니다. 서버를 삭제하고 다시 생성하면, 새로운 서버가 동일한 주소에서 새로운 키로 응답합니다.
Host key verification failed.ssh-keygen -R 203.0.113.10을 사용하여 오래된 항목을 제거하십시오. Terraform으로 재구축을 수행할 때 빈번하게 발생하는 문제이므로, 데이터가 저장되는 머신은 가급적 재구축을 지양하는 것이 좋습니다.
Sudo가 실패합니다. fatal: [web1]: FAILED! => {"msg": "Missing sudo password"}은 해당 호스트에서 become: true에 암호가 필요함을 의미합니다. 배포 사용자에게 암호 없는 sudo를 설정하거나, --ask-become-pass를 전달하십시오.
Terraform이 수정하지 않은 항목을 삭제하려고 합니다. 계획(plan)에 작성하지 않은 변경 사항이 나타난다면, 실제 인프라가 코드와 일치하지 않는 상태(drift)가 된 것입니다. 보통 누군가 제공자의 웹 패널에서 설정을 변경했을 때 발생합니다. terraform plan -refresh-only을 실행하여 차이점을 직접 확인한 뒤, 코드와 실제 리소스 중 어느 쪽이 잘못되었는지 판단하십시오. 줄 단위로 설명할 수 없는 파괴적인 계획은 절대 적용하지 마십시오.
Ansible이 매 실행마다 변경(changed)되었다고 보고합니다. creates나 when 가드가 없는 shell 작업은 무조건 실행됩니다. 이는 단순히 보기의 문제가 아닙니다. 서버가 원하는 상태에 도달했음을 알리는 신호로 changed=0를 더 이상 사용할 수 없게 되기 때문입니다.
FAQ
Terraform이 Ansible을 대체할 수 있습니까?
서버 내부 구성 관리 용도로는 대체할 수 없습니다. Terraform은 remote-exec 프로비저너를 통해 스크립트를 실행할 수 있지만, 이는 리소스 생성 시점에만 동작하며 terraform plan에 기록되지 않습니다. 또한 스크립트 실패 시 리소스가 오염(taint)되어 다음 적용(apply) 시점에 삭제 후 재생성되는 문제가 발생합니다. Terraform에는 nginx가 이미 설치되어 있는지 확인하고 아무 작업도 하지 않는 모듈과 같은 기능이 없습니다. Terraform으로 머신을 생성한 뒤, 이후 작업은 Ansible로 넘기는 것이 좋습니다.
Ansible이 Terraform을 대체할 수 있습니까?
장기간 운영되는 소수의 서버라면 가능합니다. Ansible에는 서버를 생성하는 클라우드 모듈이 있으며, VPS 인스턴스 두 대를 생성하고 계속 유지하는 정도라면 충분합니다. 하지만 상태 파일(state file)과 의존성 그래프(dependency graph)가 없다는 점이 단점입니다. 플레이북에서 작업을 삭제해도 리소스는 계속 실행되며 비용이 발생하는데, 이는 Ansible이 해당 리소스를 생성했다는 사실을 기록하지 않기 때문입니다. Terraform을 사용했다면 삭제 계획(destroy plan)이 수립되었을 것입니다.
무엇을 먼저 배워야 합니까?
현재 운영 중인 서버가 있다면 Ansible을 먼저 배우십시오. 첫 번째 머신부터 즉각적인 효과를 볼 수 있고, SSH 외에는 아무것도 필요하지 않으며, 수동으로 생성한 서버에도 기술을 바로 적용할 수 있습니다. Terraform은 환경을 반복적으로 재구축하거나 DNS 레코드, 방화벽 규칙 등 서버 외의 프로바이더 리소스를 관리할 때 더 큰 가치를 발휘합니다.
Terraform에서 생성한 새 서버의 IP를 Ansible로 어떻게 전달합니까?
Terraform 코드에 output을 선언하고, 적용(apply) 후에 이를 읽어오면 됩니다. terraform output -raw web_ip은 셸 치환을 위해 순수 값만 출력하며, terraform output -json는 여러 호스트가 있을 때 모든 출력을 한 번에 보여줍니다. 이 값을 인벤토리 파일에 기록하거나, cloud.terraform 컬렉션을 설치한 뒤 ansible-inventory -i terraform.yml --graph이 프로젝트 디렉터리를 가리키도록 설정하십시오.
Terraform 작업이 끝나자마자 플레이북이 실패하는 이유는 무엇입니까?
프로바이더는 API가 서버 생성 완료를 알리는 즉시 성공으로 보고하지만, 실제 운영체제는 부팅 중인 상태이므로 초기 몇 초간은 SSH 연결이 거부됩니다. 이때 발생하는 오류는 Connection refused과 함께 나타나는 UNREACHABLE!입니다. 부팅 시간은 이미지와 플랜에 따라 다르므로, 임의로 대기 시간을 설정하는 대신 ansible.builtin.wait_for_connection를 플레이의 첫 번째 작업으로 추가하십시오.