Proxmox VE Automation
수동 클릭은 여기서 끝 — API, Terraform, Ansible로 인프라를 코드로 관리함
📌 이 글의 목적
지금까지 8편에 걸쳐 Proxmox를 설치하고, 네트워크/스토리지를 구성하고, VM/CT를 만들고, 클러스터/HA/백업까지 다뤘음. 마지막 퍼즐은 이 모든 것을 코드로 자동화하는 것임.
이 글을 읽고 나면:
- Proxmox REST API를 직접 호출할 수 있음
- Terraform으로 VM/CT를 선언적으로 프로비저닝할 수 있음
- Ansible로 VM/CT 내부 설정을 자동화할 수 있음
- Terraform + Cloud-Init + Ansible을 통합한 워크플로우를 구성할 수 있음
1. Proxmox API
1.1 API 구조
Proxmox의 모든 기능은 REST API로 노출됨. 웹 UI도 이 API를 호출할 뿐임.
API 엔드포인트 구조:
https://<host>:8006/api2/json/
├── /access/ # 인증, 사용자, 역할
├── /cluster/ # 클러스터, HA, 방화벽
├── /nodes/ # 노드 관리
│ └── /<node>/
│ ├── /qemu/ # VM 관리
│ │ └── /<vmid>/
│ │ ├── /status/ # 시작/중지
│ │ ├── /config # 설정
│ │ ├── /snapshot/ # 스냅샷
│ │ └── /clone # 복제
│ ├── /lxc/ # CT 관리
│ ├── /storage/ # 스토리지
│ ├── /network/ # 네트워크
│ └── /tasks/ # 작업 상태
├── /pools/ # 리소스 풀
└── /storage/ # 스토리지
1.2 인증
API Token (권장):
# API 토큰 생성
pveum user token add root@pam terraform --privsep=0
# 출력:
# ┌──────────────┬──────────────────────────────────────┐
# │ key │ value │
# ╞══════════════╪══════════════════════════════════════╡
# │ full-tokenid │ root@pam!terraform │
# │ info │ {"privsep":"0"} │
# │ value │ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx │
# └──────────────┴──────────────────────────────────────┘
# ⚠️ value(시크릿)는 이 순간만 표시됨 — 반드시 저장
| 인증 방식 | 헤더 | 용도 |
|---|---|---|
| API Token | Authorization: PVEAPIToken=<tokenid>=<secret> | ✅ 자동화 |
| Ticket (세션) | Cookie: PVEAuthCookie=<ticket> | 웹 UI |
| Password | POST /access/ticket | 임시/테스트 |
💡 API Token은 privsep(권한 분리) 설정에 주의.
privsep=0이면 토큰이 사용자의 전체 권한을 가짐.privsep=1이면 토큰에 별도 권한을 할당해야 함. 자동화 초기에는privsep=0이 편하지만, 프로덕션에서는 최소 권한 원칙을 적용.
1.3 pvesh (CLI)
# 노드 목록
pvesh get /nodes
# VM 목록
pvesh get /nodes/pve1/qemu
# VM 설정 확인
pvesh get /nodes/pve1/qemu/100/config
# VM 시작
pvesh create /nodes/pve1/qemu/100/status/start
# VM 생성
pvesh create /nodes/pve1/qemu -vmid 100 -name test-vm -memory 2048 -cores 2 \
-scsi0 local-lvm:32 -net0 virtio,bridge=vmbr0
# 출력 형식 지정
pvesh get /nodes/pve1/qemu --output-format json-pretty
1.4 curl로 API 호출
# 환경 변수 설정
export PVE_HOST="https://192.168.1.100:8006"
export PVE_TOKEN="root@pam!terraform"
export PVE_SECRET="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# 노드 목록
curl -s -k \
-H "Authorization: PVEAPIToken=${PVE_TOKEN}=${PVE_SECRET}" \
"${PVE_HOST}/api2/json/nodes" | jq .
# VM 목록
curl -s -k \
-H "Authorization: PVEAPIToken=${PVE_TOKEN}=${PVE_SECRET}" \
"${PVE_HOST}/api2/json/nodes/pve1/qemu" | jq '.data[] | {vmid, name, status}'
# VM 시작
curl -s -k -X POST \
-H "Authorization: PVEAPIToken=${PVE_TOKEN}=${PVE_SECRET}" \
"${PVE_HOST}/api2/json/nodes/pve1/qemu/100/status/start"
# VM 생성
curl -s -k -X POST \
-H "Authorization: PVEAPIToken=${PVE_TOKEN}=${PVE_SECRET}" \
-d "vmid=100&name=test-vm&memory=2048&cores=2" \
"${PVE_HOST}/api2/json/nodes/pve1/qemu"
1.5 Python (proxmoxer)
# pip install proxmoxer requests
from proxmoxer import ProxmoxAPI
prox = ProxmoxAPI(
'192.168.1.100',
user='root@pam',
token_name='terraform',
token_value='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx',
verify_ssl=False
)
# 노드 목록
for node in prox.nodes.get():
print(f"{node['node']}: {node['status']}")
# VM 목록
for vm in prox.nodes('pve1').qemu.get():
print(f"VM {vm['vmid']}: {vm['name']} ({vm['status']})")
# VM 시작
prox.nodes('pve1').qemu(100).status.start.post()
# VM Clone
prox.nodes('pve1').qemu(910).clone.post(
newid=100,
name='web-server-01',
full=1,
target='pve1'
)
2. Terraform
2.1 왜 Terraform인가
| 수동 | Terraform |
|---|---|
| 재현 불가 | 코드 = 문서 |
| 실수 가능 | Plan으로 미리보기 |
| 변경 추적 없음 | State로 변경 추적 |
| 10대 만들기 어려움 | count = 10 |
| 롤백 어려움 | terraform destroy |
2.2 Provider 설정
# versions.tf
terraform {
required_version = ">= 1.5.0"
required_providers {
proxmox = {
source = "bpg/proxmox"
version = ">= 0.50.0"
}
}
}
# provider.tf
provider "proxmox" {
endpoint = "https://192.168.1.100:8006/"
api_token = "root@pam!terraform=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
# 또는 환경변수: PROXMOX_VE_API_TOKEN
insecure = true # 자체 서명 인증서 허용
ssh {
agent = true
node {
name = "pve1"
address = "192.168.1.100"
}
}
}
💡 Provider 선택: Proxmox Terraform Provider는 여러 개 존재함.
bpg/proxmox가 현재 가장 활발하게 유지보수되고 기능이 풍부함. 기존Telmate/proxmox는 유지보수가 느려지고 있음.
2.3 VM 프로비저닝
Cloud-Init 템플릿에서 Clone:
# vm.tf
resource "proxmox_virtual_environment_vm" "web_server" {
count = 3
name = "web-${format("%02d", count.index + 1)}"
node_name = "pve1"
clone {
vm_id = 910 # Cloud-Init 템플릿
full = true
}
# CPU
cpu {
cores = 2
sockets = 1
type = "x86-64-v2-AES"
}
# 메모리
memory {
dedicated = 4096
}
# 디스크
disk {
datastore_id = "local-lvm"
size = 32
interface = "scsi0"
iothread = true
ssd = true
discard = "on"
}
# 네트워크
network_device {
bridge = "vmbr0"
model = "virtio"
}
# Cloud-Init
initialization {
ip_config {
ipv4 {
address = "192.168.1.${count.index + 101}/24"
gateway = "192.168.1.1"
}
}
dns {
servers = ["8.8.8.8", "8.8.4.4"]
domain = "lab.local"
}
user_account {
username = "admin"
keys = [file("~/.ssh/id_ed25519.pub")]
}
}
# Guest Agent
agent {
enabled = true
}
# 태그
tags = ["web", "production"]
lifecycle {
ignore_changes = [
initialization[0].user_account[0].keys,
]
}
}
# Output
output "web_server_ips" {
value = [for vm in proxmox_virtual_environment_vm.web_server : vm.ipv4_addresses]
}
2.4 CT 프로비저닝
# ct.tf
resource "proxmox_virtual_environment_container" "dns_server" {
description = "Pi-hole DNS Server"
node_name = "pve1"
vm_id = 210
operating_system {
template_file_id = "local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst"
type = "debian"
}
unprivileged = true
features {
nesting = true
}
cpu {
cores = 1
}
memory {
dedicated = 256
swap = 128
}
disk {
datastore_id = "local-lvm"
size = 4
}
network_interface {
name = "eth0"
bridge = "vmbr0"
firewall = true
}
initialization {
hostname = "pihole"
ip_config {
ipv4 {
address = "192.168.1.53/24"
gateway = "192.168.1.1"
}
}
dns {
servers = ["8.8.8.8"]
domain = "lab.local"
}
}
startup {
order = 1
up = 15
down = 60
}
start_on_boot = true
tags = ["dns", "infrastructure"]
}
2.5 변수와 모듈화
# variables.tf
variable "proxmox_endpoint" {
type = string
default = "https://192.168.1.100:8006/"
}
variable "proxmox_token" {
type = string
sensitive = true
}
variable "ssh_public_key" {
type = string
default = "~/.ssh/id_ed25519.pub"
}
variable "web_servers" {
type = map(object({
cores = number
memory = number
disk = number
ip = string
}))
default = {
"web-01" = { cores = 2, memory = 4096, disk = 32, ip = "192.168.1.101" }
"web-02" = { cores = 2, memory = 4096, disk = 32, ip = "192.168.1.102" }
"web-03" = { cores = 4, memory = 8192, disk = 64, ip = "192.168.1.103" }
}
}
# main.tf (for_each 활용)
resource "proxmox_virtual_environment_vm" "web" {
for_each = var.web_servers
name = each.key
node_name = "pve1"
clone {
vm_id = 910
full = true
}
cpu {
cores = each.value.cores
type = "x86-64-v2-AES"
}
memory {
dedicated = each.value.memory
}
disk {
datastore_id = "local-lvm"
size = each.value.disk
interface = "scsi0"
iothread = true
}
network_device {
bridge = "vmbr0"
model = "virtio"
}
initialization {
ip_config {
ipv4 {
address = "${each.value.ip}/24"
gateway = "192.168.1.1"
}
}
user_account {
username = "admin"
keys = [file(var.ssh_public_key)]
}
}
agent {
enabled = true
}
}
2.6 Terraform 실행
# 초기화
terraform init
# 계획 확인
terraform plan
# 적용
terraform apply
# 특정 리소스만 적용
terraform apply -target=proxmox_virtual_environment_vm.web["web-01"]
# 삭제
terraform destroy
# 상태 확인
terraform state list
terraform state show proxmox_virtual_environment_vm.web["web-01"]
3. Ansible
3.1 역할 분담
| 도구 | 역할 | "무엇"을 관리 |
|---|---|---|
| Terraform | 인프라 프로비저닝 | VM/CT 생성, 리소스 할당 |
| Ansible | 구성 관리 | OS 내부 설정, 패키지, 서비스 |
3.2 Inventory
# inventory/hosts.ini
[proxmox]
pve1 ansible_host=192.168.1.100
pve2 ansible_host=192.168.1.101
pve3 ansible_host=192.168.1.102
[proxmox:vars]
ansible_user=root
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
[webservers]
web-01 ansible_host=192.168.1.101
web-02 ansible_host=192.168.1.102
web-03 ansible_host=192.168.1.103
[webservers:vars]
ansible_user=admin
ansible_become=yes
[dns]
pihole ansible_host=192.168.1.53
[monitoring]
grafana ansible_host=192.168.1.90
동적 Inventory (Proxmox API):
# inventory/proxmox.yml
plugin: community.general.proxmox
url: https://192.168.1.100:8006
user: root@pam
token_id: terraform
token_secret: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
validate_certs: false
# VM을 태그로 그룹화
groups:
webservers: "'web' in proxmox_tags_parsed"
databases: "'db' in proxmox_tags_parsed"
infrastructure: "'infrastructure' in proxmox_tags_parsed"
# 동적 Inventory 설치
ansible-galaxy collection install community.general
# 인벤토리 확인
ansible-inventory -i inventory/proxmox.yml --list
3.3 Playbook — 웹 서버 설정
# playbooks/setup-webserver.yml
---
- name: Configure Web Servers
hosts: webservers
become: yes
vars:
nginx_worker_processes: auto
certbot_email: admin@lab.local
tasks:
- name: Update apt cache
apt:
update_cache: yes
cache_valid_time: 3600
- name: Install packages
apt:
name:
- nginx
- certbot
- python3-certbot-nginx
- htop
- vim
- curl
- ufw
state: present
- name: Configure Nginx
template:
src: templates/nginx.conf.j2
dest: /etc/nginx/nginx.conf
backup: yes
notify: Reload Nginx
- name: Enable and start Nginx
systemd:
name: nginx
enabled: yes
state: started
- name: Configure UFW
ufw:
rule: allow
name: "{{ item }}"
loop:
- 'Nginx Full'
- 'OpenSSH'
- name: Enable UFW
ufw:
state: enabled
default: deny
- name: Set timezone
timezone:
name: Asia/Seoul
handlers:
- name: Reload Nginx
systemd:
name: nginx
state: reloaded
3.4 Playbook — Proxmox 호스트 설정
# playbooks/setup-proxmox-host.yml
---
- name: Configure Proxmox Hosts
hosts: proxmox
become: yes
tasks:
- name: Disable enterprise repository
lineinfile:
path: /etc/apt/sources.list.d/pve-enterprise.list
regexp: '^deb'
line: '#deb https://enterprise.proxmox.com/debian/pve bookworm pve-enterprise'
- name: Add no-subscription repository
copy:
content: "deb http://download.proxmox.com/debian/pve bookworm pve-no-subscription\n"
dest: /etc/apt/sources.list.d/pve-no-subscription.list
- name: Update packages
apt:
update_cache: yes
upgrade: full
- name: Configure NTP
copy:
content: |
server time.google.com iburst
server time.cloudflare.com iburst
driftfile /var/lib/chrony/chrony.drift
makestep 1.0 3
rtcsync
dest: /etc/chrony/chrony.conf
notify: Restart Chrony
- name: Install monitoring tools
apt:
name:
- prometheus-node-exporter
- smartmontools
state: present
- name: Enable node exporter
systemd:
name: prometheus-node-exporter
enabled: yes
state: started
handlers:
- name: Restart Chrony
systemd:
name: chrony
state: restarted
3.5 Role 구조
ansible/
├── inventory/
│ ├── hosts.ini
│ └── proxmox.yml # 동적 Inventory
├── playbooks/
│ ├── setup-proxmox-host.yml
│ ├── setup-webserver.yml
│ └── site.yml # 마스터 Playbook
├── roles/
│ ├── common/ # 모든 서버 공통
│ │ ├── tasks/main.yml
│ │ ├── handlers/main.yml
│ │ └── templates/
│ ├── webserver/
│ │ ├── tasks/main.yml
│ │ ├── handlers/main.yml
│ │ ├── templates/
│ │ └── vars/main.yml
│ ├── monitoring/
│ │ └── ...
│ └── security/
│ └── ...
└── ansible.cfg
3.6 실행
# Playbook 실행
ansible-playbook -i inventory/hosts.ini playbooks/setup-webserver.yml
# 특정 호스트만
ansible-playbook -i inventory/hosts.ini playbooks/setup-webserver.yml --limit web-01
# Dry-run (확인만)
ansible-playbook -i inventory/hosts.ini playbooks/setup-webserver.yml --check --diff
# 태그로 특정 작업만
ansible-playbook -i inventory/hosts.ini playbooks/setup-webserver.yml --tags "nginx"
4. Cloud-Init
4.1 Cloud-Init이란
Cloud-Init은 VM이 첫 부팅될 때 자동으로 초기 설정을 수행하는 업계 표준 도구임. AWS, GCP, Azure, OpenStack 등 대부분의 클라우드 환경에서 사용되며, Proxmox도 네이티브로 지원함.
Cloud-Init이 처리하는 항목:
| 항목 | 설명 | 예시 |
|---|---|---|
| 호스트명 | VM 호스트명 자동 설정 | web-01.lab.local |
| 사용자 | 기본 사용자 생성, 비밀번호, sudo 권한 | admin / SSH key |
| SSH | 공개키 등록, 부팅 시 자동 인증 가능 | ssh-ed25519 AAAA... |
| 네트워크 | IP, 게이트웨이, DNS 설정 | 정적 IP 또는 DHCP |
| 패키지 | 첫 부팅 시 패키지 설치 | qemu-guest-agent, curl |
| 스크립트 | 커스텀 명령 실행 | 보안 설정, 모니터링 에이전트 |
4.2 Cloud-Init 템플릿 생성
# 1. 클라우드 이미지 다운로드 (Ubuntu 예시)
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img
# 2. VM 생성 (Cloud-Init용 번호를 사용, 예: 9000번대)
qm create 9000 --name ubuntu-2404-ci-template \
--memory 2048 --cores 2 --cpu host \
--net0 virtio,bridge=vmbr0 \
--ostype l26 --machine q35 \
--scsihw virtio-scsi-pci
# 3. 다운로드한 이미지를 디스크로 import
qm importdisk 9000 noble-server-cloudimg-amd64.img local-lvm
# 4. import된 디스크를 SCSI로 연결
qm set 9000 --scsi0 local-lvm:vm-9000-disk-0,iothread=1,discard=on
# 5. Cloud-Init 드라이브 추가
qm set 9000 --ide2 local-lvm:cloudinit
# 6. 부팅 순서 설정
qm set 9000 --boot order=scsi0
# 7. 기본 Cloud-Init 설정
qm set 9000 --ciuser admin \
--sshkeys ~/.ssh/id_ed25519.pub \
--ipconfig0 ip=dhcp
# 8. 템플릿으로 변환
qm template 9000
4.3 Cloud-Init VM 생성 (템플릿에서 클론)
# 클론 + Cloud-Init 설정 변경
qm clone 9000 100 --name web-01 --full
qm set 100 --ipconfig0 ip=10.10.10.101/24,gw=10.10.10.1
qm set 100 --nameserver 8.8.8.8
qm set 100 --searchdomain lab.local
qm start 100
VM이 부팅되면 Cloud-Init이 자동으로 사용자 생성, SSH 키 등록, IP 설정, 호스트명 변경을 수행함. 부팅 후 바로 SSH 접속 가능.
4.4 Cloud-Init 상태 확인
# VM 내부에서 Cloud-Init 완료 확인
cloud-init status
# status: done
# Cloud-Init 로그 확인
cat /var/log/cloud-init-output.log
# Cloud-Init 설정 초기화 (템플릿으로 만들기 전)
sudo cloud-init clean --logs
💡 VMware의 VM Customization Spec과 대응되는 개념임. VMware에서 템플릿 클론 시 호스트명, IP, 도메인 등을 자동 설정하는 것과 동일한 역할. 다만 Cloud-Init은 클라우드 업계 표준이라 VMware에 종속되지 않고 AWS, Azure, OpenStack 등 어디서든 동일하게 동작함.
5. 통합 워크플로우
5.1 Terraform + Cloud-Init + Ansible
4.2 Terraform Output → Ansible Inventory
# terraform/outputs.tf
output "ansible_inventory" {
value = templatefile("${path.module}/templates/inventory.tftpl", {
web_servers = {
for name, vm in proxmox_virtual_environment_vm.web : name => {
ip = vm.initialization[0].ip_config[0].ipv4[0].address
}
}
})
}
# terraform/templates/inventory.tftpl
[webservers]
%{ for name, server in web_servers ~}
${name} ansible_host=${split("/", server.ip)[0]}
%{ endfor ~}
[webservers:vars]
ansible_user=admin
ansible_become=yes
# Terraform output을 Ansible inventory로 저장
terraform output -raw ansible_inventory > ../ansible/inventory/terraform-hosts.ini
# Ansible 실행
cd ../ansible
ansible-playbook -i inventory/terraform-hosts.ini playbooks/setup-webserver.yml
4.3 CI/CD 파이프라인 연동
GitLab CI 예시:
# .gitlab-ci.yml
stages:
- validate
- plan
- apply
- configure
terraform-validate:
stage: validate
image: hashicorp/terraform:latest
script:
- cd terraform
- terraform init
- terraform validate
- terraform fmt -check
terraform-plan:
stage: plan
image: hashicorp/terraform:latest
script:
- cd terraform
- terraform init
- terraform plan -out=tfplan
artifacts:
paths:
- terraform/tfplan
terraform-apply:
stage: apply
image: hashicorp/terraform:latest
script:
- cd terraform
- terraform init
- terraform apply tfplan
- terraform output -raw ansible_inventory > ../ansible/inventory/hosts.ini
when: manual # 수동 승인
artifacts:
paths:
- ansible/inventory/hosts.ini
ansible-configure:
stage: configure
image: cytopia/ansible:latest
script:
- cd ansible
- ansible-playbook -i inventory/hosts.ini playbooks/site.yml
needs:
- terraform-apply
6. 실전 자동화 시나리오
5.1 3-Tier 웹 아키텍처 자동 배포
5.2 스케일 아웃 예시
# terraform.tfvars
web_server_count = 5 # 3 → 5로 변경
# terraform plan 결과:
# + proxmox_virtual_environment_vm.web["web-04"]
# + proxmox_virtual_environment_vm.web["web-05"]
# Plan: 2 to add, 0 to change, 0 to destroy.
terraform apply
# → web-04, web-05 자동 생성
ansible-playbook -i inventory/terraform-hosts.ini playbooks/setup-webserver.yml --limit web-04,web-05
# → 신규 서버에 Nginx 설치/설정
5.3 Infrastructure as Code 디렉토리 구조
infrastructure/
├── terraform/
│ ├── environments/
│ │ ├── dev/
│ │ │ ├── main.tf
│ │ │ ├── variables.tf
│ │ │ └── terraform.tfvars
│ │ └── prod/
│ │ ├── main.tf
│ │ ├── variables.tf
│ │ └── terraform.tfvars
│ ├── modules/
│ │ ├── vm/
│ │ │ ├── main.tf
│ │ │ ├── variables.tf
│ │ │ └── outputs.tf
│ │ └── ct/
│ │ └── ...
│ └── templates/
│ └── inventory.tftpl
├── ansible/
│ ├── inventory/
│ ├── playbooks/
│ ├── roles/
│ └── ansible.cfg
├── packer/ # 템플릿 빌드 자동화 (선택)
│ └── proxmox-ubuntu.pkr.hcl
├── .gitlab-ci.yml
└── README.md
7. Packer (템플릿 자동화)
6.1 왜 Packer인가
Cloud-Init 템플릿도 결국 수동으로 만듦. Packer는 템플릿 생성 자체를 자동화함.
6.2 Packer 설정
# proxmox-ubuntu.pkr.hcl
packer {
required_plugins {
proxmox = {
version = ">= 1.1.0"
source = "github.com/hashicorp/proxmox"
}
}
}
source "proxmox-iso" "ubuntu-2204" {
proxmox_url = "https://192.168.1.100:8006/api2/json"
username = "root@pam!terraform"
token = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
insecure_skip_tls_verify = true
node = "pve1"
vm_id = 910
vm_name = "ubuntu-2204-template"
template_name = "ubuntu-2204-template"
template_description = "Ubuntu 22.04 Cloud Template (Packer built)"
iso_file = "local:iso/ubuntu-22.04.5-live-server-amd64.iso"
unmount_iso = true
os = "l26"
cores = 2
memory = 2048
cpu_type = "x86-64-v2-AES"
scsi_controller = "virtio-scsi-single"
disks {
type = "scsi"
disk_size = "16G"
storage_pool = "local-lvm"
io_thread = true
ssd = true
discard = true
}
network_adapters {
model = "virtio"
bridge = "vmbr0"
firewall = true
}
cloud_init = true
cloud_init_storage_pool = "local-lvm"
qemu_agent = true
# Autoinstall (Ubuntu)
boot_command = [
"c<wait>",
"linux /casper/vmlinuz --- autoinstall ds='nocloud-net;s=http://{{.HTTPIP}}:{{.HTTPPort}}/'",
"<enter><wait>",
"initrd /casper/initrd",
"<enter><wait>",
"boot",
"<enter>"
]
http_directory = "http"
ssh_username = "admin"
ssh_password = "packer"
ssh_timeout = "20m"
}
build {
sources = ["source.proxmox-iso.ubuntu-2204"]
provisioner "shell" {
inline = [
"sudo apt update",
"sudo apt install -y qemu-guest-agent cloud-init curl vim htop",
"sudo systemctl enable qemu-guest-agent",
"sudo cloud-init clean --logs",
"sudo truncate -s 0 /etc/machine-id",
"sudo rm -f /etc/ssh/ssh_host_*",
"sudo apt autoremove -y && sudo apt clean",
"sudo sync"
]
}
}
# 템플릿 빌드
packer init proxmox-ubuntu.pkr.hcl
packer build proxmox-ubuntu.pkr.hcl
8. 트러블슈팅
Q: Terraform apply 시 "500 Internal Server Error"
# API 토큰 권한 확인
pveum user token list root@pam
# 충분한 권한이 있는지 (privsep=0 또는 적절한 ACL)
pveum acl list
# Proxmox 서비스 상태
systemctl status pveproxy
Q: Ansible SSH 접속 실패
# SSH 키 확인
ssh -i ~/.ssh/id_ed25519 admin@192.168.1.101
# Cloud-Init 완료 확인 (VM 내부)
cloud-init status
# Ansible 디버그
ansible -i inventory/hosts.ini web-01 -m ping -vvv
Q: Terraform state와 실제 상태 불일치
# 상태 갱신
terraform refresh
# 특정 리소스 import
terraform import 'proxmox_virtual_environment_vm.web["web-01"]' pve1/qemu/100
# 상태에서 제거 (실제 리소스는 유지)
terraform state rm 'proxmox_virtual_environment_vm.web["web-01"]'
9. VMware 대응표
| VMware | Proxmox + IaC | 비고 |
|---|---|---|
| vSphere API | Proxmox REST API | |
| PowerCLI | pvesh / proxmoxer | |
| Terraform vSphere Provider | Terraform bpg/proxmox | |
| vRealize Automation | Terraform + Ansible | |
| VM Template + Customization | Template + Cloud-Init | |
| Packer vSphere Builder | Packer Proxmox Builder | |
| Ansible VMware modules | Ansible community.general.proxmox |
정리
자동화의 핵심 원칙:
- API First — Proxmox는 모든 기능을 API로 노출함. 활용하지 않을 이유 없음
- Terraform = 인프라, Ansible = 구성 — 각자의 역할이 다름. 같이 써야 완성
- Cloud-Init으로 Day 0 자동화 — 첫 부팅 시 네트워크, SSH, 호스트명 자동 설정
- Packer로 템플릿도 코드화 — 골든 이미지를 수동으로 만들지 않음
- Git + CI/CD로 인프라 변경 추적 — 누가, 언제, 무엇을 바꿨는지 이력 관리
이것으로 Proxmox VE 시리즈 9편이 완성됨. 아키텍처 이해부터 설치, 네트워크, 스토리지, VM, LXC, 클러스터, 백업, 자동화까지 — VMware 대안으로서 Proxmox의 전체 그림을 다뤘음.
🔗 관련 문서
- Proxmox VE Overview — API 아키텍처
- Compute — CPU, Memory, VM & LXC — VM/LXC 워크로드, Cloud-Init 연동
- Proxmox Management Architecture — API 구조, 인증
- Proxmox VE Series Index — 시리즈 목차