Proxmox VE: Cloud-Init + `cicustom` ile Provisioning ve API ile Cloud Image’den Template Üretimi (Uçtan Uca)
Amaç ve Akış
Hedefimiz:
- Cloud image (Ubuntu/Debian/Rocky/Alma vs.) indir
- Proxmox’ta VM oluştur
- Cloud image’i VM’e import et (disk)
- Cloud-Init diskini ekle (cloudinit drive)
- Cloud-Init ayarlarını (user/meta/network)
cicustomile snippets üzerinden ver - VM’i template’e çevir
- Template’ten clone alırken API ile parametre basıp provisioning yap
- Troubleshooting + best practices
Ön Koşullar
- Proxmox VE kurulu
- Bir storage:
- Disk import için:
local-lvm/zfspool/cephvb. - Snippets için: genelde
local(dir storage) daha rahattır
- Disk import için:
- Snippets aktif olmalı (çok önemli)
1.1) Snippets’ı Storage’da Aktifleştirme
Datacenter → Storage → local (veya kullandığın dir storage)
Content içinde Snippets işaretli olmalı.
CLI ile kontrol (örnek):
cat /etc/pve/storage.cfg
local altında şunu görmek istersin:
dir: local
path /var/lib/vz
content iso,vztmpl,backup,snippets
Snippets dosya yolu tipik olarak:
/var/lib/vz/snippets/
Cloud Image İndirme
Örnek: Ubuntu 22.04 cloud image:
cd /var/lib/vz/template/iso
wget -O ubuntu-22.04-server-cloudimg-amd64.img \
https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
Not: “iso” klasörüne koymak şart değil. Ama düzen için iyi.
Cloud Image’den Template Üretme (CLI ile)
Aşağıdaki örnek “golden template” üretir. Sonra her VM clone ile çıkar.
VM Oluştur
Örnek VMID: 9001
VMID=9001
qm create $VMID \
--name ubuntu-2204-cloudinit \
--memory 2048 \
--cores 2 \
--cpu host \
--net0 virtio,bridge=vmbr0 \
--scsihw virtio-scsi-pci
Cloud Image’i Import Et
Storage örneği: local-lvm
qm importdisk $VMID /var/lib/vz/template/iso/ubuntu-22.04-server-cloudimg-amd64.img local-lvm
Import sonrası disk ismi genelde vm-9001-disk-0 gibi olur.
Import Edilen Diski VM’e Bağla
qm set $VMID --scsi0 local-lvm:vm-$VMID-disk-0
qm set $VMID --boot order=scsi0
Cloud-Init Diski Ekle
qm set $VMID --ide2 local-lvm:cloudinit
Proxmox cloud-init için çoğunlukla
ide2kullanılır. (SCSI de olur amaide2yaygın.)
Seri Konsol + QEMU Guest Agent (Önerilir)
qm set $VMID --serial0 socket --vga serial0
qm set $VMID --agent enabled=1
QEMU agent’ın çalışması için guest OS içinde paket de gerekebilir (Ubuntu:
qemu-guest-agent).
Template’e Çevirmeden Önce Genel Ayarlar
Proxmox’un native Cloud-Init alanlarını da kullanabilirsin:
qm set $VMID --ciuser burak --sshkeys /root/.ssh/authorized_keys
qm set $VMID --ipconfig0 ip=dhcp
Ama biz asıl gücü cicustom ile alacağız.
Template’e Çevir
qm template $VMID
Artık 9001 bir template.
cicustom ile Cloud-Init (Snippets) Kullanımı
Proxmox Cloud-Init üç dosya üzerinden yönetilebilir:
user-data→ kullanıcı, paket, runcmd, write_files…meta-data→ instance-id, hostname…network-data→ netplan/network config
Proxmox, cicustom ile bu dosyaları snippet olarak göstermenize izin verir:
Format:
cicustom: user=<storage>:snippets/<user.yml>,meta=<storage>:snippets/<meta.yml>,network=<storage>:snippets/<net.yml>
Örnek:
cicustom: user=local:snippets/user-data.yml,meta=local:snippets/meta-data.yml,network=local:snippets/network-data.yml
Snippet Dosyalarını Oluşturma
Klasör:
mkdir -p /var/lib/vz/snippets
user-data.yml (örnek)
/var/lib/vz/snippets/ubuntu-user-data.yml
#cloud-config
hostname: "{{HOSTNAME}}"
manage_etc_hosts: true
users:
- name: burak
groups: [sudo]
shell: /bin/bash
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
ssh_authorized_keys:
- "{{SSH_PUBLIC_KEY}}"
package_update: true
package_upgrade: true
packages:
- qemu-guest-agent
- curl
- git
write_files:
- path: /etc/motd
content: |
Provisioned by Proxmox Cloud-Init + cicustom
runcmd:
- systemctl enable --now qemu-guest-agent
- echo "cloud-init done" > /var/log/cloud-init-provisioning.ok
Buradaki
{{...}}placeholder. Proxmox Cloud-Init doğrudan template engine değildir. Değişken basmak istiyorsan 2 yol var:
- (A) API ile clone sonrası
cicustom’u farklı snippet dosyasına pointing yap (her VM için ayrı snippet)- (B) Tek snippet + Proxmox’un
--ciuser,--sshkeys,--ipconfig0gibi alanlarıyla karışık kullan
meta-data.yml (örnek)
/var/lib/vz/snippets/ubuntu-meta-data.yml
instance-id: iid-local01
local-hostname: ubuntu-ci
network-data.yml (DHCP örnek)
/var/lib/vz/snippets/ubuntu-network-data.yml
version: 2
ethernets:
ens18:
dhcp4: true
Proxmox’ta virtio NIC genelde guest içinde
ens18olur (distroya göre değişebilir).
Template Üzerinde cicustom Tanımlama
Template VMID: 9001
qm set 9001 --cicustom "user=local:snippets/ubuntu-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"
Cloud-init diskini yeniden üretmek için bazen “regenerate” gerekir:
qm cloudinit update 9001
Bazı sürümlerde bu komut yoksa sorun değil; clone sonrası zaten cloud-init drive yeniden üretilir.
Template’ten VM Clone Alma ve Provisioning
Basit Clone (CLI)
qm clone 9001 7878 --name testcloud --full 1 --target pve
Sonra cloud-init parametreleri:
qm set 7878 --ipconfig0 ip=dhcp
qm set 7878 --ciuser burak
qm set 7878 --sshkeys /root/.ssh/authorized_keys
qm start 7878
“Her VM’e Farklı Cloud-Init” (Snippets ile)
Her VM için ayrı snippet üret:
cat > /var/lib/vz/snippets/vm-7878-user-data.yml <<'YAML'
#cloud-config
hostname: vm-7878
users:
- name: burak
groups: [sudo]
shell: /bin/bash
sudo: ["ALL=(ALL) NOPASSWD:ALL"]
ssh_authorized_keys:
- "ssh-ed25519 AAAA... burak@mbp"
runcmd:
- echo "hello from 7878" > /root/hello.txt
YAML
Sonra clone’da cicustom pointing değiştir:
qm set 7878 --cicustom "user=local:snippets/vm-7878-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"
qm start 7878
Proxmox API ile Otomasyon (Template Üretimi + Clone + Config)
Aşağıdaki bölümler:
- API Token ile auth
- Cloud image’den template üretimi (API + bazı işlerde SSH/CLI gerekebilir)
- Template clone + config + start
Auth: API Token Header
Genel format:
Authorization: PVEAPIToken=<user>@<realm>!<tokenid>=<secret>
Örnek (placeholder):
-H "Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>"
zsh’de “event not found” (!) sorunu
zsh ! gördüğünde history expansion yapar. Çözümler:
- Header’ı tek tırnak içine al:
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>'
- Ya da komut öncesi history expansion kapat:
set +H
API ile Template’ten Clone (En Yaygın Workflow)
Clone Endpoint
curl -k -X POST "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/<TEMPLATE_VMID>/clone" \
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
-d "newid=7878" \
-d "name=testcloud" \
-d "full=1" \
-d "target=<NODE>"
Bu endpoint bir
UPIDdöner. Task durumunu poll edebilirsin:/nodes/<NODE>/tasks/<UPID>/status
Clone Sonrası Config Güncelleme
Clone bittiğinde:
curl -k -X PUT "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/7878/config" \
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
-d "ciuser=burak" \
-d "ipconfig0=ip=dhcp" \
--data-urlencode "sshkeys=$(cat /root/.ssh/authorized_keys)"
cicustom set etmek istersen:
curl -k -X PUT "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/7878/config" \
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
--data-urlencode "cicustom=user=local:snippets/vm-7878-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"
VM Start
curl -k -X POST "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/7878/status/start" \
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>'
API ile “Cloud Image’den Template Üretimi” (Gerçekçi Yaklaşım)
Burada kritik nokta: Disk import (qm importdisk) gibi bazı işler Proxmox’ta en pratik CLI ile yapılır. API ile “tamamen sıfırdan” yapmak mümkün ama disk import aşaması çoğu zaman:
- ya node üzerinde komut çalıştırmayı (SSH)
- ya da storage’a uygun formatta önceden yüklemeyi gerektirir.
Bu yüzden production otomasyonda en temiz yaklaşım:
- API ile VM “create”
- Node’da (SSH/Ansible)
qm importdisk+qm set(disk attach, ide2 cloudinit) - API ile kalan config (cicustom, ciuser, ipconfig, sshkeys)
qm template
API: VM Create
curl -k -X POST "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu" \
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
-d "vmid=9001" \
-d "name=ubuntu-2204-cloudinit" \
-d "memory=2048" \
-d "cores=2" \
-d "cpu=host" \
-d "net0=virtio,bridge=vmbr0" \
-d "scsihw=virtio-scsi-pci" \
-d "serial0=socket" \
-d "vga=serial0" \
-d "agent=enabled=1"
8.2) Node Üzerinde Import + Attach (SSH/Ansible)
Node’da:
qm importdisk 9001 /var/lib/vz/template/iso/ubuntu-22.04-server-cloudimg-amd64.img local-lvm
qm set 9001 --scsi0 local-lvm:vm-9001-disk-0
qm set 9001 --ide2 local-lvm:cloudinit
qm set 9001 --boot order=scsi0
API: cicustom Set
curl -k -X PUT "https://<PVE_HOST>:8006/api2/json/nodes/<NODE>/qemu/9001/config" \
-H 'Authorization: PVEAPIToken=root@pam!apiroot=<TOKEN_SECRET>' \
--data-urlencode "cicustom=user=local:snippets/ubuntu-user-data.yml,meta=local:snippets/ubuntu-meta-data.yml,network=local:snippets/ubuntu-network-data.yml"
Template’e Çevirme
Template’e çevirme genelde CLI ile:
qm template 9001
API’de template flag’i set etmek mümkün olsa da pratikte
qm templateen stabil yoldur.
Cloud-Init İçerikleri: Örnek Şablonlar
Statik IP (Netplan) Örneği
/var/lib/vz/snippets/net-static.yml
version: 2
ethernets:
ens18:
dhcp4: false
addresses:
- 10.0.0.50/24
gateway4: 10.0.0.1
nameservers:
addresses: [1.1.1.1, 8.8.8.8]
Docker Kurulumlu user-data Örneği
#cloud-config
packages:
- ca-certificates
- curl
- gnupg
runcmd:
- install -m 0755 -d /etc/apt/keyrings
- curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
- chmod a+r /etc/apt/keyrings/docker.gpg
- bash -lc 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" > /etc/apt/sources.list.d/docker.list'
- apt-get update
- apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
- systemctl enable --now docker
İnternete erişim yoksa bu paket kurulumları fail eder. Offline repo gerekiyorsa farklı yaklaşım gerekir.
Sık Karşılaşılan Sorunlar ve Çözümler
ide2 ... cloudinit iki kez görünüyor
Bazen config update’leri yanlışlıkla iki kez basınca olur. Çözüm:
qm config <vmid>ile kontrol et- Fazlalığı kaldır (örnek: ide2’yi resetleyip tekrar set):
qm set 7878 --delete ide2
qm set 7878 --ide2 local-lvm:cloudinit
Cloud-Init çalışmıyor / değişiklikler yansımıyor
- VM içinde kontrol:
cloud-init status --long
cloud-init query
tail -n 200 /var/log/cloud-init.log
tail -n 200 /var/log/cloud-init-output.log
- Gerekirse reset:
cloud-init clean --logs
reboot
Hostname değişmiyor
Bazı distro’larda NetworkManager/hostnamectl davranışı farklı. user-data içinde hostname + manage_etc_hosts: true iyi pratik.
NIC adı ens18 değil
ip linkile doğru interface adını bul- network-data’da onu kullan
API ile sshkeys basarken bozuluyor
--data-urlencode kullan:
--data-urlencode "sshkeys=$(cat ~/.ssh/id_ed25519.pub)"
Best Practices (Production)
- Template içinde mümkün olduğunca “genel” bırak:
- Paket update/upgrade template boot’unda değil, VM provisioning’de yapılsın (isteğe bağlı)
- Snippets dosyalarını:
snippets/templates/ubuntu-2204/gibi klasörleyerek düzenle
- Her VM için farklı user-data istiyorsan:
- VMID bazlı snippet üret (ör.
vm-<id>-user.yml) - Sonra API ile
cicustompointing değiştir
- VMID bazlı snippet üret (ör.
- Secret’ları user-data içine plaintext gömmek yerine:
- Vault/CyberArk/SSM benzeri sistemden runtime çek
- veya cloud-init içinde “one-time token” yaklaşımı kullan
Minimal “Tam Otomasyon” Pseudocode (Akıl Haritası)
- (Node) cloud image indir / doğrula
- API: VM create (9001)
- SSH:
qm importdisk+ disk attach + cloudinit drive - API:
cicustomset - SSH:
qm template 9001 - API: clone (newid)
- API: config set (ciuser, ipconfig0, sshkeys, cicustom override)
- API: start
- (Ops) VM içinde
cloud-init statushealth-check