Customizing a Guest
The ability to deploy a virtual machine with Kubernetes is nice, but one of the values of VM Operator is its support for popular bootstrap providers such as Cloud-Init, Sysprep, and vAppConfig. This page reviews these bootstrap providers to help inform when to select one over the other.
Bootstrap Providers
There are a number of methods that may be used to bootstrap a virtual machine's (VM) guest operating system:
Provider | Supported | Network Config | Linux | Windows | Description |
---|---|---|---|---|---|
Cloud-Init | ✓ | Cloud-Init Network v2 | ✓ | ✓ | The industry standard, multi-distro method for cross-platform, cloud instance initialization with modern, VM images |
Sysprep | ✓ | Guest OS Customization (GOSC) | ✓ | Microsoft Sysprep is used by VMware to customize Windows images on first-boot | |
vAppConfig | ✓ | Bespoke | ✓ | For images with bespoke, bootstrap engines driven by vAppConfig properties | |
OvfEnv | deprecated | Guest OS Customization (GOSC) | ✓ | A combination of GOSC and Cloud-Init user-data | |
ExtraConfig | deprecated | GOSC | ✓ | For images with bespoke, bootstrap engines that rely on Guest Info data |
ConfigMap
or Secret
The choice of a ConfigMap
or Secret
resource in no way impacts the choice or behavior of the selected bootstrap provider. When VM Operator was first released, the only way to store bootstrap data was via a ConfigMap
resource. While this still works, it is not recommended as data stored in a ConfigMap
is not encrypted at rest. Instead, it is recommended users switch to using Secret
resources for storing bootstrap data. Aside from how the data is stored in etcd, the following two resources are effectively identical and serve the same purpose:
apiVersion: v1
kind: ConfigMap
metadata:
name: my-vm-bootstrap-data
namespace: my-namespace
data:
user-data: |
#cloud-config
users:
- default
apiVersion: v1
kind: Secret
metadata:
name: my-vm-bootstrap-data
namespace: my-namespace
stringData:
user-data: |
#cloud-config
users:
- default
Supported
Cloud-Init
Cloud-Init is widely recognized as the de facto method for bootstrapping modern VM instances on hyperscalers, including Tanzu with VM Operator. For example, the following resources may be used to deploy a VM and bootstrap its guest with Cloud-Init to:
- add a custom user
- execute commands on boot
- write files
apiVersion: vmoperator.vmware.com/v1alpha1
kind: VirtualMachine
metadata:
name: my-vm
namespace: my-namespace
spec:
className: small
imageName: ubuntu-2210
storageClass: iscsi
vmMetadata:
transport: CloudInit
secretName: my-vm-bootstrap-data
apiVersion: v1
kind: Secret
metadata:
name: my-vm-bootstrap-data
namespace: my-namespace
stringData:
user-data: |
#cloud-config
users:
- default
- name: akutz
primary_group: akutz
groups: users
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj...
runcmd:
- "ls /"
- [ "ls", "-a", "-l", "/" ]
write_files:
- path: /etc/my-plaintext
permissions: '0644'
owner: root:root
content: |
Hello, world.
The data in the above Secret
is called the Cloud-Init Cloud Config. For more information on the Cloud-Init Cloud Config format, please see its official documentation.
Windows and Cloud-Init
It is possible to use the Cloud-Init bootstrap provider to deploy a Windows image if it contains Cloudbase-Init, the Windows port of Cloud-Init.
Sysprep
Microsoft originally designed Sysprep as a means to prepare a deployed system for use as a template. It was such a useful tool, that VMware utilized it as the means to customize a VM with a Windows guest.
Sysprep State
Deploying Windows images that have not completed their previous Sysprep operation could cause the Guest OS customization to fail. Therefore, it is important to ensure that the image is sealed correctly and in a clean state when using Sysprep. For more information on this issue, please refer to this article.
Minimal Config
The following YAML may be used to bootstrap a Windows image with minimal information. For proper network configuration and Guest OS Customization (GOSC) completion, Sysprep unattend data requires a template for providing network info and RunSynchronousCommand
to record GOSC status. Both components are essential for Windows Vista and later versions.
Product Key
Please note the image would have to be using a Volume License SKU as the product ID is not provided in the following Sysprep configuration. See the "Activate Windows" section below for more information.
apiVersion: vmoperator.vmware.com/v1alpha1
kind: VirtualMachine
metadata:
name: my-vm
namespace: my-namespace
spec:
className: small
imageName: windows11
storageClass: iscsi
vmMetadata:
transport: Sysprep
secretName: my-vm-bootstrap-data
apiVersion: v1
kind: Secret
metadata:
name: my-vm-bootstrap-data
namespace: my-ns
stringData:
unattend: |
<?xml version="1.0" encoding="UTF-8"?>
<unattend xmlns="urn:schemas-microsoft-com:unattend">
<settings pass="specialize">
<component name="Microsoft-Windows-TCPIP" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Interfaces>
<Interface wcm:action="add">
<Ipv4Settings>
<DhcpEnabled>false</DhcpEnabled>
</Ipv4Settings>
<Ipv6Settings>
<DhcpEnabled>false</DhcpEnabled>
</Ipv6Settings>
<Identifier>{{ V1alpha1_FirstNicMacAddr }}</Identifier>
<UnicastIpAddresses>
<IpAddress wcm:action="add" wcm:keyValue="1">{{ V1alpha1_FirstIP }}</IpAddress>
</UnicastIpAddresses>
<Routes>
<Route wcm:action="add">
<Identifier>0</Identifier>
<Metric>10</Metric>
<NextHopAddress>{{ (index .V1alpha1.Net.Devices 0).Gateway4 }}</NextHopAddress>
<Prefix>{{ V1alpha1_SubnetMask V1alpha1_FirstIP }}</Prefix>
</Route>
</Routes>
</Interface>
</Interfaces>
</component>
<component name="Microsoft-Windows-DNS-Client" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Interfaces>
<Interface wcm:action="add">
<Identifier>{{ V1alpha1_FirstNicMacAddr }}</Identifier>
<DNSServerSearchOrder> {{ range .V1alpha1.Net.Nameservers }} <IpAddress
wcm:action="add"
wcm:keyValue="{{.}}"></IpAddress> {{ end }} </DNSServerSearchOrder>
</Interface>
</Interfaces>
</component>
<component name="Microsoft-Windows-Deployment" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<RunSynchronous>
<RunSynchronousCommand wcm:action="add">
<Path>C:\sysprep\guestcustutil.exe restoreMountedDevices</Path>
<Order>1</Order>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Path>C:\sysprep\guestcustutil.exe flagComplete</Path>
<Order>2</Order>
</RunSynchronousCommand>
<RunSynchronousCommand wcm:action="add">
<Path>C:\sysprep\guestcustutil.exe deleteContainingFolder</Path>
<Order>3</Order>
</RunSynchronousCommand>
</RunSynchronous>
</component>
</settings>
</unattend>
Activate Windows
The following XML can be used to supply a product key to activate Windows:
<settings pass="windowsPE">
<component name="Microsoft-Windows-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserData>
<AcceptEula>true</AcceptEula>
<FullName>akutz</FullName>
<Organization>VMware</Organization>
<ProductKey>
<Key>1234-5678-9abc-defg</Key>
<WillShowUI>Never</WillShowUI>
</ProductKey>
</UserData>
</component>
</settings>
User Accounts
The following XML can be used to update Administrator password and create a new local user account:
<settings pass="oobeSystem">
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<UserAccounts>
<AdministratorPassword>
<Value>FakePassword</Value>
<PlainText>true</PlainText>
</AdministratorPassword>
<LocalAccounts>
<LocalAccount wcm:action="add">
<Name>sdiliyaer</Name>
<Password>
<Value>vmware</Value>
<PlainText>true</PlainText>
</Password>
<Group>Administrators</Group>
<DisplayName>sdiliyaer</DisplayName>
<Description>Local administrator account</Description>
</LocalAccount>
</LocalAccounts>
</UserAccounts>
</component>
</settings>
Automate OOBE
The following XML can be used to prevent appearing the Windows OOBE screen:
<settings pass="oobeSystem">
<component name="Microsoft-Windows-International-Core" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<InputLocale>0409:00000409</InputLocale>
<SystemLocale>en-US</SystemLocale>
<UserLocale>en-US</UserLocale>
<UILanguage>en-US</UILanguage>
</component>
<component name="Microsoft-Windows-Shell-Setup" processorArchitecture="amd64"
publicKeyToken="31bf3856ad364e35" language="neutral" versionScope="nonSxS"
xmlns:wcm="http://schemas.microsoft.com/WMIConfig/2002/State"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<OOBE>
<HideEULAPage>true</HideEULAPage>
<HideLocalAccountScreen>true</HideLocalAccountScreen>
<HideOEMRegistrationScreen>true</HideOEMRegistrationScreen>
<HideOnlineAccountScreens>true</HideOnlineAccountScreens>
<HideWirelessSetupInOOBE>true</HideWirelessSetupInOOBE>
<ProtectYourPC>3</ProtectYourPC>
<SkipMachineOOBE>true</SkipMachineOOBE>
<SkipUserOOBE>true</SkipUserOOBE>
</OOBE>
<TimeZone>Central Standard Time</TimeZone>
</component>
</settings>
For more information on Sysprep, please refer to Microsoft's official documentation.
vAppConfig
The vAppConfig bootstrap method is useful for legacy, VM images that rely on bespoke, boot-time processes that leverage vAppConfig properties for customizing a guest.
To illustrate, the following YAML can be utilized to deploy a VirtualMachine and bootstrap OVF properties that define the network information:
apiVersion: vmoperator.vmware.com/v1alpha1
kind: VirtualMachine
metadata:
name: my-vm
namespace: my-namespace
spec:
className: small
imageName: my-vm-image
storageClass: my-storage-class
vmMetadata:
transport: vAppConfig
secretName: my-vm-bootstrap-template
apiVersion: v1
kind: Secret
metadata:
name: my-vm-bootstrap-template
namespace: my-namespace
stringData:
nameservers: "{{ (index .V1alpha1.Net.Nameservers 0) }}"
hostname: "{{ .V1alpha1.VM.Name }} "
management_ip: "{{ (index (index .V1alpha1.Net.Devices 0).IPAddresses 0) }}"
management_gateway: "{{ (index .V1alpha1.Net.Devices 0).Gateway4 }}"
apiVersion: v1
kind: Secret
metadata:
name: my-secret
namespace: test-ns
stringData:
# see more details on below Supporting Template Queries section
nameservers: "{{ V1alpha1_FormatNameservers 2 \",\" }}"
management_ip: "{{ V1alpha1_FormatIP \"192.168.1.10\" \"255.255.255.0\" }}"
hostname: "{{ .V1alpha1.VM.Name }} "
management_gateway: "{{ (index .V1alpha1.Net.Devices 0).Gateway4 }}"
For more information on vAppConfig, please refer to tutorial/deploy-vm/vappconfig.
Deprecated
The following bootstrap providers are still available, but they are deprecated and are not recommended.
OvfEnv
The OvfEnv
method combines VMware's Guest OS Customization (GOSC) for bootstrapping a guest's network and the Cloud-Init, OVF data source to supply a Cloud-Init Cloud Config.
Deprecation Notice
The OvfEnv
transport has been deprecated in favor of the CloudInit
provider. Apart from the reasons outlined in the Cloud-Init section on why one would want to use Cloud-Init, the OvfEnv
provider's reliance on the GOSC APIs for bootstrapping the guest's network and Cloud-Init for additional customizations resulted in a race condition. VMware Tools would reboot the guest to satisfy the GOSC network configuration while Cloud-Init was still running. Thus any image that used OvfEnv
needed to have a special fix applied to prevent Cloud-Init from running on first-boot. This method is removed in v1alpha2, and any consumers still relying on this provider should switch to Cloud-Init.
The following resources may be used to deploy a VM and bootstrap the guest's network with GOSC and then leverage Cloud-Init to:
- add a custom user
- execute commands on boot
- write files
apiVersion: vmoperator.vmware.com/v1alpha1
kind: VirtualMachine
metadata:
name: my-vm
namespace: my-namespace
spec:
className: small
imageName: ubuntu-2210
storageClass: iscsi
vmMetadata:
transport: OvfEnv
configMapName: my-vm-bootstrap-data
apiVersion: v1
kind: ConfigMap
metadata:
name: my-vm-bootstrap-data
namespace: my-namespace
data:
user-data: I2Nsb3VkLWNvbmZpZwp1c2VyczoKLSBkZWZhdWx0Ci0gbmFtZTogYWt1dHoKICBwcmltYXJ5X2dyb3VwOiBha3V0egogIGdyb3VwczogdXNlcnMKICBzc2hfYXV0aG9yaXplZF9rZXlzOgogIC0gc3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEU0w3dVdHai4uLgpydW5jbWQ6Ci0gImxzIC8iCi0gWyAibHMiLCAiLWEiLCAiLWwiLCAiLyIgXQp3cml0ZV9maWxlczoKLSBwYXRoOiAvZXRjL215LXBsYWludGV4dAogIHBlcm1pc3Npb25zOiAnMDY0NCcKICBvd25lcjogcm9vdDpyb290CiAgY29udGVudDogfAogICAgSGVsbG8sIHdvcmxkLg==
Base64 Encoded User Data
Unlike the CloudInit
transport which accepts user data as either plain-text or base64-encoded, the OvfEnv
provider requires the base64-encoding. The base64-encoded value above decodes to:
#cloud-config
users:
- default
- name: akutz
primary_group: akutz
groups: users
ssh_authorized_keys:
- ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDSL7uWGj...
runcmd:
- "ls /"
- [ "ls", "-a", "-l", "/" ]
write_files:
- path: /etc/my-plaintext
permissions: '0644'
owner: root:root
content: |
Hello, world.
ExtraConfig
The ExtraConfig
provider combined VMware's Guest OS Customization (GOSC) for bootstrapping a guest's network allowed clients to set any guestinfo
key they wanted in order to influence a guest's bootstrap engine.
Deprecation Notice
When Tanzu Kubernetes was first released, the Cluster API provider that depended upon VM Operator used the ExtraConfig
provider for supplying bootstrap information. This method was never intended for wide use, and Tanzu now uses Cloud-Init anyway. To that end, this provider is no longer supported and will be removed in v1alpha2. Any consumers still relying on this provider should switch to Cloud-Init.