Bootstrapping Newly Provisioned RHEL 9 Hosts
Fifty RHEL 9 servers are provisioned nightly from a template and added to inventory. Twelve fail ansible all -m ping with UNREACHABLE and host-key verification errors, five fail with "python3 not found" at /usr/bin/python, and every root-requiring task fails with "Missing sudo password" for the automation account svc_ansible. On the hosts that work, ansible all -m command -a 'id' -b blocks waiting for input when run from CI. The hosts must not be reimaged, the only access is the svc_ansible SSH key, and the security team refuses to give that account a shell password.
1. How do you bring the twelve unreachable hosts into a working state without weakening host-key checking for every playbook on the control node?
Read the worked answer
Do not set host_key_checking = False in the shared ansible.cfg: on a shared control node that removes pinning for every play and hides a genuine man-in-the-middle. Two scoped options, in order of preference:
- Pre-seed known_hosts in a bootstrap play, then use default strict checking. A task on the control node runs
ssh-keyscan -t ed25519 <host>and merges the key into the automation user'sknown_hosts(with a uniqueness check so re-runs do not duplicate entries). Strict checking stays on; the first-seen key is pinned. - If pre-seeding is impractical, scope
accept-newto the bootstrap group only:
# group_vars/bootstrap.yml
ansible_ssh_common_args: '-o StrictHostKeyChecking=accept-new'
accept-new pins on first contact, unlike StrictHostKeyChecking=no, which disables verification entirely. Run the bootstrap with --limit bootstrap so no other host inherits the looser setting, and add ansible.builtin.wait_for_connection after the first connect so a slow template does not surface as a random task failure later. Record the accepted fingerprints in inventory metadata so a changed key on a rebuild is visible.
2. How do you install Python on the five hosts that have no interpreter, and what must be disabled for that to work?
Read the worked answer
Use ansible.builtin.raw, the only core module that does not require a remote Python interpreter.
ansible 'pythonless' -i inventory/prod -b -m ansible.builtin.raw \
-a 'dnf -y install python3'
Key details:
rawexecutes over SSH without Python, butbecomestill goes throughsudo, so the credential problem must already be solved (see the sudo task) or the ad hoc call needs-K.- In a playbook, set
gather_facts: falseat play level, otherwise the implicitsetuptask fails beforerawever runs. Follow the raw task withansible.builtin.setuporwait_for_connectionto collect facts once Python exists. - On RHEL 8/9,
python3may land at/usr/libexec/platform-python. If a non-standard path is used, setansible_python_interpreteringroup_varsfor that group and remove it once the standard path is valid. - If repos are not yet configured on the template, install from the ISO or use
ansible.builtin.scriptto push a bootstrap shell script, still withgather_facts: false.
Then confirm with ansible all -m ansible.builtin.ping before running any module-based play, so later failures are not misattributed to the new task.
3. The security team will not give `svc_ansible` a password. What is the correct way to satisfy the become requirement, and what alternative exists if they change their mind?
Read the worked answer
Preferred: a sudoers drop-in granting NOPASSWD to svc_ansible, scoped as tightly as the automation allows.
# /etc/sudoers.d/svc_ansible (mode 0440, validated with visudo -c)
svc_ansible ALL=(root) NOPASSWD: ALL
or, if tasks are known, an explicit command list instead of ALL. This removes the prompt entirely so CI can run unattended, and it decouples automation from a stored secret. Verify with ansible all -m command -a 'id -u' -b, which must return 0 with no prompt; also confirm requiretty is not set, since that breaks non-interactive sudo on older policy.
Alternative if a password must exist: store it vaulted, never in plain group_vars.
# group_vars/all/vault.yml (ansible-vault encrypted)
vault_become_pass: <secret>
# group_vars/all/vars.yml
ansible_become_password: "{{ vault_become_pass }}"
The vault key then has to be available on the control node (--vault-password-file with mode 0600), which is a second secret to rotate. Never rely on -K inside CI: it either hangs or requires the password in the job definition. Also add become: true at play level rather than per task, so a single missing flag cannot silently run a task unprivileged.