diff --git a/roles/kvirt_vm/README.md b/roles/kvirt_vm/README.md new file mode 100644 index 000000000..fb0eef0b6 --- /dev/null +++ b/roles/kvirt_vm/README.md @@ -0,0 +1,115 @@ +# kvirt_vm role + +This role allows the deployment of Kubevirt virtual machines. In order to execute the role a running OpenShift cluster and its credentials are required. i.e. through the KUBECONFIG environment variable. + +The role is aimed for deployment of virtual nodes for OCP deployments, so only the root disk is created. + +```shell +export KUBECONFIG= +``` + +Main tasks: +- Validate the storage class +- Check the required operators (CNV) +- Create the VMs and set it to the desired state + +This role has been tested only in x86_64 architectures. + +# Role variables +| Variable | Required | Type | Default | Description +| ----------------------------- | --------- | ------- | --------- | ----------- +| kvirt_vm_config_file | Yes | String | Undefined | The configuration file with the VM's settings + +## VM configs +| Variable | Default | Required | Description +| ----------------------------- | ----------------------------- | ----------- | --------------------------------- +| vms_settings | | yes | A list with the settings for each VM +| name | | yes | VM name +| force | false | no | Destroy the VM if already +| namespace | default | no | VM namespace +| storage_class | | no | Root disk storage class +| memory | 8Gi | no | VM memory +| disk_mode | ReadWriteOnce | no | VM disk volume mode +| disk_size | 60Gi | no | Root disk size +| os | rhcos | no | VM Operating system annotation +| cpu_cores | 8 | no | VM CPU cores +| cpu_sockets | 1 | no | VM CPU sockets +| cpu_threads | 1 | no | VM CPU threads +| network_interface_multiqueue | true | no | Enable NIC multiqueue +| running | false | no | Set the initial VM power state +| node_selector | | no | Configure nodes selector +| interfaces | virtio/masquerade | no | Network interface definitions +| networks | Pod network | no | VM network definitions + +## Usage examples + +See below for some examples of how to use the kvirt_vm role to create a VM. + +Deploy a VM with custom configs + +```yaml +- name: "Create a kvirt VM" + vars: + vms_config_file: /path/to/vms-config-file.yaml + ansible.builtin.include_role: + name: redhatci.ocp.kvirt_vm +``` + +Three VMs with default settings +```yaml +--- +vm_configs: + - name: master-0 + - name: master-1 + - name: master-2 +``` + +Generic VM definition +```yaml +--- +vm_configs: + - name: test + force: true + namespace: myns + memory: 8Gi + disk_mode: ReadWriteOnce + disk_size: 60Gi + os: rhcos + cpu_cores: 8 + cpu_sockets: 1 + cpu_threads: 1 + network_interface_multiqueue: true + running: false + node_selector: + kubernetes.io/hostname: master-1 + interfaces: + - masquerade: {} + model: virtio + name: default + networks: + - name: default + pod: {} + ansible.builtin.include_role: + name: redhatci.ocp.kvirt_vm +``` + +VM config file with SRIOV settings +```yaml +--- +vm_configs: + - name: master-1 + interfaces: + - macAddress: "54:54:00:00:21:20" + name: sriov_resource_name_0 + sriov: {} + - macAddress: "54:54:00:00:21:21" + name: sriov_resource_name_1 + sriov: {} + networks: + - multus: + networkName: sriov-network-name-0 + name: sriov_resource_name_0 + - multus: + networkName: sriov-network-name-1 + name: sriov_resource_name_1 +``` diff --git a/roles/kvirt_vm/defaults/main.yml b/roles/kvirt_vm/defaults/main.yml new file mode 100644 index 000000000..7a579780a --- /dev/null +++ b/roles/kvirt_vm/defaults/main.yml @@ -0,0 +1,21 @@ +--- +kvirt_vm_api_version: kubevirt.io/v1 +kvirt_vm_namespace: default +kvirt_vm_memory: 8Gi +kvirt_vm_disk_mode: ReadWriteOnce +kvirt_vm_disk_size: 60Gi +kvirt_vm_os: rhcos +kvirt_vm_cpu_cores: 8 +kvirt_vm_cpu_sockets: 1 +kvirt_vm_cpu_threads: 1 +kvirt_vm_network_interface_multiqueue: true +kvirt_vm_running: false +kvirt_vm_force: false +kvirt_vm_interfaces: + - masquerade: {} + model: virtio + name: default +kvirt_vm_networks: + - name: default + pod: {} +... diff --git a/roles/kvirt_vm/tasks/create-vm.yml b/roles/kvirt_vm/tasks/create-vm.yml new file mode 100644 index 000000000..a087c19bb --- /dev/null +++ b/roles/kvirt_vm/tasks/create-vm.yml @@ -0,0 +1,35 @@ +--- +- name: Create the VM Namespace for {{ vm.name }} + community.kubernetes.k8s: + definition: + apiVersion: v1 + kind: Namespace + metadata: + name: "{{ vm.namespace | default(kvirt_vm_namespace) }}" + labels: + security.openshift.io/scc.podSecurityLabelSync: "false" + pod-security.kubernetes.io/enforce: "baseline" + pod-security.kubernetes.io/enforce-version: "latest" + +- name: Create the VM {{ vm.name }} + community.kubernetes.k8s: + definition: "{{ lookup('template', 'templates/vm-template.yml.j2') }}" + +- name: Wait for VM to be in desired state {{ vm.name }} + vars: + state: |- + {%- if vm.running | default(kvirt_vm_running) %} + Running{%- else %} + Stopped{%- endif %} + community.kubernetes.k8s_info: + api: "{{ vm.api_version | default(kvirt_vm_api_version) }}" + kind: VirtualMachine + name: "{{ vm.name }}" + namespace: "{{ vm.namespace | default(kvirt_vm_namespace) }}" + register: info + until: + - info.resources is defined + - info.resources[0].status.printableStatus == state + retries: 60 + delay: 5 +... diff --git a/roles/kvirt_vm/tasks/delete-vm.yml b/roles/kvirt_vm/tasks/delete-vm.yml new file mode 100644 index 000000000..4c80d39b2 --- /dev/null +++ b/roles/kvirt_vm/tasks/delete-vm.yml @@ -0,0 +1,22 @@ +--- +- name: "Delete VM {{ vm.name }}" + community.kubernetes.k8s: + kind: VirtualMachine + api_version: "{{ vm.api_version | default(kvirt_vm_api_version) }}" + name: "{{ vm.name }}" + namespace: "{{ vm.namespace | default(kvirt_vm_namespace) }}" + state: absent + +- name: Wait for VirtualMachine to be deleted + community.kubernetes.k8s_info: + api: "{{ vm.api_version | default(kvirt_vm_api_version) }}" + kind: VirtualMachine + name: "{{ vm.name }}" + namespace: "{{ vm.namespace | default(kvirt_vm_namespace) }}" + register: info + until: + - info.resources is defined + - info.resources | length == 0 + retries: 60 + delay: 5 +... diff --git a/roles/kvirt_vm/tasks/main.yml b/roles/kvirt_vm/tasks/main.yml new file mode 100644 index 000000000..98bc01a37 --- /dev/null +++ b/roles/kvirt_vm/tasks/main.yml @@ -0,0 +1,19 @@ +--- +- name: Run validations + ansible.builtin.include_tasks: validations.yml + +- name: Delete VM + ansible.builtin.include_tasks: delete-vm.yml + when: vm.force | default(kvirt_vm_force) | bool + loop: "{{ vm_configs }}" + loop_control: + loop_var: vm + label: "{{ vm.name }}" + +- name: Create the VM + ansible.builtin.include_tasks: create-vm.yml + loop: "{{ vm_configs }}" + loop_control: + loop_var: vm + label: "{{ vm.name }}" +... diff --git a/roles/kvirt_vm/tasks/validations.yml b/roles/kvirt_vm/tasks/validations.yml new file mode 100644 index 000000000..a97536b99 --- /dev/null +++ b/roles/kvirt_vm/tasks/validations.yml @@ -0,0 +1,77 @@ +--- +- name: Check VM config file + ansible.builtin.stat: + path: "{{ kvirt_vm_config_file }}" + register: vms_conf_file_stat + +- name: Fail if VM config file is not found + ansible.builtin.fail: + msg: "VM config file {{ kvirt_vm_config_file }} not found" + when: not vms_conf_file_stat.stat.exists + +- name: Fail VM config file is empty + ansible.builtin.fail: + msg: "VM config file {{ kvirt_vm_config_file }} is empty" + when: vms_conf_file_stat.stat.size == 0 + +- name: Load VM config file + ansible.builtin.include_vars: + file: "{{ kvirt_vm_config_file }}" + when: vms_conf_file_stat.stat.exists + +- name: Fail if VM config file does not contain vm_configs + ansible.builtin.fail: + msg: "VM config file {{ kvirt_vm_config_file }} does not contain vm_configs" + when: vm_configs is not defined + +- name: Fail if not all VMs have a name + ansible.builtin.fail: + msg: "At least a VM in config file {{ kvirt_vm_config_file }} does not contain a name" + when: vm_configs | selectattr('name', 'undefined') | list | length > 0 + +- name: "Get Storage Classes" + community.kubernetes.k8s_info: + api_version: v1 + kind: StorageClass + register: sc + no_log: true + +- name: "Fail when there is no storage class available" + ansible.builtin.fail: + msg: "A storage class does not exists" + when: + - sc.resources | length == 0 + +- name: "Fail when defined storage class does not exist" + vars: + query_sc_name: 'resources[*].metadata.name' + query_results: "{{ sc | json_query(query_sc_name) }}" + ansible.builtin.fail: + msg: "The defined storage class does not exist" + when: + - kvirt_cluster_storage_class is defined + - kvirt_cluster_storage_class not in query_results + +- name: "Fail when no default storage class" + vars: + query_default_sc: 'resources[*].metadata.annotations."storageclass.kubernetes.io/is-default-class"' + query_results: "{{ sc | json_query(query_default_sc) }}" + ansible.builtin.fail: + msg: "No default storage class was found" + when: + - sc is defined + - "not('true' in query_results)" + - kvirt_cluster_storage_class is undefined + +- name: "Check if the CNV CRD is present" + community.kubernetes.k8s_info: + kind: CustomResourceDefinition + name: kubevirts.kubevirt.io + register: kvirt_crd + no_log: true + +- name: "Fail if CNV CRD is not present" + fail: + msg: "CRDs are not present" + when: kvirt_crd.resources | list | count == 0 +... diff --git a/roles/kvirt_vm/templates/vm-template.yml.j2 b/roles/kvirt_vm/templates/vm-template.yml.j2 new file mode 100644 index 000000000..662c77839 --- /dev/null +++ b/roles/kvirt_vm/templates/vm-template.yml.j2 @@ -0,0 +1,64 @@ +apiVersion: "{{ vm.api_version | default(kvirt_vm_api_version) }}" +kind: "VirtualMachine" +metadata: + name: "{{ vm.name }}" + namespace: "{{ vm.namespace | default(kvirt_vm_namespace) }}" +spec: + dataVolumeTemplates: + - apiVersion: cdi.kubevirt.io/v1beta1 + kind: DataVolume + metadata: + annotations: + cdi.kubevirt.io/storage.bind.immediate.requested: "true" + name: os-disk-{{ vm.namespace | default(kvirt_vm_namespace) }}-{{ vm.name }} + spec: + accessModes: [ "{{ vm.disk_mode | default(kvirt_vm_disk_mode) }}" ] +{% if storage_class is defined %} + storageClassName: "{{ vm.storage_class | default(kvirt_vm_storage_class) }}" +{% endif %} + source: + blank: {} + storage: + resources: + requests: + storage: "{{ vm.disk_size | default(kvirt_vm_disk_size) }}" + running: {{ vm.running | default(kvirt_vm_running) }} + template: + metadata: + annotations: + vm.kubevirt.io/os: "{{ vm.os | default(kvirt_vm_os) }}" + labels: + kubevirt.io/domain: "{{ vm.namespace | default(kvirt_vm_namespace) }}" + spec: +{% if node_selector is defined %} + nodeSelector: {{ vm.node_selector }} +{% endif %} + architecture: amd64 + domain: + cpu: + cores: {{ vm.cpu_cores | default(kvirt_vm_cpu_cores) }} + sockets: {{ vm.cpu_sockets | default(kvirt_vm_cpu_sockets) }} + threads: {{ vm.cpu_threads | default(kvirt_vm_cpu_threads) }} + devices: + disks: + - disk: + bus: virtio + name: rootdisk + interfaces: {{ vm.interfaces | default(kvirt_vm_interfaces) }} + networkInterfaceMultiqueue: {{ vm.network_interface_multiqueue | default(kvirt_vm_network_interface_multiqueue) }} + rng: {} + features: + acpi: {} + smm: + enabled: true + firmware: + bootloader: + efi: {} + memory: + guest: "{{ vm.memory | default(kvirt_vm_memory)}}" + resources: {} + networks: {{ vm.networks | default(kvirt_vm_networks) }} + volumes: + - dataVolume: + name: os-disk-{{ vm.namespace | default(kvirt_vm_namespace) }}-{{ vm.name }} + name: rootdisk