Self-hosted service
geerlingguy/ansible-role-kubernetes avatar
geerlingguy/ansible-role-kubernetes

ansible-role-kubernetes and the case for configuring kubeadm through a file

Ansible Role - Kubernetes

627 stars280 forksJinjaMIT

At a glance

What is it?
This Ansible role installs Kubernetes on Linux with kubeadm, and its most consequential choice is that kubeadm init is driven by a config file rather than by command line flags, with the deprecated flag variables still present for compatibility. Its cost is a large variable surface and a release tag that is years behind the code.
Who is it for?
ansible-role-kubernetes suits a team that already runs Ansible and wants Kubernetes nodes built reproducibly, because the role pins the package version, drives kubeadm through a documented config file, and keeps the command line variables only for backward compatibility. It does not suit a team that wants a hosted control plane, and it does not suit someone who expects the role to manage the cluster after bootstrap, since it installs rather than operates.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 45 days ago.
What is it written in?
Mainly Jinja, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One role, two roles, depending on the machine

This is an Ansible role that installs Kubernetes on Linux, and the first thing it asks you to decide is what kind of machine each target is.

The variable that does it is kubernetes_role:

yaml
kubernetes_role: control_plane

Whether the particular server will serve as a Kubernetes control_plane, which is the default, or as a node. The control plane will have kubeadm init run on it to initialize the entire K8s control plane, while nodes will have kubeadm join run on them to join the control plane. So one role covers both the bootstrap of a cluster and the addition of members to it, and the difference is a single string.

The prerequisites are a compatible container runtime, with the recommended role for CRI installation given as geerlingguy.containerd. That recommendation is a real constraint on how you use the role, since a CRI has to exist before Kubernetes can be installed on top of it, and this role does not install one for you.

The packages themselves are configured as a list:

yaml
kubernetes_packages:
  - name: kubelet
    state: present
  - name: kubectl
    state: present
  - name: kubeadm
    state: present
  - name: kubernetes-cni
    state: present

You can either provide a list of package names, or set name and state to have more control over whether the package is present, absent, latest, and so on. The version is pinned separately, twice, for two different distributions:

yaml
kubernetes_version: '1.32'
kubernetes_version_rhel_package: '1.32'

The plain kubernetes_version is used to pin an apt package version on Debian, and as the Kubernetes version passed into the kubeadm init command. The rhel_package variable must be a specific Kubernetes release and is used to pin the version on Red Hat and CentOS servers.

The config file path is the recommended route

The role's central design decision is the one that shapes everything else, and it is stated as recommended.

With this role, kubeadm init will be run with --config pointing at a file, and the path is:

yaml
kubernetes_kubeadm_kubelet_config_file_path: '/etc/kubernetes/kubeadm-kubelet-config.yaml'

The path is the value of the file argument, and if the directory does not exist the role will create it.

The role then parses a set of variables as options for that file, and the documentation links to the kubelet integration and the kubeadm config file reference for the syntax. The skeleton, meaning the apiVersion and kind fields, is created by the role, so you should not define them within the variables. The template that does it is named in the documentation as templates/kubeadm-kubelet-config.j2.

There are four configuration sections, and each one is a variable tree that maps onto a named part of the kubeadm config. The init configuration:

yaml
kubernetes_config_init_configuration:
  localAPIEndpoint:
    advertiseAddress: "{{ kubernetes_apiserver_advertise_address | default(ansible_default_ipv4.address, true) }}"

The cluster configuration:

yaml
kubernetes_config_cluster_configuration:
  networking:
    podSubnet: "{{ kubernetes_pod_network.cidr }}"
  kubernetesVersion: "{{ kubernetes_version_kubeadm }}"

The kubelet configuration:

yaml
kubernetes_config_kubelet_configuration:
  cgroupDriver: systemd

And the kube-proxy configuration, which is empty by default:

yaml
kubernetes_config_kube_proxy_configuration: {}

The kubelet section is the one with a dependency worth noticing. The documentation says the recommended cgroupDriver depends on your container runtime, and that when using the role with Docker instead of containerd this value should be changed to cgroupfs. Getting that wrong produces a cluster whose nodes come up but fail to run containers correctly, which is a miserable thing to debug.

The deprecated flags are still there, and that is the point

Alongside the recommended config file route, the role keeps the older command line variables, and it is honest about their status.

The kubelet extra arguments are:

yaml
kubernetes_kubelet_extra_args: ""
kubernetes_kubelet_extra_args_config_file: /etc/default/kubelet

The description gives two concrete uses, allowing kubelet to start up even if swap is enabled on the server with the fail-swap-on flag set to false, and specifying the node IP advertised by kubelet with a node-ip value templated from the host. Then the note: this option is deprecated, please use kubernetes_config_kubelet_configuration instead. Elsewhere the documentation says the same about the kubelet configuration approach, that it is the recommended way and that most command-line options are deprecated.

The other two deprecated variables are for the bootstrap commands:

yaml
kubernetes_kubeadm_init_extra_opts: ""

for extra arguments to kubeadm init during control plane initialization, with the example of specifying extra Subject Alternative Names for the API server certificate, and:

yaml
kubernetes_join_command_extra_opts: ""

for extra arguments passed to the generated kubeadm join command during node initialization, with the example of ignoring certain preflight errors like swap being enabled.

Keeping these is a deliberate choice for existing users rather than an oversight, and the documentation is explicit that the config file variables are what new setups should use.

The CNI choice is a one-way door in this role

Pod networking is the choice in this role that has the clearest warning attached to it, and the warning is worth reading before anything else.

The pod network is configured as a single block with the three supported options and their ranges:

yaml
kubernetes_pod_network:
  # Flannel CNI.
  cni: 'flannel'
  cidr: '10.244.0.0/16'
  #
  # Calico CNI.
  # cni: 'calico'
  # cidr: '192.168.0.0/16'
  #
  # Weave CNI.
  # cni: 'weave'
  # cidr: '192.168.0.0/16'

Flannel is the default and the only one enabled out of the box, with Calico and Weave present as commented alternatives. The instruction is to choose only one for your cluster, and then the reason: converting between them is not done automatically and could result in broken networking, so if you need to switch from one to another it should be done outside of this role.

That is a real limitation and a sensible one to state. A CNI migration touches every node's on-disk state, so a role that silently rewrote the configuration would be doing something dangerous, and the documentation chooses to tell you the migration is not its job.

There is one more cluster-level setting that decides whether the control plane runs workloads:

yaml
kubernetes_allow_pods_on_control_plane: true

This controls whether the taint that denies pods from being deployed to the Kubernetes control plane is removed. For a single-node cluster the documentation says this should definitely be true, and for anything else you set it to false if you want a dedicated control plane that does not run other pods.

The repository structure, and the release tag that lags the code

The repository is laid out the way Ansible roles are, and that structure is worth naming because it tells you where to look.

There is a defaults/ directory holding the variables and their defaults, vars/ for other variables, tasks/ for the task files, handlers/ for handlers, templates/ for the Jinja templates including the one that builds the kubeadm config, meta/ for role metadata, molecule/ for the test scenarios, and a vars/ area with the apt repository settings. There is a .ansible-lint configuration and a .yamllint configuration, both of which say the project is linted rather than merely written, and a CI workflow that runs on every change.

The primary language recorded for the repository is Jinja, which is a reasonable marker for a role whose main output is templated configuration files.

The release history is where the picture gets complicated. The only listed release is 4.0.2, dated 2020-03-19, which means the published role in Ansible Galaxy is many years behind the code in the repository. The last push to the repository was on 2026-09-08, so the code is being worked on.

The apt repository settings are also worth reading, because they are how the packages get installed at all:

yaml
kubernetes_apt_release_channel: "stable"
kubernetes_apt_repository: "https://pkgs.k8s.io/core:/{{ kubernetes_apt_release_channel }}:/v{{ kubernetes_version }}/deb/"

That is the official Kubernetes package repository, addressed by release channel and version, which is the correct source for the pinned packages the role installs.

Where this role is the wrong choice

The first case is a team that does not already run Ansible. The role assumes an existing playbook structure, an inventory and a set of group variables, and it is a component rather than a project. Adopting it to install one cluster means adopting Ansible too.

The second is a managed control plane. Everything here is about bootstrapping nodes with kubeadm, and a team using a hosted Kubernetes service has no use for any of it.

The third is a cluster lifecycle beyond installation. The role installs and joins. It does not upgrade a cluster, it does not manage add-ons, it does not rotate certificates, and it does not tell you whether the cluster is healthy. A team that wants those things wants a different set of tooling, and pairing this role with a good deployment tool is usually the honest answer.

The fourth is the network choice, and this one is the sharpest. Because switching CNI is explicitly outside the role, a team that picks the wrong network on the first run has a manual migration ahead of it. The default of Flannel is reasonable, but it is a default, not a recommendation, and the documentation does not pretend otherwise.

A last practical note. The Galaxy release being from 2020 while the repository is current means you have to decide whether to install the published version or to point at the repository, and that decision should be made deliberately rather than by default.

Editorial conclusion

ansible-role-kubernetes suits a team that already runs Ansible and wants Kubernetes nodes built reproducibly, because the role pins the package version, drives kubeadm through a documented config file, and keeps the command line variables only for backward compatibility. It does not suit a team that wants a hosted control plane, and it does not suit someone who expects the role to manage the cluster after bootstrap, since it installs rather than operates. Before you use it, read the CNI section, because switching pod networks is explicitly not done by this role, and note that the last push was on 2026-09-08 while the newest listed release is 4.0.2 from 2020-03-19.

Frequently asked questions

How do I install Kubernetes with the ansible-role-kubernetes?

Add the role from Ansible Galaxy and set the required variables: a container runtime must already be present, with geerlingguy.containerd recommended. The role installs kubelet, kubectl, kubeadm and kubernetes-cni, and either runs kubeadm init on a control_plane server or kubeadm join on a node, depending on the kubernetes_role variable.

What is the recommended way to configure kubeadm in ansible-role-kubernetes?

Through a config file. The role runs kubeadm init with a config file path, and exposes variables for the init, cluster, kubelet and kube-proxy sections. The apiVersion and kind skeleton is created by the role, and the command line variables for the same settings are marked deprecated.

Which CNI options does ansible-role-kubernetes support?

flannel, which is the default, along with calico and weave. The documentation says to choose only one, because converting between them is not done automatically and could result in broken networking, so switching has to be done outside the role.

How do I control the Kubernetes version installed by the role?

Two variables. kubernetes_version pins the apt package version on Debian and is the version passed to kubeadm init, while kubernetes_version_rhel_package must be a specific release and is used on Red Hat and CentOS servers.

What does kubernetes_allow_pods_on_control_plane do?

It controls whether the taint that denies pods from being deployed to the Kubernetes control plane is removed. The documentation says it should definitely be true for a single-node cluster, and false when you want a dedicated control plane that runs no other pods.

Official sources

  1. geerlingguy/ansible-role-kubernetes on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/geerlingguy-ansible-role-kubernetes.svg)](https://hysenlabs.com/projects/geerlingguy-ansible-role-kubernetes)

Was this useful?