Skip to main content
RunBook Academy

AnsibleIII · Installing and Designing the ControllerChoosing what to install

The support matrix, and which Python is which

Foundation⏱ ~18 minbashpython3

What you'll learn

  • Read the ansible-core support matrix and state both Python requirements separately
  • Answer "can this controller manage that host" from the table rather than by trial
  • Explain why a distribution package of Ansible usually lags the matrix
  • Diagnose a single-host failure in a mixed-age fleet without blaming the controller

Prerequisites

Verified against ansible-core 2.21.x · ansible (community package) 14.x · Python (controller) 3.12+ · ansible-lint 26.x · Molecule 26.x · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · RHEL / Rocky / AlmaLinux 9.x · 2026-08-11

Not yet marked complete on this device.

Ansible has two Python requirements, and almost every question about version compatibility becomes easy the moment you stop treating them as one.

The controller runs ansible-playbook. It needs a Python that the installed ansible-core supports, and that requirement is strict — pip will refuse the install outright if it is not met.

The managed node executes the module payload the controller ships it. It needs a Python in a much wider, much older range, because a fleet contains machines built over a decade.

These are different numbers. They move on different schedules. They fail in different ways.

The matrix

For ansible-core releases around the one this course targets:

ansible-coreController PythonManaged-node Python
2.213.12 – 3.143.9 – 3.14
2.203.12 – 3.143.9 – 3.14
2.193.11 – 3.133.8 – 3.13
2.183.11 – 3.133.8 – 3.13

Read it as two independent questions:

  1. Will ansible-core 2.21 install and run on this controller? Look at column two.
  2. Can a controller running 2.21 manage this host? Look at column three, against whatever Python the host has.

The controller requirement is not a recommendation. It is encoded in the package metadata, and pip enforces it:

Read-only / Safethe requirement, straight from the installed package
$ python -c "import importlib.metadata as m; d = m.metadata('ansible-core'); print(d['Version'], d['Requires-Python'])"
2.21.3 >=3.12

The upper bound is not expressed as a metadata constraint — it is the range upstream tests and supports, published in the matrix and reflected in the package classifiers:

Read-only / Safethe tested interpreters
$ python -c "import importlib.metadata as m; [print(c) for c in m.metadata('ansible-core').get_all('Classifier') if 'Python :: 3.' in c]"
Programming Language :: Python :: 3.12
Programming Language :: Python :: 3.13
Programming Language :: Python :: 3.14

Why distribution packages lag

An estate that installs Ansible with apt install ansible gets whatever its release froze. That is the whole design of a stable distribution and it is not a defect — but it means the version you get is a property of your operating system release date, not of what Ansible currently ships.

Read-only / Safewhat the distribution actually offers
$ apt-cache policy ansible ansible-core
ansible:
Installed: 13.1.0+dfsg-1ubuntu1
Candidate: 13.1.0+dfsg-1ubuntu1
Version table:
*** 13.1.0+dfsg-1ubuntu1 500
      500 http://archive.example.com/ubuntu resolute/universe amd64 Packages
      100 /var/lib/dpkg/status
ansible-core:
Installed: 2.20.1-1
Candidate: 2.20.1-1
Version table:
*** 2.20.1-1 500
      500 http://archive.example.com/ubuntu resolute/universe amd64 Packages
      100 /var/lib/dpkg/status

That is a real distribution offering community package 13.1.0, which is ansible-core 2.20.1 — one minor release behind the 2.21.x this course targets. There is nothing wrong with 2.20. But if your repository declares 2.21 and your controller is a distribution package, the declaration is fiction, and every claim you have verified was verified against something else.

The gap grows with the age of the distribution release. On a long-term-support base two years into its life, the packaged ansible-core can be three or four minor releases behind, with deprecations already removed upstream that your playbooks still rely on — or, more awkwardly, deprecation warnings you have never seen because your version does not emit them yet.

Answering the real question from the table

The question that comes up in practice is not “which Python do I have”. It is “can this controller manage that host” — usually asked about the oldest machine in the estate, ten minutes before a change window.

Work it in two steps.

Step one: what does the controller run?

Read-only / Safecontroller side
$ ansible --version
ansible [core 2.21.3]
config file = /home/ops/estate/ansible.cfg
configured module search path = ['/home/ops/.ansible/plugins/modules']
ansible python module location = /home/ops/.venvs/estate/lib/python3.12/site-packages/ansible
executable location = /home/ops/.venvs/estate/bin/ansible
python version = 3.12.7
jinja version = 3.1.6

Illustrative output

Core 2.21, controller Python 3.12. Row one of the matrix, and it is within 3.12 – 3.14. The controller is fine.

Step two: what does the managed node run? Ask the host, not the matrix. If you can already reach it with Ansible, the fact is gathered for you; if you cannot, ssh and python3 --version answers it.

The matrix then says: 3.9 through 3.14 is supported by core 2.21. A RHEL 9 host with the platform Python 3.9 is inside the range. A long-abandoned box with Python 3.6 is not, and no amount of controller upgrading will change that — upgrading the controller moves the managed-node floor up, not down.

The failure that gets misdiagnosed

A run against ninety hosts succeeds on eighty-nine and fails on one, with a Python traceback in the module output rather than a clean Ansible error message.

The instinct is to suspect the controller — it is the machine that changed, it is the machine you can log into, and the error mentions Python. That instinct is wrong here, and following it costs an hour.

The controller’s Python is a single fact shared by all ninety hosts. If it were the problem, all ninety would have failed. A failure on one host in an otherwise healthy run is a property of that host, and on a mixed-age fleet the property is very often its interpreter: an old platform Python below the supported floor, or a host where python3 resolves to something surprising.

The diagnosis is a single fact gathered from the failing host:

Read-only / Safewhat interpreter did Ansible actually choose there?
$ ansible db07.example.com -m setup -a 'filter=ansible_python' | head -20
db07.example.com | SUCCESS => {
  "ansible_facts": {
      "ansible_python": {
          "executable": "/usr/bin/python3.8",
          "has_sslcontext": true,
          "type": "cpython",
          "version": {
              "major": 3,
              "minor": 8,
              "micro": 18,
              "releaselevel": "final",
              "serial": 0
          },
          "version_info": [3, 8, 18, "final", 0]
      }
  },
  "changed": false
}

Illustrative output

Python 3.8, against a core 2.21 floor of 3.9. The host is below the matrix. The controller is innocent, the playbook is innocent, and the fix is on the managed node — which is a different team’s ticket and a different change window, so knowing this in ten minutes rather than two hours matters.

Knowledge check

Knowledge check · 4 questions

  1. Q1. A controller runs ansible-core 2.21 on Python 3.13. A managed node runs Python 3.9. What does the support matrix say?

  2. Q2. Upgrading a controller from ansible-core 2.19 to 2.21 can make some previously managed hosts unmanageable.

  3. Q3. A run against 90 hosts succeeds on 89 and fails on one with a Python traceback in the module output. What is the most likely cause?

  4. Q4. Which statements about a distribution-packaged Ansible are accurate? Select all that apply.

Passing score: 75%. Answers are checked in this browser.