Actually Change RHEL With Ansible
This lab runs on a real RHEL-family system
AlmaLinux 9 is running inside the VM. systemd runs as PID 1, SELinux is Enforcing, and firewalld is alive. Playbooks really change the system, and the grader reads the result directly from the system.
The earlier Ansible course ran in a Pod and went only as far as syntax checking. Here, if you do not configure it, it really gets blocked.
It takes about 2 minutes to start.
Goal
You go once around what the RHCE exam really asks. Users, services, SELinux, the firewall, templates, and idempotence.
Why it matters
The RHCE (EX294) does not ask "do you know Ansible syntax". It asks whether you can bring a system into the desired state. So most of the points lost come not from syntax but from things like these.
- Writing only
state: startedand leaving outenabled: true, so the service does not start after a reboot - Opening firewalld only with
permanentso it is not open now, or only withimmediateso it disappears after a restart - Turning on an SELinux boolean without
persistent, so it reverts at reboot - Using
command/shellunconditionally so every run shows changed
The last one is especially important. A playbook that is not idempotent is a playbook that changes something every time you run it. Then you cannot know "what did this run change", and the handler restarts the service every time, causing unnecessary interruptions.
Steps
- In
/root/rhce/inventory.ini, put this host in a[webservers]group, and also create/root/rhce/ansible.cfg.ansible webservers -m pingmust succeed. - With
/root/rhce/users.yml, create awebadmingroup and usersaliceandbob(shell/bin/bash, supplementary groupwebadmin). Use a loop. - With
/root/rhce/service.yml, installhttpdand satisfy both started and enabled.curl http://127.0.0.1/must be 200. - With
/root/rhce/selinux.yml, turn on thehttpd_can_network_connectboolean permanently and attach thehttpd_sys_content_tcontext to/srv/web. - With
/root/rhce/firewall.yml, open thehttpservice in the firewall. It must satisfy both runtime and permanent. - Make a page that includes the hostname with
/root/rhce/templates/index.html.j2and deploy it with/root/rhce/template.yml. Use a handler that restarts httpd only when something changes. - Create
/root/rhce/site.yml, which calls all of the above. Then deliberately disturb the state (stop the service, delete a file, delete an account), run it twice, and write thechangedcounts to/root/rhce/idempotent.txtaschanged_first=·changed_second=. The first must be greater than 0 and the second must be 0. If you measure without disturbing, it is 0 from the first run, so you cannot tell "it became 0 because it fixed things" from "it did nothing to begin with". - In
/root/rhce/report.md, write three lines,selinux=Enforcing,idempotent=yes, andplaybooks=<개수>(playbooks= followed by the number of playbooks), along with an explanation.
Notes
- The SELinux modules are
ansible.posix.sebooleanandcommunity.general.sefcontext. You need both collections, and they must be versions that match your ansible-core version — if they do not, it only prints a warning and dies withcouldn't resolve module/action, and that wording makes you suspect a typo. After defining the context, you must useansible.builtin.command: restorecon -R /srv/webtogether withchanged_whenso that idempotence is not broken. - The firewalld module has
permanentandimmediateseparately. Both must be true to get the state "open now and open after a restart". - You write facts as
ansible_facts['hostname']or{{ ansible_hostname }}. - You count the
changednumber withansible-playbook site.yml | grep -oE 'changed=[0-9]+'. - Common mistake 1: using
command/shellunconditionally. That task is always changed. Give it a condition withcreates:orchanged_when:. - Common mistake 2: writing only
state: startedand leaving outenabled: true. It runs now, but does not start after a reboot. - Common mistake 3: leaving out
persistent: trueon an SELinux boolean. It is turned on now, but reverts at reboot. - Common mistake 4: two tasks managing the same file with different content. They undo each other and both are changed forever, and each task looks fine on its own, so it is the hardest to find. A file should have one owner, and if it is for bootstrapping, state "only when absent" explicitly with
force: false.
Inventory and configuration file
In /root/rhce/inventory.ini, put this host in a [webservers] group, and also create /root/rhce/ansible.cfg. ansible webservers -m ping must succeed.
You take this VM itself as the target. If you use ansible_connection=local, it works right away without SSH. If you put the inventory path in ansible.cfg, you do not have to give -i every time.
Creating users and groups
With /root/rhce/users.yml, create a webadmin group and users alice and bob (shell /bin/bash, supplementary group webadmin). Use a loop.
Run ansible.builtin.group first, and then ansible.builtin.user with a loop. If you create the users while the group does not exist, it fails.
A service must satisfy two things
With /root/rhce/service.yml, install httpd and satisfy both started and enabled. curl http://127.0.0.1/ must be 200.
state: started is for now and enabled: true is for after a reboot. They are different stories and are what people most often leave out in the exam.
SELinux is on and really blocks
With /root/rhce/selinux.yml, turn on the httpd_can_network_connect boolean permanently and attach the httpd_sys_content_t context to /srv/web.
For the boolean, use persistent: true with ansible.posix.seboolean, and for the file context, use restorecon after community.general.sefcontext.
Runtime and permanent are different
With /root/rhce/firewall.yml, open the http service in the firewall. It must satisfy both runtime and permanent.
permanent: true applies to the configuration file and immediate: true applies to the firewall running now. You have to give both to be complete.
Restart only when something changes
Make a page that includes the hostname with /root/rhce/templates/index.html.j2 and deploy it with /root/rhce/template.yml. Use a handler that restarts httpd only when something changes.
Attach notify to the template task and put the restart in the handlers section. The handler runs only when that task is changed.
The second run must be quiet
Create /root/rhce/site.yml, which calls all of the above. Then deliberately disturb the state (stop the service, delete a file, delete an account), run it twice, and write the changed counts to /root/rhce/idempotent.txt as changed_first=·changed_second=.
The first must be greater than 0 and the second must be 0. If you measure without disturbing, it is 0 from the first run, so you cannot tell "it became 0 because it fixed things" from "it did nothing to begin with".
In site.yml, call the earlier playbooks with import_playbook and run it twice. If the second run's changed is not 0, find that task.
What you learned
In /root/rhce/report.md, write three lines, selinux=Enforcing, idempotent=yes, and playbooks=<개수> (playbooks= followed by the number of playbooks), along with an explanation.
Along with the three lines selinux=, idempotent=, and playbooks=, summarize the places where points are lost in the exam.