The module lifecycle stageExperimental
The module has requirements for installation

This page holds ready-made scenarios of working with the ansible module. The scenarios do not depend on each other, and each of them carries a manifest and a description of what appears in the status of the run after it is executed. Every field of a run is covered in the user guide.

Before you start

Every example expects the dvp-examples namespace, a virtual machine labeled role: example and a Secret with SSH credentials. Prepare those objects before running the examples.

Preparing a machine and a Secret…

The module does not configure SSH inside a guest OS. A user with sudo rights and their public key reach the machine through a cloud-init script.

apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachine
metadata:
  name: example-vm-01
  namespace: dvp-examples
  labels:
    role: example
spec:
  # Resources, image and disk — from a template of your own.
  cloudInit:
    type: UserData
    userData: |
      #cloud-config
      packages:
        - openssh-server
        - qemu-guest-agent
      users:
        - name: ansible
          lock_passwd: false
          sudo: ALL=(ALL) NOPASSWD:ALL
          ssh_authorized_keys:
            - ssh-ed25519 <SSH_PUBLIC_KEY> ansible@example
      runcmd:
        - systemctl enable --now ssh
        - systemctl enable --now qemu-guest-agent

In the <SSH_PUBLIC_KEY> parameter, specify the public key whose private part goes into the Secret.

Wait for the virtual machine to reach the Running phase and get an address:

d8 k get vm example-vm-01 -n dvp-examples \
  -o jsonpath='{.status.phase}{"  "}{.status.ipAddress}{"\n"}'

The credentials of a run are stored in a Secret in the same namespace. The connection uses either a private key or a password:

d8 k create secret generic ssh-creds -n dvp-examples \
  --from-literal=username=ansible \
  --from-file=ssh-privatekey=./id_ed25519

d8 k create secret generic ssh-creds-password -n dvp-examples \
  --from-literal=username=ansible \
  --from-literal=password=ansible

Connectivity check with an inline playbook

The simplest scenario consists of one machine selected by a label and a playbook with a single task. It is recommended to start with this scenario, because it exercises the whole path from the creation of a run to the recording of the result in its status.

The manifest and the result…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: inline-ping
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Inline
    inline: |
      ---
      - name: Connectivity check
        hosts: all
        tasks:
          - name: Ping
            ansible.builtin.ping:

The run ends in the PlaybookSucceeded phase, and the status.hosts list gains an entry with the name of the machine, its address and the ok: 1 value:

d8 k get ansiblerun inline-ping -n dvp-examples
d8 k get ansiblerun inline-ping -n dvp-examples -o jsonpath='{.status.hosts}' | jq

Playbook from a ConfigMap

A playbook longer than a few dozen lines, or one used by more than a single run, should be stored in a ConfigMap resource. In that case the run points at a key of such a resource instead of carrying the playbook text.

The manifest and the result…

apiVersion: v1
kind: ConfigMap
metadata:
  name: hello-playbook
  namespace: dvp-examples
data:
  playbook.yaml: |
    ---
    - name: Hello from a ConfigMap
      hosts: all
      tasks:
        - name: Print hostname
          ansible.builtin.debug:
            msg: "Hello from {{ inventory_hostname }} ({{ ansible_hostname }})"
---
apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: hello-from-configmap
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: ConfigMap
    configMapRef:
      name: hello-playbook
      # key: playbook.yaml is the default.

The logs of the runner Pod carry a TASK [Print hostname] line with the name of the machine. If the ConfigMap resource does not exist yet, the run waits for it in the Pending phase and does not fail.

Selecting several targets

The targets of a run are given either by a label expression or by a list of names. The two ways cannot be combined, and a manifest with an empty selector is refused by the API server. A run is executed once, so its targets have to be stated explicitly.

Both ways…

The label expression in the example below selects the machines whose role label is in the list and which carry an env label:

spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchExpressions:
          - key: role
            operator: In
            values: [web, db]
          - key: env
            operator: Exists

A list of names cannot be empty. The names are the values of the metadata.name field of machines in the same namespace:

spec:
  target:
    type: VirtualMachines
    virtualMachines:
      names: [example-vm-01, example-vm-02]

If a machine matches the selection but cannot be configured when the run starts, it lands in the status.skippedHosts list with a reason, and the playbook is executed on the remaining machines.

Debugging a run

The runner block changes how the ansible-playbook utility is executed and does not affect which tasks run. The diff parameter shows what exactly changed in a file, the verbosity parameter sets how much the log holds, and the dryRun parameter performs a check run without changes on the hosts.

The manifest and what appears in the logs…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: debug-template-with-diff
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  runner:
    diff: true
    verbosity: 2
  playbook:
    type: Inline
    inline: |
      ---
      - name: Render a file with --diff
        hosts: all
        become: true
        tasks:
          - name: Write a message of the day
            ansible.builtin.copy:
              dest: /etc/motd
              content: |
                Managed by AnsibleRun
                Host: {{ inventory_hostname }}
              mode: '0644'

With the diff: true parameter Ansible prints the change block of the file, and with the verbosity: 2 parameter it adds the details of every task. Level 4 inflates the log so much that finding anything in it becomes hard, so it should be used only when level two was not enough.

Groups and host variables in annotations

Ansible groups and facts about a machine are set on the machine itself, in two annotations that the controller lays out into the inventory file. Thanks to that, a playbook gets the web group and an app_role variable independently of a particular run.

The annotations and a run targeting a group…

apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachine
metadata:
  name: example-vm-01
  namespace: dvp-examples
  labels:
    role: example
  annotations:
    ansible.deckhouse.io/groups: "web,production"
    vars.ansible.deckhouse.io/app_role: "frontend"
    vars.ansible.deckhouse.io/datacenter: "dc1"

The run addresses such a group the way it would address a group from an ordinary inventory file:

spec:
  playbook:
    type: Inline
    inline: |
      ---
      - name: Frontend rollout
        hosts: web
        tasks:
          - name: Show host variables
            ansible.builtin.debug:
              msg: "{{ app_role }} in {{ datacenter }}"

Group and variable names are Ansible identifiers. An unusable name in an annotation is not refused by the d8 k apply command. The run is created and then ends in the Error phase with the InvalidInventoryAnnotations reason, and its message names the machine and the annotation. The full list of refused names is in the user guide.

Installing a package with privilege escalation

Tasks that change the system need the become: true parameter. The password for privilege escalation is taken by the controller from the become-password key of the connection Secret. The package manager is chosen from the facts Ansible gathers.

The manifest and the result…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: install-htop
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Inline
    inline: |
      ---
      - name: Install htop
        hosts: all
        become: true
        tasks:
          - name: Update the apt cache
            ansible.builtin.apt:
              update_cache: true
              cache_valid_time: 3600
            when: ansible_facts.pkg_mgr == 'apt'

          - name: Install the package
            ansible.builtin.package:
              name: htop
              state: present

On the first run the install task reports the changed state, and on the second one the ok state, because the package is already in place. If the user needs a password for sudo, put it into the Secret under the become-password key.

Idempotent configuration of a service

Configuring a service consists of a config template and a handler that restarts the service only when the file has changed. Executing such a run again changes no configuration and restarts nothing.

The manifest and the result…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: render-nginx-config
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Inline
    inline: |
      ---
      - name: Configure nginx
        hosts: all
        become: true
        tasks:
          - name: Write the site config
            ansible.builtin.copy:
              dest: /etc/nginx/conf.d/ansible-demo.conf
              mode: '0644'
              validate: 'nginx -t -c %s'
              content: |
                server {
                  listen 8080 default_server;
                  server_name {{ inventory_hostname }};
                  location / {
                    return 200 "OK from {{ inventory_hostname }}\n";
                  }
                }
            notify: Reload nginx
        handlers:
          - name: Reload nginx
            ansible.builtin.service:
              name: nginx
              state: reloaded

The status.hosts list of the first run holds changed: 1, and of the second one changed: 0. A project with roles and templates is easier to keep in a Git repository than in an inline field.

Collecting facts about machines

An inventory or a capacity review uses a playbook that changes nothing and only gathers facts and prints them. The result stays in the logs of the runner Pod, so it is worth collecting into a log store.

The manifest and the result…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: weekly-audit
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Inline
    inline: |
      ---
      - name: Weekly audit
        hosts: all
        gather_facts: true
        tasks:
          - name: Show the snapshot
            ansible.builtin.debug:
              msg:
                os: "{{ ansible_distribution }} {{ ansible_distribution_version }}"
                kernel: "{{ ansible_kernel }}"
                cores: "{{ ansible_processor_vcpus }}"
                mem_mb: "{{ ansible_memtotal_mb }}"
                uptime_sec: "{{ ansible_uptime_seconds }}"

The run succeeds, the changed counter stays at zero, and the gathered values are printed into the log. The Pod of a successful run is deleted right after it finishes, so configure log collection into a store to keep the log.

Skipped machines in the status

A machine that matched the target but cannot be configured when the run starts does not fail the run. Such a machine lands in the status.skippedHosts list with a reason, and the playbook is executed on the remaining machines.

A status with skipped machines…

The status below belongs to a run with three machines of which only one is running:

status:
  phase: PlaybookSucceeded
  summary: { total: 1, successful: 1, failed: 0, unreachable: 0, skipped: 2 }
  skippedHosts:
    - name: example-vm-02
      phase: Stopped
      reason: TargetNotRunning
    - name: example-vm-03
      phase: Running
      reason: NoAddress

When no machine matched at all, the run ends in the Error phase with the NoEligibleTargets reason, and the status.skippedHosts list holds the reason for every machine. Every skip reason is listed in Troubleshooting.

Encrypted values in Ansible Vault

A project with files encrypted by Ansible Vault is executed unchanged. The password is stored in the connection Secret under the ansible-vault-password key, and the run hands it to the ansible-playbook utility as a password file.

The Secret and the run…

d8 k create secret generic ssh-creds-vault -n dvp-examples \
  --from-literal=username=ansible \
  --from-file=ssh-privatekey=./id_ed25519 \
  --from-literal=ansible-vault-password='vault-pass'

The value is encrypted with the ansible-vault encrypt_string command and goes into the playbook unchanged:

spec:
  connection:
    secretRef:
      name: ssh-creds-vault
  playbook:
    type: Inline
    inline: |
      ---
      - name: Use a vault-encrypted value
        hosts: all
        vars:
          api_token: !vault |
            $ANSIBLE_VAULT;1.1;AES256
            6633...6464
        tasks:
          - name: Write the token
            become: true
            ansible.builtin.copy:
              dest: /etc/example/api-token
              content: "{{ api_token }}"
              mode: '0600'

Without the ansible-vault-password key the run reaches the playbook but fails at the decryption step. The status.failures list then holds the Ansible error about a missing password.

Playbook project from a public Git repository

A real Ansible project is a tree of directories with roles/, group_vars/ and templates. The Git source fetches the repository whole, so roles and files next to the playbook are executed unchanged, and the declared Galaxy dependencies are installed before the playbook starts.

The manifest and the result…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: git-playbook
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Git
    git:
      url: https://github.com/example-org/ansible-demo
      revision: v1.4.0                 # a branch, a tag or a commit SHA
      path: playbooks/site.yml         # playbook.yaml by default

The commit that was actually executed is recorded by the controller in the status.playbookCommit field. If the tree holds a requirements.yml, roles/requirements.yml or collections/requirements.yml file, the declared roles and collections are installed before the playbook starts. Those steps are visible in the logs of the git-clone and galaxy-install init containers.

Private Git repository

The credentials of a repository are stored in a Secret of their own and are mounted only for the fetch step, so the tasks of the playbook never see them. For addresses of the ssh:// form such a Secret is mandatory, and host keys are checked strictly.

Both ways of access…

Access over SSH needs a private key and the contents of the known_hosts file. The host keys can be obtained with the ssh-keyscan command:

d8 k create secret generic git-ssh -n dvp-examples \
  --from-file=ssh-privatekey=./deploy_key \
  --from-file=known_hosts=<(ssh-keyscan github.com)
spec:
  playbook:
    type: Git
    git:
      url: ssh://git@github.com/example-org/ansible-project.git
      revision: main
      path: playbooks/site.yml
      secretRef:
        name: git-ssh

Over HTTPS the token is passed as the password, and the user name depends on the service. GitLab uses the name oauth2, and a job token uses the name gitlab-ci-token:

d8 k create secret generic git-https -n dvp-examples \
  --from-literal=username=oauth2 \
  --from-literal=password=<TOKEN>

In the <TOKEN> parameter, specify a token allowed to read the repository. For a server with a certificate authority of its own, add a ca.crt key to the same Secret. When the fetch fails, the run ends with the GitCloneFailed reason, and the details stay in the logs of the git-clone container.

Run in an additional network

When the SSH server in a guest OS listens only in an additional network of the sdn module, the address of the machine alone is not enough, because there is no route from the pod network into another L2 domain. The connection.network field puts the run itself into that network.

The machine, the run, and what the network needs…

In the example below the machine is attached to the additional network alongside the main one:

apiVersion: virtualization.deckhouse.io/v1alpha2
kind: VirtualMachine
metadata:
  name: vm-in-vlan
  namespace: dvp-examples
  labels:
    role: vlan-example
spec:
  networks:
    - type: Main
    - type: ClusterNetwork
      name: vlan-64

The network block of the run mirrors an entry of the spec.networks list of the machine, so its values are carried over from the manifest of the machine:

spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: vlan-example
  connection:
    secretRef:
      name: ssh-creds
    network:
      type: ClusterNetwork      # Main (the default) | Network | ClusterNetwork
      name: vlan-64

The network has to have an address pool, because both the machine and the runner Pod take an address from it. Without a pool the run ends with the NetworkWithoutIPAM reason, and an unknown network name leads to the NetworkNotFound reason. A machine without an address in that network is skipped with the NoAddressInNetwork reason. The guest OS needs a DHCP client on that interface, otherwise the platform reports an address the machine does not answer on.

Troubleshooting a failed run

A failed run is of one of two kinds, and they are investigated differently. The PlaybookFailed phase means the playbook ran and its tasks failed, while the Error phase means the tasks were never reached. Errors the playbook handled itself leave the run successful.

Where to look in each case…

Failed tasks are listed in the status of the run, up to 20 entries:

status:
  phase: PlaybookFailed
  failures:
    - host: example-vm-01
      task: This one fails
      message: "Expected failure"

The Pod of a failed run is kept until the object is deleted, so the full output is available in its logs:

d8 k logs -n dvp-examples -l ansible.deckhouse.io/owner=<RUN_NAME>

The Error phase means there is no Ansible output at all. That happens when a dependency was missing, no target matched, or the project could not be fetched from Git. The reason sits in the Completed condition, and working through it is covered in Troubleshooting.

Errors handled by a rescue block or by the ignore_errors parameter land in the rescued and ignored counters, and the run stays successful. How to read those counters is covered in Run results.

Scheduled run

A schedule creates runs by cron the way a CronJob resource creates Job objects. A run is named after its schedule plus the time of the tick.

The manifest and how to control it…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRunSchedule
metadata:
  name: nightly-ping
  namespace: dvp-examples
spec:
  schedule: "0 4 * * *"
  timeZone: "Europe/Moscow"          # UTC by default
  template:
    spec:
      target:
        type: VirtualMachines
        virtualMachines:
          selector:
            matchLabels:
              role: example
      connection:
        secretRef:
          name: ssh-creds
      playbook:
        type: Inline
        inline: |
          ---
          - name: Nightly check
            hosts: all
            tasks:
              - name: Ping
                ansible.builtin.ping:

The runs created by a schedule are selected by a label:

d8 k get ansibleruns -n dvp-examples \
  -l ansible.deckhouse.io/schedule-owner=nightly-ping

A schedule, its run template included, is editable while it works, and the changes apply from the next tick. The suspend field pauses the schedule:

d8 k patch ansiblerunschedules nightly-ping -n dvp-examples \
  --type=merge -p '{"spec":{"suspend":true}}'

Concurrency and history of a schedule

Four fields of a schedule decide what happens when a run does not finish before the next tick, how late a missed tick still makes sense, and how many runs to keep in the namespace.

The manifest and what each field does…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRunSchedule
metadata:
  name: nightly-baseline
  namespace: dvp-examples
spec:
  schedule: "0 4 * * *"
  timeZone: "Europe/Moscow"
  concurrencyPolicy: Forbid          # Forbid (the default) | Allow | Replace
  startingDeadlineSeconds: 600
  successfulRunsHistoryLimit: 3
  failedRunsHistoryLimit: 1
  template:
    spec:
      target:
        type: VirtualMachines
        virtualMachines:
          selector:
            matchLabels:
              role: example
      connection:
        secretRef:
          name: ssh-creds
      playbook:
        type: Inline
        inline: |
          ---
          - name: Baseline
            hosts: all
            tasks:
              - name: Ping
                ansible.builtin.ping:

The Forbid value skips a tick while the previous run is still active. That behaviour suits work with the same machines, since a parallel run would skip them with the TargetBusy reason anyway. The Allow value fits when the runs go to different machines, and the Replace value fits when the latest state matters more than the completion of the previous attempt.

The startingDeadlineSeconds field drops a tick missed by longer than the given time, and missed ticks are never replayed afterwards. The history limits delete the surplus runs, and deleting a schedule deletes every run it created.

Hosts named by address

The addresses of some hosts are unknown to the platform. Such hosts are machines whose address is configured inside a guest OS by hand or handed out by an external DHCP server, as well as physical servers and network appliances. Those hosts are named by address in the manifest of the run.

Targets of type Hosts are available in commercial editions only. In the Community Edition a run works with virtual machines of the platform, and a manifest with targets of type Hosts is refused by the cluster when it is created.

The list of hosts, their variables and groups…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: patch-appliances
  namespace: dvp-examples
spec:
  target:
    type: Hosts
    hosts:
      - address: 192.168.55.10
        groups: [web]
        vars:
          - name: app_role
            value: frontend
      - address: db.example.com
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Inline
    inline: |
      ---
      - name: Baseline
        hosts: all
        tasks:
          - name: Ping
            ansible.builtin.ping:

Such a host has no VirtualMachine resource and therefore no annotations, so its variables and groups are declared in the list itself and are written the way the variables of a run are. There is no phase to wait for, and an address is not checked for another active run, so two runs can work on one address at the same time. The status.hosts[].name field holds the address itself, and the status.skippedHosts list stays empty.

A host inside an isolated network also needs the connection.network field, because the runner Pod does not put the address itself into that network.

Variables of a run

Variables hand values to a playbook and make changes to the playbook unnecessary, so one project serves several environments. A value written in the manifest can be a string, a number, a list or a mapping.

The manifest and the result…

apiVersion: ansible.deckhouse.io/v1alpha1
kind: AnsibleRun
metadata:
  name: deploy-1-4-2
  namespace: dvp-examples
spec:
  target:
    type: VirtualMachines
    virtualMachines:
      selector:
        matchLabels:
          role: example
  connection:
    secretRef:
      name: ssh-creds
  playbook:
    type: Inline
    vars:
      - name: app_version
        value: "1.4.2"                 # quoted, therefore a string
      - name: packages
        value: [nginx, curl]
      - name: limits
        value:
          cpu: 2
          mem: 4Gi
    inline: |
      ---
      - name: Deploy
        hosts: all
        tasks:
          - name: Show the version
            ansible.builtin.debug:
              msg: "deploying {{ app_version }}"

The log holds the deploying 1.4.2 line. A version has to be quoted, because unquoted YAML turns 1.10 into the number 1.1. The next rollout needs another object, because the spec block cannot be changed and a variable of an existing run cannot be edited.

Values from a Secret and a ConfigMap

A password or a ready-made set of values does not have to be copied into the manifest of a run. The valueFrom field takes the value from a key of a Secret or a ConfigMap, and the varsFiles field attaches a whole YAML document with the types of the values intact.

Both ways, and what happens when a source is missing…

spec:
  playbook:
    type: Inline
    vars:
      - name: db_password
        valueFrom:
          secretKeyRef:
            name: app-secrets
            key: db-password
    varsFiles:
      - configMapRef:
          name: app-config           # vars.yaml is the default key
      - secretRef:
          name: app-secrets
          key: prod.yaml
          optional: true
    inline: |
      ---
      - name: Deploy
        hosts: all
        tasks:
          - name: Use the password
            ansible.builtin.debug:
              msg: "password length: {{ db_password | length }}"

A value from a single key always arrives as a string, so a playbook that needs a list parses it with the from_yaml filter. A set of values with structure should be placed in a file, where 1.10 stays a string and a list stays a list.

A missing object holds the run in the Pending phase with the VarsSourceNotFound reason until the object appears, while a missing key ends the run with an error. The optional: true parameter lets the run proceed without those variables.

Variable precedence

One variable name can be set in four places: an annotation on the machine, the playbook, a varsFiles document and the vars field of a run. The variables of a run take precedence over every value the playbook and its project set.

One name in three places…

# VirtualMachine
metadata:
  annotations:
    vars.ansible.deckhouse.io/app_version: "from-annotation"    # level 8
---
# AnsibleRun
spec:
  playbook:
    type: Inline
    vars:
      - name: app_version
        value: "from-the-run"                                   # level 22
    inline: |
      ---
      - hosts: all
        vars:
          app_version: "from-the-playbook"                      # level 12
        tasks:
          - ansible.builtin.debug:
              msg: "{{ app_version }}"

The task prints the value from-the-run. If the vars field is removed from the run, the task prints the value from-the-playbook rather than the value of the annotation, because a vars block inside a play ranks higher.

The full table of precedence levels is in the user guide.

Refused variable names

Some variable names a run does not accept. Those are the names the module sets itself and the Ansible magic variables. The former already have their own fields in the API, and the values of the latter Ansible fills in itself.

What exactly is refused, and how the refusal looks…

Variable Where to set it instead
ansible_host hosts[].address, or the address of the machine from its status
ansible_user The username key of the connection Secret
ansible_password, ansible_ssh_pass The password key
ansible_ssh_private_key_file The ssh-privatekey key
ansible_become_password, ansible_become_pass The become-password key
ansible_connection spec.connection.type

A name that is not an Ansible identifier is refused by the API server in response to the d8 k apply command. A name from the table above, or a magic variable, ends the run before a Pod is created:

status:
  phase: Error
  message: 'The variable "omit" in spec.playbook.vars[0] cannot be used: it shadows an Ansible magic variable, which Ansible fills in itself.'
  conditions:
    - type: Completed
      status: "False"
      reason: InvalidVariables

Every other variable with the ansible_ prefix can be set, for example ansible_port, ansible_python_interpreter or ansible_become_user. An annotation on a machine follows a stricter rule and may set no variable with the ansible_ prefix except ansible_port, because the user who edits the machine is not the user who runs the playbook.

The names inside a varsFiles document are not checked, because such a file has the same level of trust as the playbook.