Ansible + PXE + Kickstart で ESXi Server 上にVMを作成し、OSをインストールする


無償版 ESXi Server 上にVMを作成し、OSをインストールするまでを自動化できないか試行錯誤した結果をまとめます。

目標

  • 無償版 ESXi Server に Ansible から指定する構成で VM を作成できるようにする
  • VMを起動すると PXE boot、Kickstart によりOSを自動でインストールできるようにする

構成

  • ESXi Server (192.168.33.50)
    • VMware vSphere Hypervisor 7.0
  • Ansible client (192.168.33.11)
    • CentOS8
    • Ansible 2.8 + vsphere_guest モジュール
  • PXE / Kickstart Server (192.168.33.10)
    • CentOS8
    • tftp-server
    • dhcp
    • httpd
    • syslinux

Ansible client の構成

無償版ESXi ServerをAnsibleから操作する場合、Ansible2.9でobsoleteとなった vsphere_guest を選択するしかないようです。(公式モジュールの vmware_guest を使用したら以下のエラーが出て使えませんでした。)

Failed to create virtual machine due to product versioning restrictions: Current license or ESXi version prohibits execution of the requested operation.

※ vsphere_guest と vmware_guest の比較については下記記事を参照してください:

仕方がないので、virtualenv で Ansible2.8 環境をつくることにします。

PXE / Kickstart Server の構成

PXE, Kickstart によるOSインストールの流れは以下の通りです:

  1. VMの電源を入れる
  2. VMがdhcp serverに通信する
  3. dhcp serverがIPアドレスとPXE ServerのIPアドレスを返す
  4. tftp server に PXE boot file を取りに行く
  5. http server に Kickstart file, OS イメージを取りに行く
  6. OSの自動インストール

4.の処理では、まず、MACアドレスに対応する名前(16進数が小文字で表記されハイフンで区切られたもの)のブート構成ファイルを検索する仕様になっています。たとえば、MACアドレスが88:99:AA:BB:CC:DDの場合は、01-88-99-aa-bb-cc-dd (01-{MAC-ADDRESS}) というファイルを検索します。見つからない場合は default という名前のファイルを検索します。

したがって、ESXi Server にVMを作成するときは、MACアドレスを手動で設定し、そのMACアドレスに対応した PXE boot file を配置するとよさそうです。

資材

以下のISOイメージを用意します。

Ansible client のインストール

pysphere は Python2系のようなので、Python2をインストールします。Ansibleは2.8系の最新版である 2.8.12 をインストールします。

sudo dnf -y install python2
sudo pip2 install virtualenv
python2 -m virtualenv ansible-py2
source ansible-py2/bin/activate
pip install ansible==2.8.12
pip install pysphere
ansible -i localhost, all -m ping -c local

最後の ansible コマンドで pong が返れば成功です。

PXE / Kickstart Server のインストール

dhcp server, tftp server, http server をインストールし、各種資材を配置していきます。

dhcp server のインストール

以下の設計でインストール・設定します。

パラメータ
subnet 192.168.33.0/24
range 192.168.33.100 - 192.168.33.199
pxe server 192.168.33.10
boot BIOS
sudo dnf -y install dhcp-server
sudo vi /etc/dhcp/dhcpd.conf
sudo systemctl enable --now dhcpd
sudo firewall-cmd --add-service=dhcp --permanent 
sudo firewall-cmd --reload 
/etc/dhcp/dhcpd.conf
option space pxelinux;
option pxelinux.magic code 208 = string;
option pxelinux.configfile code 209 = text;
option pxelinux.pathprefix code 210 = text;
option pxelinux.reboottime code 211 = unsigned integer 32;
option architecture-type code 93 = unsigned integer 16;

authoritative;
default-lease-time 3600;
max-lease-time 43200;

subnet 192.168.33.0 netmask 255.255.255.0 {
        range 192.168.33.100 192.168.33.199;
        option subnet-mask 255.255.255.0;
        option broadcast-address 192.168.0.255;

        class "pxeclients" {
                match if substring (option vendor-class-identifier, 0, 9) = "PXEClient";
                next-server 192.168.33.10; # pxe server
                filename "pxelinux.0";
        }
}

tftp server のインストール

sudo dnf -y install tftp-server
sudo systemctl enable --now tftp
sudo firewall-cmd --add-service=tftp --permanent
sudo firewall-cmd --reload 

http server のインストール

Kickstart file は /var/www/html/pub/kickstart に配置することとします。

sudo dnf -y install httpd 
sudo systemctl enable --now httpd 
sudo firewall-cmd --add-service=http --permanent 
sudo firewall-cmd --reload 

tftp server および http server に PXE boot file, OSイメージの資材を配置

tftp server のコンテンツは /var/lib/tftpboot/ に、CentOS のメディアは /var/www/html/pub/centos8/ に配置します。
資材は syslinux パッケージ、CentOS8 のメディアから取得します。

sudo dnf -y install syslinux
sudo cp /usr/share/syslinux/pxelinux.0 /var/lib/tftpboot/ 
sudo cp /usr/share/syslinux/{menu.c32,vesamenu.c32,ldlinux.c32,libcom32.c32,libutil.c32} /var/lib/tftpboot/ 
sudo mkdir -p /var/www/html/pub/centos8
sudo mount -t iso9660 -o loop,ro ~/CentOS-8.2.2004-x86_64-dvd1.iso /var/www/html/pub/centos8

PXE boot file は後程 Ansible で作成します。

Kickstart file の配置

Kickstart file の書きかたについては、Red Hat のマニュアルが詳しいです。詳細はここでは割愛します。

Ansible playbook の作成

環境が用意できたので、いよいよ Ansible playbook を書きます。
処理したい内容は以下の通りです:

  1. PXE boot file を MACアドレスごとに生成する
  2. Kickstart file を VMごとに生成する
  3. ESXi にVMを作成し、起動する

inventory

以下のようにVMごとにホスト名とMACアドレス、NWの設定をセットにして、リスト形式で定義します。

inventory/esxi/group_vars/all.yml
---
## createvms (ESXi Server)
esx_hostname: esxi
esx_ipaddr: 192.168.11.50
esx_user: root
esx_password: <<PASSWORD>>
esx_datastore_name: datastore1

## create VMs
vms:
  - hostname: server-a
    notes: server-a
    macaddr: 52:54:00:98:09:69
    disksize: 10
    nic1_network: "2032_ProviApp"
    nic2_network: "VM Network"
    memory: 2048
    cpus: 1
    osid: centos8_64Guest
    network:
      - "--bootproto=dhcp --device=ens192 --ipv6=auto"
      - "--bootproto=static --device=ens224 --ip=192.168.11.20 --netmask=255.255.255.0 --gateway=192.168.11.1 --ipv6=auto --activate"
  - hostname: server-b
    notes: server-b
    macaddr: 52:54:00:98:09:70
    disksize: 10
    nic1_network: "2032_ProviApp"
    nic2_network: "VM Network"
    memory: 2048
    cpus: 1
    osid: centos8_64Guest
    network:
      - "--bootproto=dhcp --device=enp0s3 --ipv6=auto"
      - "--bootproto=static --device=enp0s8 --ip=192.168.11.21 --netmask=255.255.255.0 --gateway=192.168.11.1 --ipv6=auto --activate"

inventory/esxi/group_vars/pxeserver.yml
---
## pxe boot files
pxe_basedir: "/var/lib/tftpboot"
pxe_ks_basedir: "{{ pxe_basedir }}/pxelinux.cfg"
pxe_img_basedir: "{{ pxe_basedir }}/images/CentOS8"
pxe_label: "Kickstart Install CentOS8 x64 with local Repository"
pxe_kernel: "images/CentOS8/vmlinuz"
pxe_initrd: "images/CentOS8/initrd.img"
pxe_server: "192.168.10.1"

## Kickstart files
ks_basedir: "/var/www/html/pub/kickstart"
ks_server: "{{ dhcpd_next_server }}"

PXE boot file を MACアドレスごとに生成する

Ansible playbook は以下のように書きます。

pxeserver.yml
---
- hosts: pxeserver
  become: yes
  gather_facts: no
  tasks:
    - name: create PXE boot files
      template:
        src: pxelinux.cfg.j2
        dest: "{{ pxe_ks_basedir }}/01-{{ item.macaddr | regex_replace(':','-') }}"
      with_items:
        - "{{ vms }}"
pxelinux.cfg.j2
default 1
PROMPT 0
TIMEOUT 30
ONTIMEOUT local

menu title ######## PXE Boot Menu ##########

label 1
  menu label ^1) {{ pxe_label }}
  kernel {{ pxe_kernel }}
  append initrd={{ pxe_initrd }} inst.ks=http://{{ pxe_server }}/pub/kickstart/{{ item.hostname }}.cfg

生成するファイルは「01-{MACアドレス}」という命名規則ですが、「-」区切りなので、インベントリに「:」区切りで定義しても置換するようにしています。

Kickstart file を VMごとに生成する

Ansible playbook は以下のように書きます。(先ほどの pxeserver.yml に続けて書きます)

pxeserver.yml
    - name: create Kickstart directory
      file:
        path: "{{ ks_basedir }}"
        state: directory
        owner: "root"
        group: "root"
        mode: "0755"
        recurse: "yes"
    - name: create Kickstart files
      template:
        src: ks.cfg.j2
        dest: "{{ ks_basedir }}/{{ item.hostname }}.cfg"
      with_items:
        - "{{ vms }}"
ks.cfg.j2
# System authorization information
auth --enableshadow --passalgo=sha512

# Use CDROM installation media
# cdrom
# # Use network installation
url --url=http://{{ ks_server }}/pub/centos8/

# Use text install
text
# Run the Setup Agent on first boot
firstboot --enable
ignoredisk --only-use=sda
# Keyboard layouts
keyboard --vckeymap=jp --xlayouts='jp'
# System language
lang ja_JP.UTF-8

# Network information
{% for network in item.network %}
network {{ network }}
{% endfor %}
network  --hostname={{ item.hostname }}
(以下略)

ESXi にVMを作成し、起動する

vsphere_guest モジュールを使用します。使いかたは公式ドキュメントを参照してください。

createvms.yml
---
- hosts: 127.0.0.1
  connection: local
  user: root
  sudo: false
  gather_facts: false
  serial: 1
  tasks:
    - name: Deploy guest VMs
      vsphere_guest:
        vcenter_hostname: "{{ esx_ipaddr }}"
        validate_certs: no
        username: "{{ esx_user }}"
        password: "{{ esx_password }}"
        guest: "{{ item.hostname }}"
        state: powered_off
        vm_hw_version: vmx-08
        vm_extra_config:
          vcpu.hotadd: yes
          mem.hotadd:  yes
          notes: "{{ item.notes }}"
        vm_disk:
          disk1:
            size_gb: "{{ item.disksize }}"
            type: thin
            datastore: "{{ esx_datastore_name }}"
        vm_nic:
          nic1:
            type: vmxnet3
            network: "{{ item.nic1_network }}"
            network_type: standard
            mac_address: "{{ item.macaddr | regex_replace('-',':') }}"
          nic2:
            type: vmxnet3
            network: "{{ item.nic2_network }}"
            network_type: standard
        vm_hardware:
          memory_mb: "{{ item.memory }}"
          num_cpus: "{{ item.cpus }}"
          osid: "{{ item.osid }}"
          scsi: paravirtual
        esxi:
          datacenter: ha-datacenter
          hostname: "{{ esx_hostname  }}"
      with_items:
        - "{{ vms }}"

PXE boot file と違って、MACアドレスの指定は「:」区切りでないとエラーになりますので、インベントリに「-」区切りで定義しても置換するようにしています。
また、osid については、以下のページを参考にしてください。

Ansible playbook の実行

pxeserver.yml で PXE boot file および Kickstart file を作成し、createvms.yml で ESXi Server にVMを作成し、電源を入れます。

ansible-playbook -i inventory/esxi/inventory.ini pxeserver.yml
ansible-playbook -i inventory/esxi/inventory.ini createvms.yml

うまくいくと、VMが作成され、電源が入った後、OSが自動でインストールされます。

参考