はじめに #
VMをdeployする際にhostnameやuserの作成を実施したいことが多々あると思います。vSphereでは標準でcustomizationというOSのカスタマイズ手法があります。しかしvSphere7.0U3からcloud-initを使用したupdateもサポートされこちらでもOSのカスタマイズができるようになりました。
cloud-initの方がVMware以外(AWS等)でも使われている汎用的なOS初期化手法であり、userの追加や外部libのinstallできるとより複雑なことができる利点がある反面、cloud-init自体について知らないと使えないので今回はcloud-initについて軽く触れつつ、vSphereでの設定方法を紹介したいと思います。
1. cloud-init #
公式HPを見ると、cloud-initは主要クラウド、ベアメタルでサポートされる業界標準のOS初期化scriptと記載されています。実際にサポートされるプラットフォームをdatasourceと呼び、かなりのプラットフォームでサポートされていることがわかります。またcloud-initの良いところはこれらのプラットフォームを自動で判別してくれるので、基本的な使い方を覚えれば様々な場所で使えることも利点になります。
2. cloud-initの使い方 #
今回はubuntu22.04を例にして紹介します。
cloud-initで重要になるのは、deployしたいOS内部の設定であるcloud.cfg
と、OSの外からNW情報等の変数を挿入するuserdata.yaml
, metadata.yaml
になります。
cloud.cfg #
- OS側のcloud-init設定を実行するファイルで、userから受け取った情報(NW情報や、新規user作成方法)をどのように設定するか、cloud-initでカスタマイズする・しない項目の選択ができます。
- これは事前にOS側に配置しておく必要があります。
- 例えば「VMをcloneする → clone元に」、「ISOからinstallする → ISO内部に」入れておくなど
- Ubuntuであればdefaultでcloud-initがinstallされているので、最初から存在します。
- 通常は変更不要で最初から存在するものを使えば良いのですが、vSphereの場合はいくつか変更が必要 + nginx.conf等任意のファイルを編集したい等よりカスタマイズしたい場合はこれを事前に変更しておく必要があります。
# /etc/cloud/cloud.cfg
cloud_init_modules:
- migrator
- seed_random
- bootcmd
- write-files
- growpart
- resizefs
- disk_setup
- mounts
- set_hostname
- update_hostname
- update_etc_hosts
- ca-certs
- rsyslog
- users-groups
- ssh
# The modules that run in the 'config' stage
cloud_config_modules:
- wireguard
- snap
- ubuntu_autoinstall
- ssh-import-id
- keyboard
- locale
userdata.yaml #
- userdata.yamlはOSの外部から与える変数情報で、userに関する情報(作成するuser名、ssh公開鍵、所属group、passwd等)を記載することになります。
- これはOSで統一の設定ではなく、必要に応じて変更したいのでOSの外部から挿入する必要があります。
- OS外部から挿入する手法は、各プラットフォームが用意しています。もしくはInstall Media(ISO)に埋め込む等もできます。
#cloud-config
users:
- default
- name: sampleuser # 作成するユーザー
ssh_authorized_keys:
- ssh-ed25519 <公開鍵> # 登録する公開鍵
sudo: ALL=(ALL) NOPASSWD:ALL
groups: sudo, wheel # 所属させるgroup
shell: /bin/bash
lock-passwd: false # passwordのロックをするかしないか
passwd: $6$lXfnasOxmG1k/2lX$bd8lWUdX3mGSr9j2ImUzdGIhv14pmuqPE6W/ocfpx1/GVHjEZbPUJ.rteSvg05Vk6LK40Rtm93H3bViLyfIbx1
「lock_passwd: Defaults to true. Lock the password to disable password login」とdefaultではuserのpasswdはロックされるので注意。
metadata.yaml #
- metadata.yamlはOSの外部から与える変数情報で、OSに関する情報(hostname, network等)を設定する形になります。
- これもOSで統一の設定ではないので、OSの外部から挿入する必要があります。
#cloud-config
instance-id: abcdefgh
local-hostname: abcdefgh
hostname: abcdefgh
network:
version: 2
ethernets:
ens33:
addresses:
- 192.168.1.222/24
routes:
- to: default
via: 192.168.1.202
nameservers:
addresses:
- 192.168.1.202
search: [lab.local]
これらのyamlの書き方は公式ページにも記載されていますが、RedHatのページにユースケース毎に細かく掲載されているのでこちらを参考にするのがおすすめです。
3. vSphere環境でcloud-initを使ってみる。 #
- 今回はVMをclone後、cloud-initでOSを設定したいと思います。
- (isoの場合は、他blog参考)
STEP1. clone元でcloud-initの初期設定 #
先ほどの述べたようにcloud.cfgは基本的に変更不要ですが、VMware環境ではいくつか修正が必要なので面倒ですがclone元となるOS内部で以下設定を実施します。ubuntu2204を想定しますが、他OS(linux系)でも同じように使えるはずです。
1. KB59557対策 (競合するcustomizationのOFF) #
- vSphereにはOSの設定機能のcustomizationがありcloud-initと競合するので、cloud.cfgにcustomizationを無効するよう記載します。
/etc/cloud/cloud.cfg
の末尾に以下を追加します。
### 省略 ###
failsafe:
primary: http://ports.ubuntu.com/ubuntu-ports
security: http://ports.ubuntu.com/ubuntu-ports
ssh_svcname: ssh
disable_vmware_customization: false # ←を追加
2. KB80934対策 (Ubuntu20.04以降でNW設定が失敗することの回避) #
- Ubuntu20.04以降でcloud-initを使うとNW設定が失敗するようなので、以下ファイルを削除しておく。
rm -rf /etc/cloud/cloud.cfg.d/subiquity-disable-cloudinit-networking.cfg
rm -rf /etc/cloud/cloud.cfg.d/99-installer.cfg
3. cloud-initの初期化 + shutdown #
- cloud-initは基本的にOSを起動した初回のみしか発生しません。
- 今回修正を入れるためにOSを一度起動してしまったのでcleanを実行して、shutdownしておきます。
cloud-init clean
sudo shutdown
STEP2. VMのclone + userdata.yaml,metadata.yamlをOS外から挿入する #
STEP1にてOS内部の設定は完了したので、STEP2ではuserdata.yaml, metadata.yamlを用意しOS外部から挿入します。vSphereの場合は「vmの詳細パラメータに追加し、vmware-toolsによって読み込むこと」によって外部からOS内部に挿入することができます。
1. VMのclone, metadata.yaml, userdata.yamlを用意する。 #
- 前手順で用意したVMをcloneする。
- 設定したいOS情報であるmetadata.yaml, userdata.yamlを用意する。
2. vmの詳細パラメーター(Advanced Parameters)に用意したyamlを追加する。 #
- VMの詳細パラメータを開くと(vCenterを開きVMの設定 → 詳細パラメータ)、任意の情報を「key: value」の形で詰めることができます。
- cloud-init用には決まったkeyがあるのでこれにつめていきます。これらはcloud-init実行時にvmware-toolsを使って読み込まれます。
guestinfo.metadata
guestinfo.metadata.encoding
guestinfo.userdata
guestinfo.userdata.encoding
guestinfo.metadata.encoding
- metadataのデータをどのようなencodingで格納するかを記載します。
- ただし
gzip+base64
,base64
しか許容されていないので注意ください。今回はgzip+base64
を使います。
guestinfo.metadata
- ここにmetadata.yamlをgzip+base64にエンコードしたものを入力します。
- base64コマンドはdefaultだと改行を入れて見やすくしてしまうので、
-w 0
を追加しておきます。
cat metadata.yaml | gzip | base64 -w 0
(out)H4sIAAAAAAAAA22QwQ7CIBBE73zFJp6Llhqj/IrxsIVtS0RIWKq/L9XaNOptd95OZrIb4+NoKxND53ohXOCMwVDlrAZsjaWuH4SPBn01RM4Bb7QCf6RA+RHTVQuAOyV2MWhQZaE8UCqQJ1LWwE3zHgHQ2kTMxB+hgvqkZH04yloqpbZqP4MUx7w+y1FDycXR51krsQ712r9TM5qaMqWpll6uf7K/0hc3ABMmM2g4e2zl6yeXJ+Clw6w/AQAAro
- guestinfo.userdataも同様です。
STEP3. vmの起動 #
- ここまでできたら後は起動するだけで、cloud-initが実行されてVMの設定が開始されます。
おまけ #
- せっかくcloud-initという自動初期化scriptを使っているのに、手動でこれらを設定しているとあまり意味はないので、CLIで設定する方法をいくつか紹介します。
govc #
- ぱぱっと詰めれるので便利です。特にansibleのinventoryで管理するまでもない検証用にVMが欲しいけど、DHCPが使えない場合や識別のためにhost名はつけたい、proxyの設定が必要等の場合によく使います。
export GOVC_USERNAME=<vcenterのuser>
export GOVC_PASSWORD=<vcenterのpass>
export GOVC_URL=<vcenterのfqdn,ip>/sdk
export GOVC_INSECURE=true
METADATA=$(cat metadata.yaml | gzip | base64 -w 0)
USERDATA=$(cat userdata.yaml | gzip | base64 -w 0)
govc vm.change \
-vm "/Datacenter/vm/cloud-init-01" \
-e guestinfo.metadata="${METADATA}" \
-e guestinfo.metadata.encoding="gzip+base64" \
-e guestinfo.userdata="${USERDATA}" \
-e guestinfo.userdata.encoding="gzip+base64"
ansible #
<後で追記するかも>
4. deep dive #
cloud-initを調べるうちにいくつか気になったことがあったので調べて見ました。
vmの詳細パラメータの情報をどうやってOSで参照するか? #
- vmware-toolsのdaemon
vmtoolsd
もしくは、rpc用のコマンドvmware rpc
を使うことでこれらの情報が読み出せます。 - https://docs.vmware.com/jp/VMware-Tools/12.3.0/com.vmware.vsphere.vmwaretools.doc/GUID-D026777B-606D-4442-957A-B953C2049659.html
vmtoolsd --cmd "info-get guestinfo.vmtools.description"
(out)open-vm-tools 12.1.5 build 20735119
vmware-rpctool "info-get guestinfo.vmtools.description"
(out)open-vm-tools 12.1.5 build 20735119
vmware-rpctool "info-get guestinfo.metadata"
(out)H4sIAAAAAAAAA22QwQ7CIBBE73zFJp6Llhqj/IrxsIVtS0RIWKq/L9XaNOptd95OZrIb4+NoKxND53ohXOCMwVDlrAZsjaWuH4SPBn01RM4Bb7QCf6RA+RHTVQuAOyV2MWhQZaE8UCqQJ1LWwE3zHgHQ2kTMxB+hgvqkZH04yloqpbZqP4MUx7w+y1FDycXR51krsQ712r9TM5qaMqWpll6uf7K/0hc3ABMmM2g4e2zl6yeXJ+Clw6w/AQAA
vmware-rpctool "info-get guestinfo.metadata.encoding"
(out)gzip+base64
cloud-initはどうやってdatasource=vmwareを認識しているか? #
- cloud-initはvmwareだけではなく、さまざまなプラットフォームに対応しています。
- これらをdatasourceと呼び、AWS, GCP, Azure等主要なcloud等で対応しています。
- これらは、cloud.cfgで直接指定することもできますが、大抵は自動で判定してくれます
- 単純にこの仕組みについて気になったので、datasource判定用scriptを見てみます。
- https://github.com/canonical/cloud-init/blob/1baa9ff060b549391fd6e29028ac78311b5df58b/tools/ds-identify#L1528
- この内部では各種datasourceを上から総当たりで見にいき該当するdatasourceがあればそれを使用しているようです。
- datasourceがvmwareか判定する方法は以下
- 環境変数にvmware関連の情報があるか
- 今回は、
vmtoolsd --cmd "info-get guestinfo.metadata"
を実行して値が返ってきたら、datasource: vmwareという判定になっていそうです。
dscheck_VMware() {
# Checks to see if there is valid data for the VMware datasource.
# The data transports are checked in the following order:
#
# * envvars
# * guestinfo
# * imc (VMware Guest Customization)
#
# Please note when updating this function with support for new data
# transports, the order should match the order in the _get_data
# function from the file DataSourceVMware.py.
--- 省略 ---
# Activate the VMware datasource only if any of the fields used
# by the datasource are present in the guestinfo table.
if { vmware_guestinfo_metadata || \
vmware_guestinfo_userdata || \
vmware_guestinfo_vendordata; } >/dev/null 2>&1; then
return "${DS_FOUND}"
fi
--- 省略 ---
vmware_vmtoolsd_guestinfo() {
vmtoolsd --cmd "info-get guestinfo.${1}" 2>/dev/null | grep "[[:alnum:]]"
}
vmware_vmtoolsd_guestinfo_err() {
vmtoolsd --cmd "info-get guestinfo.${1}" 2>&1 | grep "[[:alnum:]]"
}
vmware_guestinfo() {
vmware_rpctool_guestinfo "${1}" || vmware_vmtoolsd_guestinfo "${1}"
}
vmware_guestinfo_err() {
vmware_rpctool_guestinfo_err "${1}" || vmware_vmtoolsd_guestinfo_err "${1}"
}
vmware_guestinfo_ovfenv_err() {
vmware_guestinfo_err "ovfEnv"
}
vmware_guestinfo_metadata() {
vmware_guestinfo "metadata"
}