ProxmoxのVM作成をCloud-initにやらせてみた

# はじめに
ProxmoxのVMでUbuntuをインストールを作るとき、毎回何回もポチポチしていてめんどくさくないですか...？

Google先生に聞いてみると、Terraformやansibleなどがあるようですが、そこまで使わなくてもCloud-initとシェルスクリプトだけでできそうだったので、Cloud-initとそれを使ってProxmoxにVMを作るシェルスクリプトを作成してみました。

**お急ぎの方は[こちら](#コード全文)に全文のコードを貼ってあります**

# Cloud-initに自動でしてほしいこと
- OSのインストール&初期設定
    - Ubuntuのインストール
    - ユーザの設定
    - SSHの設定
    - 初回のapt update & apt upgrade
- よく使うソフトウェアのインストールと設定
    - net-tools
    - VScode
    - Tailscale (必要に応じて)
    - Zabbix agent (必要に応じて)


# いろいろ入れておく
```
apt install cloud-init libguestfs-tools
```

# ベースとなるCloud-init
```config:11_template_base.cfg
#cloud-config

package_upgrade: true
packages:  # aptでインストールしたいソフトウェア一覧
    - qemu-guest-agent
    - wget
    - curl
    - nano
    - traceroute
    - net-tools
    - sl

#user setup
user: #USER_NAME
password: #PASSWORD
ssh_pwauth: False
ssh_authorized_keys:
  - ssh-rsa #SSH_KEY

timezone: Asia/Tokyo
locale: ja_JP.utf8
keyboard:
  layout: jp

runcmd:
#vscode install
  - curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
  - install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/
  - sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
  - apt update
  - apt install -y code

```
これで、Cloud-initを設定すると毎回使用するソフトウェアのインストールなどは最低限のことは自動でしてくれます。
（ネットワークの設定は後でします）



# Cloud-initとそれを使ってProxmoxにVMを作るシェルスクリプト(基本)
```bash
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img
```
まず、ubuntuのイメージをダウンロードしてきます。
```bash: make_vm.sh
qm create ${vmid} --core ${PVE_CORE} --memory ${PVE_MEMORY} --net0 virtio,bridge=vmbr0
qm importdisk ${vmid}  ubuntu-22.04.img local-lvm
qm set ${vmid} --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-${vmid}-disk-0
qm resize ${vmid} scsi0 +${PVE_VOLUME}G
qm set ${vmid} --ide2 local-lvm:cloudinit
qm set ${vmid} --boot order=scsi0
qm set ${vmid} --serial0 socket --vga serial0
qm set ${vmid} --agent enabled=1
qm set ${vmid} --ipconfig0 ip=dhcp
qm template ${vmid}
```
基本的にはこれで十分できます。


しかし、毎回vmidを指定して...などなど、めんどくさいのでさらに簡単にしましょう。










# Cloud-initとそれを使ってProxmoxにVMを作るシェルスクリプト(改良)
最後にコードをすべて貼ってあるのでお急ぎの方は[そちら](#コード全文)をご使用ください。
先ほどのコードに少しずつ機能を足して便利にしていきましょう。




---
### ネットワークの設定
サクッと使うだけのVMならばDHCPで十分なので、基本は追加したネットデバイスに
`qm set ${vmid} --ipconfig0 ip=dhcp` といった感じで設定しておけば問題ないです。

**固定IPをしたい場合**
- `qm set ${vmid}`のところで、`--ipconfig0 "gw=○○.○○.○○.○○,ip=○○.○○.○○.○○/24" --nameserver ○○.○○.○○.○○`を追加する
- GUIから設定(省略）
- Cloud-initで`/etc/netplan` にファイルを書く(省略)
など、いろいろあるのでお好きな方法でしてください

**ネットデバイスを複数使いたい場合** (SDNなどの使用時)
`qm create ${vmid} --core ${PVE_CORE} --memory ${PVE_MEMORY} --net0 virtio,bridge=vmbr0 --net1 virtio,bridge=vnet01` 
`qm set ${vmid} --ipconfig1 ip=dhcp`
といったように順番に足していけばいいです。(vnet01はSDNのVnet名)

---
### まず、VMID考えるのめんどくさい問題から

```bash: make_vm.sh
vmid=100
for i in {100..900}; do
    if ! [[ " ${vmids[@]} " =~ " $i " ]]; then
        vmid=$i
        break
    fi
done
```
`qm list`でVMのリストが得られるのでVMIDだけ取り出して使っていないVMIDを探します。
（この記事では、VMは100番以降、テンプレートは9000番以降のVMIDを取るように設定します）

:::note warn
複数ノードの場合は、普通に指定してください。
```:引数から得るパターンの例
vmid=$1
if [ -z "${vmid}" ]; then
    echo "VMID is required."
    exit 1
fi
~~~
if [[ " ${vmids[@]} " =~ " $vmid " ]]; then
    echo "エラー: 指定されたVMID ($vmid) は既に使用されています。"
    exit 1
fi
```

:::



---
### Tailscaleをインストールしてみる
```bash: make_vm.sh
tailscale_key="tskey-auth-○○○" #TailscaleのAuth keyを指定してください。
echo -e '  - ['sh', '-c', "curl -fsSL https://tailscale.com/install.sh | sh"]' >> ${cloud_init_path}
echo -e '  - ['sh', '-c', "echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf && echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf && sudo sysctl -p /etc/sysctl.d/99-tailscale.conf" ]' >>${cloud_init_path}
echo -e '  - ['tailscale', 'set', '--ssh']' >> ${cloud_init_path}
echo -e '  - ['tailscale', 'set', '--auto-update']' >> ${cloud_init_path}
echo -e "  - ['tailscale', 'up', '--authkey=${tailscale_key}']" >> ${cloud_init_path}
```
TailscaleをインストールするようにCloud-initにを加筆します。


---
### Zabbix agentをインストールしてみる
```bash: make_vm.sh
zabbix_server_IP= #Zabbix_serverのIPを指定してください。
echo -e '  - wget https://repo.zabbix.com/zabbix/6.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_6.4-1+ubuntu22.04_all.deb ' >> ${cloud_init_path}
echo -e '  - dpkg -i zabbix-release_6.4-1+ubuntu22.04_all.deb' >> ${cloud_init_path}
echo -e '  - apt update' >> ${cloud_init_path}
echo -e '  - apt install zabbix-agent2 zabbix-agent2-plugin-*' >> ${cloud_init_path}
echo -e "  - sed -i s/Server=127.0.0.1/Server=${zabbix_server_IP}/g /etc/zabbix/zabbix_agent2.conf" >> ${cloud_init_path}
echo -e "  - sed -i s/ServerActive=127.0.0.1/ServerActive=${zabbix_server_IP}/g /etc/zabbix/zabbix_agent2.conf" >> ${cloud_init_path}
echo -e '  - systemctl restart zabbix-agent2' >> ${cloud_init_path}
echo -e '  - systemctl enable zabbix-agent2' >> ${cloud_init_path}
```
ZabbixをインストールするようにCloud-initにを加筆します。
そして、`/etc/zabbix/zabbix_agent2.conf`に最低限の設定を記入します。

---
### 毎回環境変数設定するめんどくさい問題
```bash :make_vm.sh
if [ -z "${PVE_CORE}" ]; then
    PVE_CORE=4       # デフォルト値を設定
fi
if [ -z "${PVE_MEMORY}" ]; then
    PVE_MEMORY=4096  # デフォルト値を設定
fi
if [ -z "${PVE_VOLUME}" ]; then
    PVE_VOLUME=20    # デフォルト値を設定
fi
```
環境変数の有無を確認して、なければデフォルト値を設定する。


---
### 引数とってみる

おそらく、毎回テンプレートを作成したいわけでもないですし、毎回Tailscaleをインストールするわけでもないとおもうので、そこら辺を各種指定できるような引数をシェルスクリプトがとるようにしてみましょう。
```bash: make_vm.sh
while [ -n "${1:-}" ]; do
    case "$1" in
        -h|--help) usage; exit 0;;
        -e|--echo) echo "hello world"; exit 0;;
        -t|--tailscale) echo "install tailscale"; tailscale=1;;
        -z|--zabbix) echo "install zabbix"; zabbix=1;;
        -temp) echo "convert to template"; temp=1;;
        -*) echo -e 'エラー: 無効なオプションです。「-h」を使用してください。'; exit 1;;
        "") break;;
        *) test="$1";;
    esac
    shift
done
```
で、この引数の情報をもとに条件分岐などを追加したのが、以下のコード全文です。


### コード全文

<details><summary>Cloud-init</summary>

```config:11_template_base.cfg
#cloud-config

package_upgrade: true
packages:  # aptでインストールしたいソフトウェア一覧
    - qemu-guest-agent
    - wget
    - curl
    - nano
    - traceroute
    - net-tools
    - sl

#user setup
user: #USER_NAME
password: #PASSWORD
ssh_pwauth: False
ssh_authorized_keys:
  - ssh-rsa #SSH_KEY

timezone: Asia/Tokyo
locale: ja_JP.utf8
keyboard:
    layout: "jp"

runcmd:
#vscode install
  - curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
  - install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/
  - sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main" > /etc/apt/sources.list.d/vscode.list'
  - apt update
  - apt install -y code

```

</details>

<details><summary>シェルスクリプト</summary>

```bash: make_vm.sh
#!/usr/bin/env bash

function usage() {
cat <<EOF
Usage: command [<options>...] 
(Options must be specified one at a time.)
-------------
Options:
  -h|--help		 show help
  -t|--tailscale install tailscale
  -z|--zabbix    install zabbix
  -temp          convert to template 

EOF
}

while [ -n "${1:-}" ]; do
    case "$1" in
        -h|--help) usage; exit 0;;
        -e|--echo) echo "hello world"; exit 0;;
        -t|--tailscale) echo "install tailscale"; tailscale=1;;
        -z|--zabbix) echo "install zabbix"; zabbix=1;;
        -temp) echo "convert to template"; temp=1;;
        -*) echo -e 'エラー: 無効なオプションです。「-h」を使用してください。'; exit 1;;
        "") break;;
        *) test="$1";;
    esac
    shift
done

# 既存のVMIDのリストから空いているVMIDを取得
output=$(qm list)
vmids=($(echo "$output" | tail -n +2 | awk '{print $1}'))
if [ "$temp" = 1 ]; then
    vmid=9000
    for i in {9000..10000}; do
        if ! [[ " ${vmids[@]} " =~ " $i " ]]; then
            vmid=$i
            break
        fi
    done
else
    vmid=100
    for i in {100..900}; do
        if ! [[ " ${vmids[@]} " =~ " $i " ]]; then
            vmid=$i
            break
        fi
    done
fi
echo "VMID: $vmid"


# Cloud-init の設定ファイルを編集
cloud_init_base_path="11_template_base.cfg"
cloud_init_path="11_template.cfg"
cp -av ${cloud_init_base_path} ${cloud_init_path}
echo -e ' ' >> ${cloud_init_path}

# Tailscale のインストール
if [ "$tailscale" = 1 ]; then
    tailscale_key="tskey-auth-○○○" #TailscaleのAuth keyを指定してください。
    echo -e '  - ['sh', '-c', "curl -fsSL https://tailscale.com/install.sh | sh"]' >> ${cloud_init_path}
    echo -e '  - ['sh', '-c', "echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf && echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.d/99-tailscale.conf && sudo sysctl -p /etc/sysctl.d/99-tailscale.conf" ]' >>${cloud_init_path}
    echo -e '  - ['tailscale', 'set', '--ssh']' >> ${cloud_init_path}
    echo -e '  - ['tailscale', 'set', '--auto-update']' >> ${cloud_init_path}
    echo -e "  - ['tailscale', 'up', '--authkey=${tailscale_key}']" >> ${cloud_init_path}
fi

# Zabbix のインストール
if [ "$zabbix" = 1 ]; then
    zabbix_server_IP= #Zabbix_serverのIPを指定してください。
    echo -e '  - wget https://repo.zabbix.com/zabbix/6.4/ubuntu/pool/main/z/zabbix-release/zabbix-release_6.4-1+ubuntu22.04_all.deb ' >> ${cloud_init_path}
    echo -e '  - dpkg -i zabbix-release_6.4-1+ubuntu22.04_all.deb' >> ${cloud_init_path}
    echo -e '  - apt update' >> ${cloud_init_path}
    echo -e '  - apt install zabbix-agent2 zabbix-agent2-plugin-*' >> ${cloud_init_path}
    echo -e "  - sed -i s/Server=127.0.0.1/Server=${zabbix_server_IP}/g /etc/zabbix/zabbix_agent2.conf" >> ${cloud_init_path}
    echo -e "  - sed -i s/ServerActive=127.0.0.1/ServerActive=${zabbix_server_IP}/g /etc/zabbix/zabbix_agent2.conf" >> ${cloud_init_path}
    echo -e '  - systemctl restart zabbix-agent2' >> ${cloud_init_path}
    echo -e '  - systemctl enable zabbix-agent2' >> ${cloud_init_path}
    echo -e '  - systemctl stop zabbix-agent2' >> ${cloud_init_path}
fi    

# イメージファイルのコピー
cp -av jammy-server-cloudimg-amd64.img ubuntu-22.04.img
virt-copy-in -a ubuntu-22.04.img  11_template.cfg /etc/cloud/cloud.cfg.d
# virt-ls -a ubuntu-22.04.img 11_template.cfg /etc/cloud/cloud.cfg.d/

# VM の作成
if [ -z "${PVE_CORE}" ]; then
    PVE_CORE=4       # デフォルト値を設定
fi
if [ -z "${PVE_MEMORY}" ]; then
    PVE_MEMORY=4096  # デフォルト値を設定
fi
if [ -z "${PVE_VOLUME}" ]; then
    PVE_VOLUME=20    # デフォルト値を設定
fi
qm create ${vmid} --core ${PVE_CORE} --memory ${PVE_MEMORY} --net0 virtio,bridge=vmbr0
qm importdisk ${vmid}  ubuntu-22.04.img local-lvm
qm set ${vmid} --scsihw virtio-scsi-pci --scsi0 local-lvm:vm-${vmid}-disk-0
qm resize ${vmid} scsi0 +${PVE_VOLUME}G
qm set ${vmid} --ide2 local-lvm:cloudinit
qm set ${vmid} --boot order=scsi0
qm set ${vmid} --serial0 socket --vga serial0
qm set ${vmid} --agent enabled=1
qm set ${vmid} --ipconfig0 ip=dhcp # DHCP
# qm set ${vmid} --ipconfig0 "gw=○○.○○.○○.○○,ip=○○.○○.○○.○○/24" --nameserver ○○.○○.○○.○○  #固定IP

# テンプレートに変換
if [ "$temp" = 1 ]; then
    qm template ${vmid}
fi

# お行儀よく後片付け
rm 11_template.cfg
rm ubuntu-22.04.img
```

</details>



# まとめ
いかがだったでしょうか。

意外とTerraformやansibleなどを使わなくてもいろいろできたんじゃないかと思います。（Terraformやansibleを使っていないのでわかりませんが...）

数百台など作るのであれば話は違いますが、数台程度ならこれで十分じゃないかと思います。

ぜひ参考にしてみてください。
最後までご覧いただきありがとうございました。


（コードは使っていただいて結構ですが、転載などはしないでください。）

# 参考にさせていただいたサイト
- [ProxmoxVE Cloud-Init_Support](https://pve.proxmox.com/wiki/Cloud-Init_Support)
- [お家で始める仮想化環境 Proxmox VE Cloud-init編](https://blog.nishi.network/2020/11/05/proxmox-part3/)
- [とほほのBash入門](https://www.tohoho-web.com/ex/shell.html)
- [Proxmox VEでIPAM+cloud-initを用いてIP管理](https://qiita.com/segre5458/items/38195892d0b1775c2385#%E5%8F%82%E8%80%83)
- [「--help」が使えるシェルスクリプトを作る方法](https://qiita.com/yusu79/items/f3ee2898ac4449141790)