ESXiでVagrant用のCentOS8 boxを作る

#環境
ESXi6.7

- vm
  - CentOS8    ベースイメージ用VM
  - jumpserver Vagrant作業用VM

- OS
  - どちらもCentOS8.2.2004 Minimalインストールです

#はじめに
Vagrant CloudにあるCentOSのイメージはどのイメージもディスクが128GBで作成されており、自分の貧弱なESXi環境では何個もVM立ち上げたいけどそんなにディスクガバガバ使ってられない（シンプロで構築するのもありなんだけど、やっぱりオーバーコミットしてるとなんか気持ち悪い）・・・という経緯から、もっと小さいディスクサイズのboxイメージを作ることにしました。

# 手順
## CentOS8.2にVagrantをインストール
ESXiでVagrantを使うには、vagrant-vmware-esxi pluginを使う必要があります。
無料で使えるということで、vCenterを買えない自分には大変助かります。
ここではjumpserverにVagrantをインストールしていきます。

1. Vagrantのインストール

    ```
    # dnf install wget -y
    # wget https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.rpm
    # dnf install -y https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.rpm
    ```

    <details><summary>実行例</summary><div>

    ```
    [root@jumpserver ~]# dnf install -y https://releases.hashicorp.com/vagrant/2.2.9/vagrant_2.2.9_x86_64.rpm
    メタデータの期限切れの最終確認: 2:02:35 時間前の 2020年07月31日 07時01分01秒 に実施しました。
    vagrant_2.2.9_x86_64.rpm                                                                                                                               27 MB/s |  42 MB     00:01
    依存関係が解決しました。
    ======================================================================================================================================================================================
     パッケージ                                アーキテクチャー                         バージョン                                   リポジトリー                                   サイズ
    ======================================================================================================================================================================================
    インストール中:
     vagrant                                   x86_64                                   1:2.2.9-1                                    @commandline                                    42 M

    トランザクションの概要
    ======================================================================================================================================================================================
    インストール  1 パッケージ

    合計サイズ: 42 M
    インストール済みのサイズ: 122 M
    パッケージのダウンロード:
    トランザクションの確認を実行中
    トランザクションの確認に成功しました。
    トランザクションのテストを実行中
    トランザクションのテストに成功しました。
    トランザクションを実行中
      準備              :                                                                                                                                                             1/1
      インストール中    : vagrant-1:2.2.9-1.x86_64                                                                                                                                    1/1
      検証              : vagrant-1:2.2.9-1.x86_64                                                                                                                                    1/1
    Installed products updated.

    インストール済み:
      vagrant-1:2.2.9-1.x86_64

    完了しました!
    [root@jumpserver ~]#
    ```
    </div></details>

    インストールされていることを確認します。

    ```
    [root@jumpserver ~]# vagrant --version
    Vagrant 2.2.9
    [root@jumpserver ~]#
    ```

1. vagrant-vmware-vsphereのインストール
参考：[How to install](https://github.com/josenk/vagrant-vmware-esxi#how-to-install)

    ovftoolをインストールします
    https://code.vmware.com/web/tool/4.4.0/ovf
    →上記サイトからダウンロードします「VMware-ovftool-4.4.0-xxxxxxxx-lin.x86_64.bundle」
    ダウンロードしたファイルをjumpserverにコピーします。

    ##### ovftoolのインストール

    ```
    # dnf install libnsl -y
    # chmod 755 VMware-ovftool-4.4.0-15722219-lin.x86_64.bundle
    # ./VMware-ovftool-4.4.0-15722219-lin.x86_64.bundle
    # rm VMware-ovftool-4.4.0-15722219-lin.x86_64.bundle
    ```

    ##### プラグインのインストール

    ```
    # vagrant plugin install vagrant-vmware-esxi
    ```

    <details><summary>実行例</summary><div>
    
    ```
    [root@jumpserver ~]# vagrant plugin install vagrant-vmware-esxi
    Installing the 'vagrant-vmware-esxi' plugin. This can take a few minutes...
    Fetching iniparse-1.5.0.gem
    Fetching mini_portile2-2.4.0.gem
    Fetching nokogiri-1.10.10.gem
    Building native extensions. This could take a while...
    Vagrant failed to properly resolve required dependencies. These
    errors can commonly be caused by misconfigured plugin installations
    or transient network issues. The reported error is:

    ERROR: Failed to build gem native extension.

        current directory: /root/.vagrant.d/gems/2.6.6/gems/nokogiri-1.10.10/ext/nokogiri
    /opt/vagrant/embedded/bin/ruby -I /opt/vagrant/embedded/lib/ruby/2.6.0 -r ./siteconf20200731-37296-147ltcn.rb extconf.rb
    checking if the C compiler accepts -I/opt/vagrant/embedded/include -I/opt/vagrant/embedded/include/libxml2... *** extconf.rb failed ***
    Could not create Makefile due to some reason, probably lack of necessary
    libraries and/or headers.  Check the mkmf.log file for more details.  You may
    need configuration options.

    Provided configuration options:
            --with-opt-dir
            --with-opt-include
            --without-opt-include=${opt-dir}/include
            --with-opt-lib
            --without-opt-lib=${opt-dir}/lib
            --with-make-prog
            --without-make-prog
            --srcdir=.
            --curdir
            --ruby=/opt/vagrant/embedded/bin/$(RUBY_BASE_NAME)
            --help
            --clean
    /opt/vagrant/embedded/lib/ruby/2.6.0/mkmf.rb:467:in `try_do': The compiler failed to generate an executable file. (RuntimeError)
    You have to install development tools first.
            from /opt/vagrant/embedded/lib/ruby/2.6.0/mkmf.rb:585:in `block in try_compile'
            from /opt/vagrant/embedded/lib/ruby/2.6.0/mkmf.rb:532:in `with_werror'
            from /opt/vagrant/embedded/lib/ruby/2.6.0/mkmf.rb:585:in `try_compile'
            from extconf.rb:138:in `nokogiri_try_compile'
            from extconf.rb:162:in `block in add_cflags'
            from /opt/vagrant/embedded/lib/ruby/2.6.0/mkmf.rb:643:in `with_cflags'
            from extconf.rb:161:in `add_cflags'
            from extconf.rb:416:in `<main>'

    To see why this extension failed to compile, please check the mkmf.log which can be found here:

      /root/.vagrant.d/gems/2.6.6/extensions/x86_64-linux/2.6.0/nokogiri-1.10.10/mkmf.log

    extconf failed, exit code 1

    Gem files will remain installed in /root/.vagrant.d/gems/2.6.6/gems/nokogiri-1.10.10 for inspection.
    Results logged to /root/.vagrant.d/gems/2.6.6/extensions/x86_64-linux/2.6.0/nokogiri-1.10.10/gem_make.out

    [root@jumpserver ~]#

    ```
    </div></details>
    
    CentOS8のminimalインストールだといろいろパッケージが足りないらしい。
    メッセージに従ってログを確認して、何度かトライして。。。
    最終的には、以下のパッケージを追加でインストールしました。
    
    必要なパッケージの追加
    
    ```
    # dnf install -y gcc make
    ```

    vagrant-vmware-vsphereのインストール（再実行）

    ```
    # vagrant plugin install vagrant-vmware-esxi
    # vagrant plugin list
    # vagrant version
    ```

    <details><summary>実行例</summary><div>

    ```
    [root@jumpserver ~]# vagrant plugin install vagrant-vmware-esxi
    Installing the 'vagrant-vmware-esxi' plugin. This can take a few minutes...
    Building native extensions. This could take a while...
    Fetching vagrant-vmware-esxi-2.5.0.gem
    Installed the plugin 'vagrant-vmware-esxi (2.5.0)'!
    [root@jumpserver ~]#
    [root@jumpserver ~]# vagrant plugin list
    vagrant-vmware-esxi (2.5.0, global)
    [root@jumpserver ~]#
    [root@jumpserver ~]# vagrant version
    Installed Version: 2.2.9
    Latest Version: 2.2.9

    You're running an up-to-date version of Vagrant!
    [root@jumpserver ~]#
    ```
    </div></details>
    
## Vagrant box用イメージの設定

1. ESXi6.7にCentOS8.2をminimalインストールします

    boxのディスクサイズを30GBにしたかったのでvmのディスクを30GBにしてインストールしました。

1. OSのパッケージを最新化します

    ```
    # dnf update -y
    ```

1. Vagrant のデフォルトユーザの仕様に従いユーザを作成します
参照：[Default User Settings](https://www.vagrantup.com/docs/boxes/base#default-user-settings)

    #### vagrantユーザの作成

    ```
    # adduser vagrant
    ```

    #### パスワードは「vagrant」を設定します

    ```
    # password vagrant
    ```

    #### vagrantユーザの.sshディレクトリにsshの公開鍵を配置します
    ["ここ"](https://github.com/hashicorp/vagrant/tree/master/keys)の vagrant.pubの内容を/home/vagrant/.ssh/authorized_keysに記載します

    ```
    実際の操作
    [root@localhost ~]# su vagrant
    [vagrant@localhost root]$ cd
    [vagrant@localhost ~]$ pwd
    /home/vagrant
    [vagrant@localhost ~]$
    [vagrant@localhost ~]$ mkdir -m 700 /home/vagrant/.ssh
    [vagrant@localhost ~]$ cd /home/vagrant/.ssh
    [vagrant@localhost ~]$ vi authorized_keys
    [vagrant@localhost ~]$ chmod 600 authorized_keys
    [vagrant@localhost ~]$ exit
    ```

    rootのパスワードは必要ないけど一般的なboxだと「vagrant」にしといたらと書いてあります。今回は自分用のboxなのでOSインストール時に設定した自分が使っているパスワードのまま変更しません。
    参考：[Root Password: "vagrant"](https://www.vagrantup.com/docs/boxes/base#root-password-vagrant)

    #### vagrantユーザがパスワードなしでsudoできるようにします
    参考：[Password-less Sudo](https://www.vagrantup.com/docs/boxes/base#password-less-sudo)

    ```
    # visodo

    以下の行を最終行に追加します
    vagrant ALL=(ALL) NOPASSWD: ALL
    ```

    #### sshdのUseDNSをnoにしてssh接続を高速化します
    参考：[SSH Tweaks](https://www.vagrantup.com/docs/boxes/base#ssh-tweaks)

    ```
    # vi /etc/ssh/sshd_config

    UseDNS no がコメント化されている行があるので先頭の#を外します
    ```

    #### Boxのサイズを小さくするために不要なキャッシュなどを削除します
    参考：[CentOS7の綺麗なboxを自作する](https://qiita.com/ShibuyaBizman/items/6dde0c6cb9b5e8cb64f7)

    ##### キャッシュ（今までダウンロードしたrpmファイル、ヘッダファイルなど）の削除

    ```
    # sudo dnf clean all
    ```

    ##### 履歴を残さない

    ```
    # export HISTSIZE=0
    ```
    ##### 履歴の削除

    ```
    # rm -f /home/vagrant/.bash_history
    # rm -f /root/.bash_history
    ```
    ##### ログの削除

    ```
    # rm -rf /var/log/*
    # rm -rf /tmp/*
    ```

    ##### 仮想ディスクの空き容量に0を書き込む（Boxのサイズが小さくなる）

    ```
    # dd if=/dev/zero of=/EMPTY bs=1M
    # rm -f /EMPTY
    ```

    ##### 仮想マシンの停止

    ```
    # shutdown -h now
    ```

## Vagrant box用イメージの作成
参考：[ESXi上のVMをboxファイルにする方法](https://qiita.com/user_h/items/8ea654aaa3a81601b77a)

1. ESXiはsshログインできるように設定します

    WebUIにログインし、ホスト→管理→サービスのTSM-SSHを右クリックして「起動」します。

1. Vagrantサーバー（jumpserver）でbox作成用のVagrantfileを作成する。

    ```
    [root@jumpserver ~]# mkdir create-baseos
    [root@jumpserver ~]# cd create-baseos/
    [root@jumpserver create-baseos]# vagrant init
    [root@jumpserver create-baseos]# vi Vagrantfile
    [root@jumpserver create-baseos]# vi config.yaml
    ```

    ##### Vagrantfileを以下のように編集

    ```
    # -*- mode: ruby -*-
    # vi: set ft=ruby :

    require 'yaml'

    # 設定ファイルの読み込み
    settings = YAML.load_file('config.yaml')

    Vagrant.configure("2") do |config|

      config.vm.define settings['node']['hostname'] do |node|
        node.vm.hostname = settings['node']['hostname']
      end

      config.vm.provider :vmware_esxi do |esxi|
        # イメージ作成元のESXiの情報
        esxi.guest_name = settings['node']['hostname']
        esxi.esxi_hostname = settings['esxi']['hostname']
        esxi.esxi_username = settings['esxi']['username']
        esxi.esxi_password = settings['esxi']['password']
        esxi.esxi_hostport = settings['esxi']['hostport']
      end
    end
    ```

    ##### config.yamlを以下のように作成


    ```
    esxi:
      hostname: "192.168.123.74"  # ESXiのIPアドレス
      username: "root"            # ESXiのログインユーザ
      password: "prompt:"         # ログインパスワード("prompt:" とするとvagrant コマンド実行時にESXiのパスワードを入力)
      hostport: 22                # ESXiのsshログインポート
    node:
      hostname: "CentOS8"         # ベース用に作成したVMの名前
    ```

1. VMのマシンIDを取得します
    ESXiにSSHでログインし、box用のイメージとして用意したVMの情報を取得します。

    ```
    # vim-cmd vmsvc/getallvms
    ```

    <details><summary>実行例</summary><div>

    ```
    [root@localhost:~] vim-cmd vmsvc/getallvms
    Vmid    Name                   File                    Guest OS       Version   Annotation
    32     CentOS8   [datastore1] CentOS8/CentOS8.vmx   centos8_64Guest   vmx-14
    [root@localhost:~]
    ```
    </div></details>
  
1. VMのマシンIDをvagrantの設定ファイルに記載します。

    以下のファイルに記載する

    ```
    .vagrant/machines/<ESXiのVMの名前>/vmware_esxi/id
    ```

    <details><summary>実行例</summary><div>

    ```
    [root@jumpserver ~]# cd ~/create-baseos/
    [root@jumpserver create-baseos]# mkdir -p .vagrant/machines/CentOS8/vmware_esxi
    [root@jumpserver create-baseos]# vi .vagrant/machines/CentOS8/vmware_esxi/id
    [root@jumpserver create-baseos]# cat .vagrant/machines/CentOS8/vmware_esxi/id
    32
    [root@jumpserver create-baseos]#
    ```
    </div></details>

1. boxファイルの作成

    ESXiのVMがisoファイルをマウントする設定になっている場合は、設定を編集してマウントを外す。
    →インストールした後にイメージファイルを設定したまま忘れていることが多いので注意

    ##### boxの作成
    outputには出力するboxファイルの名前を指定します。

    ````
    # vagrant package --output centos8base
    ````

    <details><summary>実行例</summary><div>

    ```
    [root@jumpserver create-baseos]# vagrant package --output centos8base

    ==> CentOS8: --- Attempting to package
    ==> CentOS8: --- boxname: centos8base.box
    VMware ovftool 4.4.0 (build-15722219)
    Opening VI source: vi://root@192.168.123.74:443/
    Opening VMX target: ZZZZ_tmpdir
    Writing VMX file: ZZZZ_tmpdir/centos8base/centos8base.vmx
    Transfer Completed
    Completed successfully
    ==> CentOS8: tarring centos8base.box
    centos8base-disk1.vmdk
    centos8base-file1.nvram
    centos8base.vmx
    metadata.json
    ==> CentOS8: Doing cleanup.
    [root@jumpserver create-baseos]# ls -lh
    合計 1.7G
    -rw-r--r--. 1 root root  654  8月  7 22:02 Vagrantfile
    -rw-r--r--. 1 root root 1.7G  8月  7 22:16 centos8base.box      ←★作成されている
    -rw-r--r--. 1 root root  369  8月  7 21:52 config.yaml
    [root@jumpserver create-baseos]#
    ```
    </div></details>

    ##### boxファイルをvagrantに追加
    nameはboxに付ける名前を指定します。

    ```
    # vagrant box add centos8base.box --name centos8box
    ```

    <details><summary>実行例</summary><div>

    ```
    [root@jumpserver create-baseos]# vagrant box add centos8base.box --name centos8box

    ==> box: Box file was not detected as metadata. Adding it directly...
    ==> box: Adding box 'centos8box' (v0) for provider:
        box: Unpacking necessary files from: file:///root/create-baseos/centos8base.box
    ==> box: Successfully added box 'centos8box' (v0) for 'vmware'!
    [root@jumpserver create-baseos]# vagrant box list
    centos8box      (vmware, 0)                     ←★追加されている
    [root@jumpserver create-baseos]#
    ```
    </div></details>


## 作成したboxを使用してVMを起動する（動作確認）

1. Vagrantファイルの作成
    作業用フォルダの作成

    ```
    [root@jumpserver ~]# mkdir test-baseos/
    [root@jumpserver ~]# cd test-baseos
    [root@jumpserver test-baseos]#
    ```

    Vagrantfileの作成

    ```
    [root@jumpserver test-baseos]# vagrant init
    A `Vagrantfile` has been placed in this directory. You are now
    ready to `vagrant up` your first virtual environment! Please read
    the comments in the Vagrantfile as well as documentation on
    `vagrantup.com` for more information on using Vagrant.
    [root@jumpserver test-baseos]# vi Vagrantfile
    [root@jumpserver test-baseos]# vi config.yaml
    [root@jumpserver test-baseos]#
    ```

    ##### Vagrantfileを以下のように編集

    ```
    # -*- mode: ruby -*-
    # vi: set ft=ruby :

    require 'yaml'

    # 設定ファイルの読み込み
    settings = YAML.load_file('config.yaml')

    Vagrant.configure("2") do |config|

      # 共有フォルダを使用しない
      config.vm.synced_folder '.', '/vagrant', disabled: true

      config.vm.define settings['node']['hostname'] do |node|
        # ホスト名
        node.vm.hostname = settings['node']['hostname']
        # ノードのベースOSを指定
        node.vm.box = settings['os']
      end

      config.vm.provider :vmware_esxi do |esxi|
        # 構築先のESXiの情報
        esxi.esxi_hostname = settings['esxi']['hostname']
        esxi.esxi_username = settings['esxi']['username']
        esxi.esxi_password = settings['esxi']['password']
        esxi.esxi_hostport = settings['esxi']['hostport']
        esxi.esxi_disk_store = settings['esxi']['diskstore']
        esxi.guest_guestos = settings['os_type']

        # 構築するVMの設定
        esxi.guest_name = settings['node']['hostname']
        esxi.guest_username = settings['guest_user']
        esxi.guest_memsize = settings['node']['memory']
        esxi.guest_numvcpus = settings['node']['cpus']
        esxi.guest_disk_type = settings['node']['disk_type']
      end
    end
    ```

    ##### config.yamlを以下のように作成


    ```
    esxi:
      hostname: "192.168.123.74"  # ESXiのIPアドレス
      username: "root"            # ESXiのログインユーザ
      password: "prompt:"         # ログインパスワード("prompt:" とするとvagrant コマンド実行時にESXiのパスワードを入力)
      hostport: 22                # ESXiのsshログインポート
      diskstore: "datastore1"     # 使用するデータストア名

    os: "centos8box"              # 使用するOSイメージの指定（box）
    os_type: "centos8-64Guest"    # OSタイプ

    guest_user: "vagrant"         # VMのゲストユーザ名

    node:
      hostname: "CentOS8-test"  # 作成するVMの名前
      cpus: 2                   # CPU数
      memory: 4096              # メモリ(GB)
      disk_type: "thin"         # ディスクの形式（thin, thick, eagerzeroedthick）
    ```

1. VMの起動

    ```
    # vagrant up
    ```

    <details><summary>実行例</summary><div>

    ```
    [root@jumpserver test-baseos]# vagrant up

    Bringing machine 'CentOS8-test' up with 'vmware_esxi' provider...
    ==> CentOS8-test: Virtual Machine will be built.
    VMware ovftool 4.4.0 (build-15722219)
    ==> CentOS8-test: --- WARNING         : esxi_virtual_network[0] not set, using VM Network
    ==> CentOS8-test: --- WARNING         : GuestOS:  not supported, using box/ovftool defaults
    ==> CentOS8-test: ---   --- ESXi Summary ---
    ==> CentOS8-test: --- ESXi host       : 192.168.123.74
    ==> CentOS8-test: --- Virtual Network : ["VM Network"]
    ==> CentOS8-test: --- Disk Store      : datastore1
    ==> CentOS8-test: --- Resource Pool   : /
    ==> CentOS8-test: ---  --- Guest Summary ---
    ==> CentOS8-test: --- VM Name         : CentOS8-test
    ==> CentOS8-test: --- Box             : centos8box
    ==> CentOS8-test: --- Box Ver         : 0
    ==> CentOS8-test: --- Memsize (MB)    : 4096
    ==> CentOS8-test: --- CPUS            : 2
    ==> CentOS8-test: --- Disk Type       : thin
    ==> CentOS8-test: --- Guest OS type   : centos8-64
    ==> CentOS8-test: ---   --- Guest Build ---
    Opening VMX source: /root/.vagrant.d/boxes/centos8box/0/vmware/ZZZZ_CentOS8-test.vmx
    Opening VI target: vi://root@192.168.123.74:443/
    Deploying to VI: vi://root@192.168.123.74:443/
    Transfer Completed
    Completed successfully
    ==> CentOS8-test: --- VMID            : 34
    ==> CentOS8-test: --- VM has been Powered On...
    ==> CentOS8-test: --- Waiting for state "running"
    ==> CentOS8-test: --- Success, state is now "running"
    ==> CentOS8-test: Setting hostname...
        CentOS8-test:
        CentOS8-test: Vagrant insecure key detected. Vagrant will automatically replace
        CentOS8-test: this with a newly generated keypair for better security.
        CentOS8-test:
        CentOS8-test: Inserting generated public key within guest...
        CentOS8-test: Removing insecure key from the guest if it's present...
        CentOS8-test: Key inserted! Disconnecting and reconnecting using new SSH key...
    [root@jumpserver test-baseos]#
    ```
    </div></details>

# まとめ
テストや環境構築をする際に何度もVMにOSをインストールするのがめんどうになり、自動化に手を出してしまいました。
高価なvCenterは使えないので無料のESXiで何とかしようと思って先人の知恵をお借りすることで目的を達することができました。ありがとうございます。
本当は、terraformでやろうとしていたのですが、カスタムイメージの作成のところで躓いて不完全燃焼のままです。
最終的に事例の多いVagrantでやりました。そのうちterraformで。。。


#参考URL
[vagrant-vmware-esxi plugin](https://github.com/josenk/vagrant-vmware-esxi)
[Creating a Base Box](https://www.vagrantup.com/docs/boxes/base)
[CentOS7の綺麗なboxを自作する](https://qiita.com/ShibuyaBizman/items/6dde0c6cb9b5e8cb64f7)
[ESXi上のVMをboxファイルにする方法](https://qiita.com/user_h/items/8ea654aaa3a81601b77a)