diff --git a/docs/kubevirt.md b/docs/kubevirt.md index 921ed0b0f9..2e99e05ec6 100644 --- a/docs/kubevirt.md +++ b/docs/kubevirt.md @@ -8,7 +8,7 @@ This example is using Ansible playbooks and it does not need any molecule plugins to run. You can fully control which test requirements you need to be installed. -## Prerequisites +## Prerequisites The `create.yml` and `destroy.yml` Ansible playbooks require the Ansible collection `kubernetes.core`. For seamless communication with the Kubernetes API server, the collection uses the following environment variables: @@ -16,7 +16,7 @@ The `create.yml` and `destroy.yml` Ansible playbooks require the Ansible collect - `K8S_AUTH_HOST`: This points to the URL of the Kubernetes cluster's API. -- `K8S_AUTH_VERIFY_SSL`: If set to `false`, this disables the verification of SSL/TLS certificates, which might pose a security risk. It's mainly used for testing environments, particularly when dealing with self-signed certificates. +- `K8S_AUTH_VERIFY_SSL`: If set to `false`, this disables the verification of SSL/TLS certificates, which might pose a security risk. It's mainly used for testing environments, particularly when dealing with self-signed certificates. Additionally, for the playbooks to work, the Kubernetes service account needs specific roles and role bindings to operate in a particular namespace. This ensures the playbook has sufficient privileges to execute commands on the Kubernetes resources. These roles include getting, listing, watching, creating, deleting, and editing virtual machines and services. @@ -34,12 +34,12 @@ metadata: namespace: name: rules: -- apiGroups: ["kubevirt.io"] - resources: ["virtualmachines"] - verbs: ["get", "list", "watch", "create", "delete", "patch", "edit"] -- apiGroups: [""] - resources: ["services"] - verbs: ["get", "list", "watch", "create", "delete", "patch", "edit"] + - apiGroups: ["kubevirt.io"] + resources: ["virtualmachines"] + verbs: ["get", "list", "watch", "create", "delete", "patch", "edit"] + - apiGroups: [""] + resources: ["services"] + verbs: ["get", "list", "watch", "create", "delete", "patch", "edit"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding @@ -47,21 +47,24 @@ metadata: name: namespace: subjects: -- kind: ServiceAccount - name: - namespace: + - kind: ServiceAccount + name: + namespace: roleRef: kind: Role name: apiGroup: rbac.authorization.k8s.io ``` + You will need to substitute the following placeholders: + - ``: This refers to the name of the Kubernetes Serviceaccount that the molecule test utilizes to create the KubeVirt VM. - ``: This denotes the name of the Kubernetes namespace where the VMs will be instantiated. - ``: This is the name of the Kubernetes role which encapsulates the necessary permissions for the molecule test to function. - ``: This represents the name of the Kubernetes rolebinding that associates the role `` with the serviceaccount ``. ## Considerations + - This example employs ephemeral VMs, which enhance the speed of VM creation and cleanup. However, it's important to note that any data in the system will not be retained if the VM is rebooted. - You don't need to worry about setting up SSH keys. The `create.yml` Ansible playbook takes care of configuring a temporary SSH key. @@ -72,6 +75,7 @@ You will need to substitute the following placeholders: ``` Please, replace the following parameters: + - ``: This should be replaced with the namespace in Kubernetes where you intend to create the KubeVirt VMs. - ``: Change this to the fully qualified domain name (FQDN) of the Kubernetes node that Ansible will attempt to SSH into via the Service NodePort. @@ -103,4 +107,4 @@ Please, replace the following parameters: ```yaml title="destroy.yml" {!../molecule/kubevirt/destroy.yml!} -``` \ No newline at end of file +``` diff --git a/molecule/kubevirt/create.yml b/molecule/kubevirt/create.yml index f7a6efea6d..f53c389db3 100644 --- a/molecule/kubevirt/create.yml +++ b/molecule/kubevirt/create.yml @@ -3,31 +3,31 @@ connection: local gather_facts: false vars: - temporary_ssh_key_size: 2048 # Variable for the size of the SSH key + temporary_ssh_key_size: 2048 # Variable for the size of the SSH key tasks: - - name: Set default SSH key path # Sets the path of the SSH key + - name: Set default SSH key path # Sets the path of the SSH key set_fact: tempoary_ssh_key_path: "{{ molecule_ephemeral_directory }}/identity_file" - - name: Generate SSH key pair # Generates a new SSH key pair + - name: Generate SSH key pair # Generates a new SSH key pair community.crypto.openssh_keypair: path: "{{ tempoary_ssh_key_path }}" size: "{{ temporary_ssh_key_size }}" - register: temporary_ssh_keypair # Stores the output of this task in a variable + register: temporary_ssh_keypair # Stores the output of this task in a variable - - name: Set SSH public key # Sets the SSH public key from the key pair - set_fact: + - name: Set SSH public key # Sets the SSH public key from the key pair + set_fact: temporary_ssh_public_key: "{{ temporary_ssh_keypair.public_key }}" - - name: Create VM in KubeVirt # Calls another file to create the VM in KubeVirt + - name: Create VM in KubeVirt # Calls another file to create the VM in KubeVirt include_tasks: tasks/create_vm.yml - loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml + loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml loop_control: - loop_var: vm # Sets the variable for the current item in the loop + loop_var: vm # Sets the variable for the current item in the loop - - name: Create Nodeport service if ssh_type is set to NodePort # Conditional block, executes if vm.ssh_service.type is NodePort + - name: Create Nodeport service if ssh_type is set to NodePort # Conditional block, executes if vm.ssh_service.type is NodePort block: - - name: Create ssh NodePort Kubernetes Services # Creates a new NodePort service in Kubernetes + - name: Create ssh NodePort Kubernetes Services # Creates a new NodePort service in Kubernetes kubernetes.core.k8s: state: present definition: @@ -44,53 +44,53 @@ selector: kubevirt.io/domain: "{{ vm.name }}" type: NodePort - loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml + loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml loop_control: - loop_var: vm # Sets the variable for the current item in the loop + loop_var: vm # Sets the variable for the current item in the loop - - name: Retrieve Service Info # Retrieves information about the service + - name: Retrieve Service Info # Retrieves information about the service kubernetes.core.k8s_info: api_version: v1 kind: Service name: "{{ vm.name }}" namespace: "{{ vm.namespace }}" - loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml + loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml loop_control: - loop_var: vm # Sets the variable for the current item in the loop - register: node_port_services # Stores the output of this task in a variable - when: "vm.ssh_service.type == 'NodePort'" # The block is executed when this condition is met + loop_var: vm # Sets the variable for the current item in the loop + register: node_port_services # Stores the output of this task in a variable + when: "vm.ssh_service.type == 'NodePort'" # The block is executed when this condition is met - - name: Create VM dictionary # Calls another file to create a dictionary with information about the VM + - name: Create VM dictionary # Calls another file to create a dictionary with information about the VM include_tasks: tasks/create_vm_dictionary.yml - loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml + loop: "{{ molecule_yml.platforms }}" # Loops over all platforms defined in molecule_yml loop_control: - loop_var: vm # Sets the variable for the current item in the loop + loop_var: vm # Sets the variable for the current item in the loop - - name: Create ansible inventory from dictionary # Creates an Ansible inventory file from the dictionary + - name: Create ansible inventory from dictionary # Creates an Ansible inventory file from the dictionary vars: molecule_inventory: all: children: molecule: - hosts: "{{ molecule_systems }}" + hosts: "{{ molecule_systems }}" ansible.builtin.copy: content: "{{ molecule_inventory | to_nice_yaml }}" dest: "{{ molecule_ephemeral_directory }}/inventory/molecule_inventory.yml" - mode: 0600 # Sets the permissions of the file to -rw------- + mode: 0600 # Sets the permissions of the file to -rw------- - - name: Refresh inventory # Refreshes the inventory + - name: Refresh inventory # Refreshes the inventory ansible.builtin.meta: refresh_inventory - - name: Assert molecule group exists # Checks if the 'molecule' group exists in the inventory + - name: Assert molecule group exists # Checks if the 'molecule' group exists in the inventory ansible.builtin.assert: that: "'molecule' in groups" fail_msg: "Molecule group was not found in inventory groups: {{ groups }}" - run_once: true # Ensures this task is only run once, not on every host in 'hosts' + run_once: true # Ensures this task is only run once, not on every host in 'hosts' -- name: Validate that inventory was refreshed # New playbook to validate the inventory - hosts: molecule # Runs on hosts in the 'molecule' group - gather_facts: false # Disables fact gathering +- name: Validate that inventory was refreshed # New playbook to validate the inventory + hosts: molecule # Runs on hosts in the 'molecule' group + gather_facts: false # Disables fact gathering tasks: - - name: Wait for the host to be reachable # Waits for the host to become reachable + - name: Wait for the host to be reachable # Waits for the host to become reachable ansible.builtin.wait_for_connection: - timeout: 120 # Waits for up to 120 seconds \ No newline at end of file + timeout: 120 # Waits for up to 120 seconds diff --git a/molecule/kubevirt/destroy.yml b/molecule/kubevirt/destroy.yml index d95afc05bd..36e84ed015 100644 --- a/molecule/kubevirt/destroy.yml +++ b/molecule/kubevirt/destroy.yml @@ -22,4 +22,4 @@ namespace: "{{ vm.namespace }}" loop: "{{ molecule_yml.platforms }}" loop_control: - loop_var: vm \ No newline at end of file + loop_var: vm diff --git a/molecule/kubevirt/molecule.yml b/molecule/kubevirt/molecule.yml index 950bcf8679..0edfba1508 100644 --- a/molecule/kubevirt/molecule.yml +++ b/molecule/kubevirt/molecule.yml @@ -42,4 +42,4 @@ scenario: - idempotence - side_effect - verify - - destroy \ No newline at end of file + - destroy diff --git a/molecule/kubevirt/tasks/create_vm.yml b/molecule/kubevirt/tasks/create_vm.yml index 0d6fe17c46..840e49cff8 100644 --- a/molecule/kubevirt/tasks/create_vm.yml +++ b/molecule/kubevirt/tasks/create_vm.yml @@ -1,59 +1,59 @@ --- - name: Create VM in KubeVirt - kubernetes.core.k8s: # Uses the k8s module from the kubernetes.core Ansible collection - state: present # Ensures the VM exists. If it doesn't, it will be created. + kubernetes.core.k8s: # Uses the k8s module from the kubernetes.core Ansible collection + state: present # Ensures the VM exists. If it doesn't, it will be created. definition: - apiVersion: kubevirt.io/v1 # KubeVirt's API version - kind: VirtualMachine # The type of Kubernetes resource to create + apiVersion: kubevirt.io/v1 # KubeVirt's API version + kind: VirtualMachine # The type of Kubernetes resource to create metadata: labels: - kubevirt.io/domain: "{{ vm.name }}" # Labels for the VM - name: "{{ vm.name }}" # Name of the VM + kubevirt.io/domain: "{{ vm.name }}" # Labels for the VM + name: "{{ vm.name }}" # Name of the VM namespace: "{{ vm.namespace }}" # Namespace where the VM will be created spec: - running: true # Starts the VM after creation - template: - metadata: - labels: - kubevirt.io/domain: "{{ vm.name }}" # Labels for the VM's template - spec: - domain: - devices: - disks: - - disk: - bus: virtio # Type of disk bus - name: containerdisk # Name of the container disk - - disk: - bus: virtio # Type of disk bus - name: cloudinitdisk # Name of the cloud-init disk - - name: emptydisk # Name of the empty disk - disk: - bus: virtio # Type of disk bus - resources: - requests: - memory: "{{ vm.memory | default('1Gi') }}" # Amount of memory requested for the VM - volumes: - - name: emptydisk - emptyDisk: - capacity: "{{ vm.capacity | default('2Gi') }}" # Capacity of the empty ephemeral disk - - containerDisk: - image: "{{ vm.image }}" # The image used for the container disk - name: containerdisk - - cloudInitNoCloud: # Cloud-init configuration - userData: | # User-data script - #cloud-config - preserve_hostname: true - hostname: "{{ vm.name }}" # Sets the hostname - fqdn: "{{ vm.name }}" # Fully Qualified Domain Name - prefer_fqdn_over_hostname: true - users: - - default - - name: {{ vm.ansible_user }} - lock_passwd: true # Locks the password - ssh_authorized_keys: - - "{{ temporary_ssh_public_key }}" # SSH public key - runcmd: - - [ sh, -c, "hostnamectl set-hostname {{ vm.name }}" ] # Sets the hostname - - [ sudo, yum, install, -y, qemu-guest-agent ] # Installs qemu-guest-agent - - [ sudo, systemctl, start, qemu-guest-agent ] # Starts qemu-guest-agent - name: cloudinitdisk + running: true # Starts the VM after creation + template: + metadata: + labels: + kubevirt.io/domain: "{{ vm.name }}" # Labels for the VM's template + spec: + domain: + devices: + disks: + - disk: + bus: virtio # Type of disk bus + name: containerdisk # Name of the container disk + - disk: + bus: virtio # Type of disk bus + name: cloudinitdisk # Name of the cloud-init disk + - name: emptydisk # Name of the empty disk + disk: + bus: virtio # Type of disk bus + resources: + requests: + memory: "{{ vm.memory | default('1Gi') }}" # Amount of memory requested for the VM + volumes: + - name: emptydisk + emptyDisk: + capacity: "{{ vm.capacity | default('2Gi') }}" # Capacity of the empty ephemeral disk + - containerDisk: + image: "{{ vm.image }}" # The image used for the container disk + name: containerdisk + - cloudInitNoCloud: # Cloud-init configuration + userData: | # User-data script + #cloud-config + preserve_hostname: true + hostname: "{{ vm.name }}" # Sets the hostname + fqdn: "{{ vm.name }}" # Fully Qualified Domain Name + prefer_fqdn_over_hostname: true + users: + - default + - name: {{ vm.ansible_user }} + lock_passwd: true # Locks the password + ssh_authorized_keys: + - "{{ temporary_ssh_public_key }}" # SSH public key + runcmd: + - [ sh, -c, "hostnamectl set-hostname {{ vm.name }}" ] # Sets the hostname + - [ sudo, yum, install, -y, qemu-guest-agent ] # Installs qemu-guest-agent + - [ sudo, systemctl, start, qemu-guest-agent ] # Starts qemu-guest-agent + name: cloudinitdisk diff --git a/molecule/kubevirt/tasks/create_vm_dictionary.yml b/molecule/kubevirt/tasks/create_vm_dictionary.yml index a5d218fc34..fd7dddf51d 100644 --- a/molecule/kubevirt/tasks/create_vm_dictionary.yml +++ b/molecule/kubevirt/tasks/create_vm_dictionary.yml @@ -2,25 +2,25 @@ - name: Create VM dictionary vars: # This variable block is setting the `ssh_service_address` variable. - # It first checks if the service type of the SSH service is 'NodePort'. + # It first checks if the service type of the SSH service is 'NodePort'. # If it is, it retrieves the 'nodePort' from the services results. ssh_service_address: >- - {%- set svc_type = vm.ssh_service.type | default(None) -%} - {%- if svc_type == 'NodePort' -%} + {%- set svc_type = vm.ssh_service.type | default(None) -%} + {%- if svc_type == 'NodePort' -%} {{(node_port_services.results | selectattr('vm.name','==',vm.name) | first)['resources'][0]['spec']['ports'][0]['nodePort'] }} {%- endif -%} - set_fact: + set_fact: # Here, the task is updating the `molecule_systems` dictionary with new VM information. # If `molecule_systems` doesn't exist, it is created as an empty dictionary. # Then it is combined with a new dictionary for the current VM, containing ansible connection details. molecule_systems: >- - {{ + {{ molecule_systems | default({}) | combine({ vm.name: { - 'ansible_user': 'cloud-user', - 'ansible_host': vm.ssh_service.nodeport_host, - 'ansible_ssh_port': ssh_service_address, + 'ansible_user': 'cloud-user', + 'ansible_host': vm.ssh_service.nodeport_host, + 'ansible_ssh_port': ssh_service_address, 'ansible_ssh_private_key_file': tempoary_ssh_key_path } - }) + }) }}