Ansible on Talos Linux 1.8: Ingress Controller Installation Complete Guide
By Luca Berton · Published 2024-01-01 · Category: installation
Automate ingress controller installation on Talos Linux 1.8 (Talos 1.8, GA 2024-10) with Ansible.
Talos Linux 1.8 (released 2024) is an immutable, API-managed Kubernetes OS — no SSH, no shell, no package manager. Once the cluster is bootstrapped you manage everything inside it like any Kubernetes cluster, by applying manifests and Helm releases, and Ansible's kubernetes.core collection drives that from your control node.
This guide installs the ingress-nginx controller on a Talos Linux 1.8 cluster with Ansible — and covers the one thing that trips people up on bare-metal Talos: getting the controller an external IP.
The bare-metal catch: you need a LoadBalancer
On a cloud provider a Service of type: LoadBalancer is fulfilled automatically. On bare-metal Talos there is no cloud controller, so an ingress controller's LoadBalancer service sits at EXTERNAL-IP: forever. The standard fix is MetalLB, which hands out IPs from a pool you define. This guide installs MetalLB first, then ingress-nginx.
> Prefer not to run MetalLB? Expose ingress-nginx with type: NodePort instead and point an external load balancer or DNS at the node IPs.
See also: Ansible on Microk8s: Ingress Controller Installation Complete Guide
Prerequisites
• A bootstrapped Talos Linux 1.8 cluster and its kubeconfig (see the cluster bootstrap guide). • Control node with ansible-core 2.15+,kubernetes.core 3.x+ (ansible-galaxy collection install kubernetes.core), the kubernetes Python library, and the Helm binary on PATH (the kubernetes.core.helm modules wrap it).
• A small range of free IPs on the node L2 network for MetalLB (e.g. 192.168.0.240-192.168.0.250), outside your DHCP scope.
Inventory
# inventory/talos.ini
[talos]
localhost ansible_connection=local
[talos:vars]
kubeconfig=/path/to/talos/kubeconfig
metallb_pool=192.168.0.240-192.168.0.250
See also: Ansible on RKE2: Ingress Controller Installation Complete Guide
Playbook
The play installs MetalLB so LoadBalancer services get a real IP, configures an address pool, installs ingress-nginx via Helm with wait: true, then blocks until the controller's external IP is assigned. Every module here is idempotent.
---
- name: Install ingress-nginx on Talos Linux 1.8
hosts: localhost
connection: local
gather_facts: false
vars:
kubeconfig: "{{ hostvars['localhost'].kubeconfig }}"
metallb_pool: "{{ hostvars['localhost'].metallb_pool }}"
tasks:
- name: Add Helm repositories
kubernetes.core.helm_repository:
name: "{{ item.name }}"
repo_url: "{{ item.url }}"
loop:
- { name: metallb, url: 'https://metallb.github.io/metallb' }
- { name: ingress-nginx, url: 'https://kubernetes.github.io/ingress-nginx' }
- name: Install MetalLB (provides LoadBalancer IPs on bare metal)
kubernetes.core.helm:
kubeconfig: "{{ kubeconfig }}"
name: metallb
chart_ref: metallb/metallb
release_namespace: metallb-system
create_namespace: true
wait: true
- name: Configure the MetalLB address pool
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig }}"
state: present
definition:
- apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: default-pool
namespace: metallb-system
spec:
addresses:
- "{{ metallb_pool }}"
- apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: default-l2
namespace: metallb-system
spec:
ipAddressPools:
- default-pool
- name: Install the ingress-nginx controller
kubernetes.core.helm:
kubeconfig: "{{ kubeconfig }}"
name: ingress-nginx
chart_ref: ingress-nginx/ingress-nginx
release_namespace: ingress-nginx
create_namespace: true
wait: true
values:
controller:
service:
type: LoadBalancer
metrics:
enabled: true
- name: Wait for the LoadBalancer to get an external IP
kubernetes.core.k8s_info:
kubeconfig: "{{ kubeconfig }}"
kind: Service
namespace: ingress-nginx
name: ingress-nginx-controller
register: svc
retries: 30
delay: 10
until: svc.resources[0].status.loadBalancer.ingress[0].ip is defined
- name: Show the assigned ingress IP
ansible.builtin.debug:
msg: "ingress-nginx is reachable at {{ svc.resources[0].status.loadBalancer.ingress[0].ip }}"
Validation
ansible-playbook -i inventory/talos.ini install-ingress.yml
kubectl --kubeconfig talos/kubeconfig get svc -n ingress-nginx
kubectl --kubeconfig talos/kubeconfig get pods -n ingress-nginx
The controller Service should now show a real EXTERNAL-IP from your MetalLB pool. Re-run the playbook to confirm idempotency — the Helm releases report changed=false and the address pool is unchanged.
See also: Ansible on Vanilla Kubernetes 1.30: Ingress Controller Installation Complete Guide
Smoke-test the ingress
Deploy a tiny app and route to it through an Ingress to prove the path end to end:
- name: Deploy a demo app behind the ingress
kubernetes.core.k8s:
kubeconfig: "{{ kubeconfig }}"
state: present
definition:
- apiVersion: apps/v1
kind: Deployment
metadata: { name: hello, namespace: default }
spec:
replicas: 1
selector: { matchLabels: { app: hello } }
template:
metadata: { labels: { app: hello } }
spec:
containers:
- name: hello
image: nginxdemos/hello:plain-text
ports: [{ containerPort: 80 }]
- apiVersion: v1
kind: Service
metadata: { name: hello, namespace: default }
spec:
selector: { app: hello }
ports: [{ port: 80, targetPort: 80 }]
- apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hello
namespace: default
spec:
ingressClassName: nginx
rules:
- host: hello.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port: { number: 80 }
Then curl the controller IP with the Host header:
curl -H 'Host: hello.example.com' http://<EXTERNAL-IP>/
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Controller Service stuck at EXTERNAL-IP | No LoadBalancer provider on bare metal | Install MetalLB and define an IPAddressPool + L2Advertisement (above), or switch the Service to NodePort |
| MetalLB assigns an IP you can't reach | Pool overlaps DHCP or wrong L2 segment | Use a free range on the node subnet, outside the DHCP scope |
| Admission webhook timeout creating an Ingress | ingress-nginx admission webhook not ready yet | Keep wait: true on the Helm task, or re-run; check kubectl -n ingress-nginx get validatingwebhookconfiguration |
| Every host returns 404 default backend | No Ingress resource matches the host/path | Create an Ingress with ingressClassName: nginx and the correct host/path |
| Controller pod CrashLoopBackOff | Forced hostNetwork under Talos Pod Security | ingress-nginx runs fine under the baseline PSA Talos enforces — don't enable hostNetwork unless the namespace is labelled for it |
FAQ
Q. Why does the ingress Service stay at on Talos?
Bare-metal Talos has no cloud load balancer, so nothing fulfils type: LoadBalancer. Install MetalLB (shown above) to assign external IPs, or use type: NodePort.
Q. Do I install ingress-nginx with Ansible or with Helm?
Both — kubernetes.core.helm is* Ansible driving Helm. It is idempotent and supports wait: true, so it fits the same playbook as the rest of your cluster tasks.
Q. Does this need SSH to the Talos nodes? No. Everything here talks to the Kubernetes API with the kubeconfig you fetched during bootstrap; Talos itself is never SSH'd into.
Q. How do I pin the ingress-nginx version?
Add chart_version: " to the kubernetes.core.helm task so re-runs are reproducible.
Q. Can I run more than one ingress controller?
Yes — install each in its own namespace with a distinct ingressClassName, and set the matching class on each Ingress resource.
Related guides
• bootstrap a Talos Linux 1.8 cluster first • provision StorageClass and PVCs on Talos Linux 1.8 • the kubernetes.core Helm and k8s collection guide • manage cluster resources with the k8s module • install ingress on RKE2 with AnsibleConclusion
Installing ingress on Talos Linux 1.8 is ordinary Kubernetes work once the cluster is up — the one bare-metal wrinkle is providing a LoadBalancer. With Ansible's kubernetes.core collection you install MetalLB for external IPs, deploy ingress-nginx via Helm with wait: true, and verify the assigned IP through the API, all idempotently from the control node. Pin chart versions, keep the values in Git, and the same playbook reproduces your ingress layer on any Talos cluster.
Category: installation