Core Ansible Components and How They Fit Together
The moving parts
Ansible is agentless: a control node runs the CLI tools and reaches managed nodes over SSH, copying a small program (a module) to each host and executing it. Nothing persists on the managed node between runs, so there is no daemon to manage and no certificate to enroll.
Know the vocabulary that questions are built on. An inventory lists managed hosts, optionally in INI or YAML form, with groups and group children. A module is the unit of work (ansible.builtin.dnf, ansible.builtin.copy). A task calls exactly one module with arguments. A play binds a set of tasks to a host pattern and to play-level keywords such as become or vars. A playbook is an ordered list of plays. A role is a reusable bundle of tasks, handlers, defaults, vars, templates and files in a fixed directory layout. A collection is the namespaced distribution unit (ansible.posix, redhat.rhel_system_roles) that ships modules, roles and plugins together.
Traps
command is the default module for ad hoc work, and it does not invoke a shell, so pipes, redirects and $VAR expansion silently fail; use shell only when you truly need shell features, and add changed_when or creates to keep it idempotent. Short module names (dnf) resolve only if a collection providing them is in the configured search path; fully qualified collection names always resolve. Tasks run in order within a host, and plays run in sequence, but hosts advance independently - a task on host B never waits for host A.
Rule of thumb
If the goal is a state on the system, use a purpose-built module (dnf, service, user, file, template). Reach for command only when no module exists, and pair it with creates: or changed_when: false.