Files
home-server/ansible/playbooks/noble.yml

229 lines
9.0 KiB
YAML

---
# Full platform install — **after** Talos bootstrap (`talosctl bootstrap` + working kubeconfig).
# Do not run until `kubectl get --raw /healthz` returns ok (see talos/README.md §3, CLUSTER-BUILD Phase A).
# Run from repo **ansible/** directory: ansible-playbook playbooks/noble.yml
#
# Tags: repos, cilium, metrics, longhorn, metallb, kube_vip, traefik, cert_manager, newt,
# argocd, kyverno, kyverno_policies, platform, all (default)
- name: Noble cluster — platform stack (Ansible-managed)
hosts: localhost
connection: local
gather_facts: false
vars:
noble_repo_root: "{{ playbook_dir | dirname | dirname }}"
noble_kubeconfig: "{{ lookup('env', 'KUBECONFIG') | default(noble_repo_root + '/talos/kubeconfig', true) }}"
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
pre_tasks:
# Helm/kubectl use $KUBECONFIG; a missing file yields "connection refused" to localhost:8080.
- name: Stat kubeconfig path from KUBECONFIG or default
ansible.builtin.stat:
path: "{{ noble_kubeconfig }}"
register: noble_kubeconfig_stat
tags: [always]
- name: Fall back to repo talos/kubeconfig when $KUBECONFIG is unset or not a file
ansible.builtin.set_fact:
noble_kubeconfig: "{{ noble_repo_root }}/talos/kubeconfig"
when: not noble_kubeconfig_stat.stat.exists | default(false)
tags: [always]
- name: Stat kubeconfig after fallback
ansible.builtin.stat:
path: "{{ noble_kubeconfig }}"
register: noble_kubeconfig_stat2
tags: [always]
- name: Require a real kubeconfig file
ansible.builtin.assert:
that:
- noble_kubeconfig_stat2.stat.exists | default(false)
- noble_kubeconfig_stat2.stat.isreg | default(false)
fail_msg: >-
No kubeconfig file at {{ noble_kubeconfig }}.
Fix: export KUBECONFIG=/actual/path/from/talosctl-kubeconfig (see talos/README.md),
or copy the admin kubeconfig to {{ noble_repo_root }}/talos/kubeconfig.
Do not use documentation placeholders as the path.
tags: [always]
- name: Ensure temp dir for kubeconfig API override
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/.ansible-tmp"
state: directory
mode: "0700"
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Copy kubeconfig for API server override (original file unchanged)
ansible.builtin.copy:
src: "{{ noble_kubeconfig }}"
dest: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched"
mode: "0600"
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Resolve current cluster name (for set-cluster)
ansible.builtin.command:
argv:
- kubectl
- config
- view
- --minify
- -o
- jsonpath={.clusters[0].name}
environment:
KUBECONFIG: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched"
register: noble_k8s_cluster_name
changed_when: false
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Point patched kubeconfig at reachable apiserver
ansible.builtin.command:
argv:
- kubectl
- config
- set-cluster
- "{{ noble_k8s_cluster_name.stdout }}"
- --server={{ noble_k8s_api_server_override }}
- --kubeconfig={{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched
when: noble_k8s_api_server_override | default('') | length > 0
changed_when: true
tags: [always]
- name: Use patched kubeconfig for this play
ansible.builtin.set_fact:
noble_kubeconfig: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.patched"
when: noble_k8s_api_server_override | default('') | length > 0
tags: [always]
- name: Verify Kubernetes API is reachable from this host
ansible.builtin.command:
argv:
- kubectl
- get
- --raw
- /healthz
- --request-timeout=15s
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_k8s_health_first
failed_when: false
changed_when: false
tags: [always]
# talosctl kubeconfig often sets server to the VIP; off-LAN you can reach a control-plane IP but not 192.168.50.230.
- name: Auto-fallback API server when VIP is unreachable (temp kubeconfig)
tags: [always]
when:
- noble_k8s_api_server_auto_fallback | default(true) | bool
- noble_k8s_api_server_override | default('') | length == 0
- not (noble_skip_k8s_health_check | default(false) | bool)
- (noble_k8s_health_first.rc | default(1)) != 0 or (noble_k8s_health_first.stdout | default('') | trim) != 'ok'
- ('network is unreachable' in (noble_k8s_health_first.stderr | default('') | lower)) or
('no route to host' in (noble_k8s_health_first.stderr | default('') | lower))
block:
- name: Ensure temp dir for kubeconfig auto-fallback
ansible.builtin.file:
path: "{{ noble_repo_root }}/ansible/.ansible-tmp"
state: directory
mode: "0700"
- name: Copy kubeconfig for API auto-fallback
ansible.builtin.copy:
src: "{{ noble_kubeconfig }}"
dest: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback"
mode: "0600"
- name: Resolve cluster name for kubectl set-cluster
ansible.builtin.command:
argv:
- kubectl
- config
- view
- --minify
- -o
- jsonpath={.clusters[0].name}
environment:
KUBECONFIG: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback"
register: noble_k8s_cluster_fb
changed_when: false
- name: Point temp kubeconfig at fallback apiserver
ansible.builtin.command:
argv:
- kubectl
- config
- set-cluster
- "{{ noble_k8s_cluster_fb.stdout }}"
- --server={{ noble_k8s_api_server_fallback | default('https://192.168.50.20:6443', true) }}
- --kubeconfig={{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback
changed_when: true
- name: Use kubeconfig with fallback API server for this play
ansible.builtin.set_fact:
noble_kubeconfig: "{{ noble_repo_root }}/ansible/.ansible-tmp/kubeconfig.auto-fallback"
- name: Re-verify Kubernetes API after auto-fallback
ansible.builtin.command:
argv:
- kubectl
- get
- --raw
- /healthz
- --request-timeout=15s
environment:
KUBECONFIG: "{{ noble_kubeconfig }}"
register: noble_k8s_health_after_fallback
failed_when: false
changed_when: false
- name: Mark that API was re-checked after kubeconfig fallback
ansible.builtin.set_fact:
noble_k8s_api_fallback_used: true
- name: Normalize API health result for preflight (scalars; avoids dict merge / set_fact stringification)
ansible.builtin.set_fact:
noble_k8s_health_rc: "{{ noble_k8s_health_after_fallback.rc | default(1) if (noble_k8s_api_fallback_used | default(false) | bool) else (noble_k8s_health_first.rc | default(1)) }}"
noble_k8s_health_stdout: "{{ noble_k8s_health_after_fallback.stdout | default('') if (noble_k8s_api_fallback_used | default(false) | bool) else (noble_k8s_health_first.stdout | default('')) }}"
noble_k8s_health_stderr: "{{ noble_k8s_health_after_fallback.stderr | default('') if (noble_k8s_api_fallback_used | default(false) | bool) else (noble_k8s_health_first.stderr | default('')) }}"
tags: [always]
- name: Fail when API check did not return ok
ansible.builtin.fail:
msg: "{{ lookup('template', 'templates/api_health_hint.j2') }}"
when:
- not (noble_skip_k8s_health_check | default(false) | bool)
- (noble_k8s_health_rc | int) != 0 or (noble_k8s_health_stdout | default('') | trim) != 'ok'
tags: [always]
roles:
- role: helm_repos
tags: [repos, helm]
- role: noble_cilium
tags: [cilium, cni]
- role: noble_metrics_server
tags: [metrics, metrics_server]
- role: noble_longhorn
tags: [longhorn, storage]
- role: noble_metallb
tags: [metallb, lb]
- role: noble_kube_vip
tags: [kube_vip, vip]
- role: noble_traefik
tags: [traefik, ingress]
- role: noble_cert_manager
tags: [cert_manager, certs]
- role: noble_newt
tags: [newt]
- role: noble_argocd
tags: [argocd, gitops]
- role: noble_kyverno
tags: [kyverno, policy]
- role: noble_kyverno_policies
tags: [kyverno_policies, policy]
- role: noble_platform
tags: [platform, observability, apps]
- role: noble_landing_urls
tags: [landing, platform, observability, apps]