--- # 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, csi_snapshot, metrics, longhorn, metallb, kube_vip, traefik, cert_manager, newt, # argocd, kyverno, kyverno_policies, platform, velero, 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_csi_snapshot_controller tags: [csi_snapshot, snapshot, storage] - 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_velero tags: [velero, backups] - role: noble_landing_urls tags: [landing, platform, observability, apps]