first commit

This commit is contained in:
Sergey Antropoff
2026-04-17 08:37:27 +03:00
commit 095b276cb3
82 changed files with 5731 additions and 0 deletions

View File

@@ -0,0 +1,32 @@
---
# CNI плагин выбирается через k3s_cni в group_vars/all/main.yml:
# flannel (по умолчанию, встроен в k3s)
# calico — Tigera operator
# cilium — eBPF, устанавливается через Helm
# ─── Calico ───────────────────────────────────────────────────────────────────
calico_version: "v3.28.0"
calico_operator_url: "https://raw.githubusercontent.com/projectcalico/calico/{{ calico_version }}/manifests/tigera-operator.yaml"
calico_namespace: "calico-system"
# CIDR должен совпадать с k3s_cluster_cidr
calico_pod_cidr: "{{ k3s_cluster_cidr | default('10.42.0.0/16') }}"
# Encapsulation: VXLAN | IPIP | None
calico_encapsulation: "VXLAN"
# ─── Cilium ───────────────────────────────────────────────────────────────────
cilium_version: "1.15.5"
cilium_namespace: "kube-system"
cilium_chart_repo: "https://helm.cilium.io"
# IP k3s API server (обычно kube_vip_address если используется kube-vip)
cilium_k8s_service_host: "{{ kube_vip_address | default(hostvars[groups['k3s_master'][0]]['ansible_host']) }}"
cilium_k8s_service_port: 6443
# Включить Hubble (observability)
cilium_hubble_enabled: true
cilium_hubble_ui_enabled: false
# Ресурсы оператора
cilium_operator_replicas: 1

View File

@@ -0,0 +1,36 @@
---
- name: Apply Tigera operator
ansible.builtin.command: >
k3s kubectl apply -f {{ calico_operator_url }}
register: calico_operator
changed_when: "'created' in calico_operator.stdout or 'configured' in calico_operator.stdout"
retries: 3
delay: 10
- name: Wait for Tigera operator to be ready
ansible.builtin.command: >
k3s kubectl -n tigera-operator wait deployment/tigera-operator
--for=condition=Available --timeout=120s
changed_when: false
retries: 6
delay: 10
- name: Template Calico Installation CR
ansible.builtin.template:
src: calico-installation.yaml.j2
dest: /tmp/calico-installation.yaml
mode: '0644'
- name: Apply Calico Installation CR
ansible.builtin.command: k3s kubectl apply -f /tmp/calico-installation.yaml
register: calico_install
changed_when: "'created' in calico_install.stdout or 'configured' in calico_install.stdout"
- name: Wait for Calico nodes to be ready
ansible.builtin.command: >
k3s kubectl -n {{ calico_namespace }} wait pod
-l k8s-app=calico-node
--for=condition=Ready --timeout=180s
changed_when: false
retries: 6
delay: 15

View File

@@ -0,0 +1,36 @@
---
- name: Add Cilium Helm repo
kubernetes.core.helm_repository:
name: cilium
repo_url: "{{ cilium_chart_repo }}"
- name: Install Cilium via Helm
kubernetes.core.helm:
name: cilium
chart_ref: cilium/cilium
chart_version: "{{ cilium_version }}"
release_namespace: "{{ cilium_namespace }}"
create_namespace: false
kubeconfig: /etc/rancher/k3s/k3s.yaml
values:
k8sServiceHost: "{{ cilium_k8s_service_host }}"
k8sServicePort: "{{ cilium_k8s_service_port }}"
ipam:
mode: kubernetes
operator:
replicas: "{{ cilium_operator_replicas }}"
hubble:
enabled: "{{ cilium_hubble_enabled }}"
ui:
enabled: "{{ cilium_hubble_ui_enabled }}"
kubeProxyReplacement: true
register: cilium_deploy
- name: Wait for Cilium pods to be ready
ansible.builtin.command: >
k3s kubectl -n {{ cilium_namespace }} wait pod
-l k8s-app=cilium
--for=condition=Ready --timeout=180s
changed_when: false
retries: 6
delay: 15

12
roles/cni/tasks/main.yml Normal file
View File

@@ -0,0 +1,12 @@
---
- name: Skip CNI role when using built-in Flannel
ansible.builtin.meta: end_play
when: k3s_cni | default('flannel') == 'flannel'
- name: Install Calico CNI
ansible.builtin.include_tasks: calico.yml
when: k3s_cni == 'calico'
- name: Install Cilium CNI
ansible.builtin.include_tasks: cilium.yml
when: k3s_cni == 'cilium'

View File

@@ -0,0 +1,13 @@
---
apiVersion: operator.tigera.io/v1
kind: Installation
metadata:
name: default
spec:
calicoNetwork:
ipPools:
- blockSize: 26
cidr: "{{ calico_pod_cidr }}"
encapsulation: "{{ calico_encapsulation }}"
natOutgoing: Enabled
nodeSelector: all()

View File

@@ -0,0 +1,29 @@
---
# Версия CSI NFS Driver
csi_nfs_version: "v4.8.0"
# Helm chart
csi_nfs_chart_repo: "https://raw.githubusercontent.com/kubernetes-csi/csi-driver-nfs/master/charts"
csi_nfs_chart_name: "csi-driver-nfs"
csi_nfs_namespace: "kube-system"
# NFS сервер — IP или hostname машины с NFS
# Обычно это master нода или отдельный NFS сервер
csi_nfs_server: "{{ hostvars[groups['nfs_server'][0]]['ansible_host'] | default(hostvars[groups['k3s_master'][0]]['ansible_host']) }}"
# Базовый путь экспорта на NFS сервере
csi_nfs_share: "/srv/nfs/k8s"
# StorageClass настройки
# Имя включает hostname NFS сервера: nfs-master01, nfs-storage01, etc.
csi_nfs_storageclass_name: "nfs-{{ groups['nfs_server'][0] | default(groups['k3s_master'][0]) }}"
csi_nfs_storageclass_default: true
csi_nfs_reclaim_policy: "Delete" # Delete | Retain
csi_nfs_volume_binding_mode: "Immediate"
# Монтирование подпапок для каждого PVC
# onDelete: delete | retain | archive
csi_nfs_on_delete: "delete"
# nfs-common нужен на всех нодах для монтирования
csi_nfs_install_client: true

View File

@@ -0,0 +1,8 @@
---
galaxy_info:
author: "your-name"
description: "Deploy CSI NFS Driver and StorageClass for K3S"
license: "MIT"
min_ansible_version: "2.12"
dependencies:
- role: nfs-server

View File

@@ -0,0 +1,27 @@
---
- name: Check if Helm is installed
ansible.builtin.command: helm version --short
register: helm_check
failed_when: false
changed_when: false
become: true
- name: Download and install Helm
ansible.builtin.shell: |
set -o pipefail
curl -fsSL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
args:
executable: /bin/bash
become: true
when: helm_check.rc != 0
retries: 3
delay: 5
- name: Install kubernetes.core collection (for Helm module)
ansible.builtin.command: >
ansible-galaxy collection install kubernetes.core --upgrade
delegate_to: localhost
become: false
changed_when: true
run_once: true
failed_when: false

View File

@@ -0,0 +1,102 @@
---
- name: Install NFS client on all K3S nodes
ansible.builtin.apt:
name: nfs-common
state: present
update_cache: true
become: true
when: csi_nfs_install_client
# Выполняется на ВСЕХ нодах кластера (master + workers)
- name: Install Helm (if not present)
ansible.builtin.include_tasks: install_helm.yml
run_once: true
delegate_to: "{{ groups['k3s_master'][0] }}"
- name: Add CSI NFS Helm repo
kubernetes.core.helm_repository:
name: "{{ csi_nfs_chart_name }}"
repo_url: "{{ csi_nfs_chart_repo }}"
run_once: true
delegate_to: "{{ groups['k3s_master'][0] }}"
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
become: true
- name: Deploy CSI NFS Driver via Helm
kubernetes.core.helm:
name: "{{ csi_nfs_chart_name }}"
chart_ref: "{{ csi_nfs_chart_name }}/{{ csi_nfs_chart_name }}"
chart_version: "{{ csi_nfs_version }}"
release_namespace: "{{ csi_nfs_namespace }}"
create_namespace: false
wait: true
wait_condition:
type: Ready
timeout: "5m0s"
values:
controller:
replicas: 1
resources:
limits:
cpu: 200m
memory: 200Mi
requests:
cpu: 10m
memory: 20Mi
node:
resources:
limits:
cpu: 100m
memory: 100Mi
requests:
cpu: 10m
memory: 20Mi
run_once: true
delegate_to: "{{ groups['k3s_master'][0] }}"
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
become: true
- name: Deploy NFS StorageClass
ansible.builtin.template:
src: storageclass.yaml.j2
dest: /tmp/nfs-storageclass.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Apply NFS StorageClass
ansible.builtin.command: >
k3s kubectl apply -f /tmp/nfs-storageclass.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: true
- name: Verify CSI NFS pods are running
ansible.builtin.command: >
k3s kubectl -n {{ csi_nfs_namespace }} get pods
-l app=csi-nfs-controller
-o jsonpath='{.items[*].status.phase}'
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: csi_pods
until: "'Running' in csi_pods.stdout"
retries: 20
delay: 10
changed_when: false
- name: Show StorageClass
ansible.builtin.command: k3s kubectl get storageclass
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: sc_list
changed_when: false
- name: Display StorageClasses
ansible.builtin.debug:
msg: "{{ sc_list.stdout_lines }}"
run_once: true

View File

@@ -0,0 +1,23 @@
---
# NFS StorageClass — управляется Ansible (roles/csi-nfs)
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: {{ csi_nfs_storageclass_name }}
annotations:
storageclass.kubernetes.io/is-default-class: "{{ 'true' if csi_nfs_storageclass_default else 'false' }}"
provisioner: nfs.csi.k8s.io
parameters:
server: {{ csi_nfs_server }}
share: {{ csi_nfs_share }}
# Создавать подпапку для каждого PVC (рекомендуется)
subDir: ${pvc.metadata.namespace}/${pvc.metadata.name}/${pv.metadata.name}
onDelete: {{ csi_nfs_on_delete }}
reclaimPolicy: {{ csi_nfs_reclaim_policy }}
volumeBindingMode: {{ csi_nfs_volume_binding_mode }}
mountOptions:
- nfsvers=4.1
- hard
- intr
- timeo=600
- retrans=3

View File

@@ -0,0 +1,48 @@
---
# Версия ingress-nginx
ingress_nginx_version: "4.10.1" # Helm chart version
ingress_nginx_namespace: "ingress-nginx"
# Helm repo
ingress_nginx_chart_repo: "https://kubernetes.github.io/ingress-nginx"
ingress_nginx_chart_name: "ingress-nginx"
# Тип сервиса: LoadBalancer (с kube-vip) или NodePort
ingress_nginx_service_type: "LoadBalancer"
# Если LoadBalancer — статический IP (из пула kube-vip)
# Оставь пустым для автоматического назначения
ingress_nginx_load_balancer_ip: ""
# NodePort порты (используются когда service_type = NodePort)
ingress_nginx_http_nodeport: 30080
ingress_nginx_https_nodeport: 30443
# Количество реплик контроллера
ingress_nginx_replica_count: 1
# Включить Prometheus метрики
ingress_nginx_metrics_enabled: false
# Использовать DaemonSet вместо Deployment (рекомендуется для edge/RPi кластеров)
ingress_nginx_use_daemonset: false
# Дополнительные аргументы контроллера
ingress_nginx_extra_args: {}
# Пример:
# ingress_nginx_extra_args:
# enable-ssl-passthrough: ""
# default-ssl-certificate: "default/my-tls-secret"
# IngressClass
ingress_nginx_class_name: "nginx"
ingress_nginx_set_default_class: true
# Ресурсы контроллера
ingress_nginx_resources:
requests:
cpu: 100m
memory: 90Mi
limits:
cpu: 500m
memory: 256Mi

View File

@@ -0,0 +1,8 @@
---
- name: Restart K3S server
ansible.builtin.systemd:
name: k3s
state: restarted
daemon_reload: true
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"

View File

@@ -0,0 +1,8 @@
---
galaxy_info:
author: "your-name"
description: "Deploy ingress-nginx controller via Helm for K3S"
license: "MIT"
min_ansible_version: "2.12"
dependencies:
- role: kube-vip

View File

@@ -0,0 +1,103 @@
---
- name: Disable K3S built-in Traefik (required before ingress-nginx)
ansible.builtin.lineinfile:
path: "{{ k3s_config_dir }}/config.yaml"
line: "disable: traefik"
regexp: "^disable:"
state: present
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
notify: Restart K3S server
when: not k3s_disable_traefik
- name: Flush handlers (restart K3S if Traefik was just disabled)
ansible.builtin.meta: flush_handlers
- name: Ensure ingress-nginx namespace exists
ansible.builtin.command: >
k3s kubectl create namespace {{ ingress_nginx_namespace }}
--dry-run=client -o yaml | k3s kubectl apply -f -
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
- name: Add ingress-nginx Helm repo
kubernetes.core.helm_repository:
name: "{{ ingress_nginx_chart_name }}"
repo_url: "{{ ingress_nginx_chart_repo }}"
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Template Helm values
ansible.builtin.template:
src: ingress-nginx-values.yaml.j2
dest: /tmp/ingress-nginx-values.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Deploy ingress-nginx via Helm
kubernetes.core.helm:
name: "{{ ingress_nginx_chart_name }}"
chart_ref: "{{ ingress_nginx_chart_name }}/{{ ingress_nginx_chart_name }}"
chart_version: "{{ ingress_nginx_version }}"
release_namespace: "{{ ingress_nginx_namespace }}"
create_namespace: true
wait: true
timeout: "5m0s"
values_files:
- /tmp/ingress-nginx-values.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Wait for ingress-nginx controller to be ready
ansible.builtin.command: >
k3s kubectl -n {{ ingress_nginx_namespace }} rollout status
deployment/{{ ingress_nginx_chart_name }}-controller
--timeout=180s
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: nginx_rollout
changed_when: false
retries: 3
delay: 10
until: nginx_rollout.rc == 0
- name: Get ingress-nginx service info
ansible.builtin.command: >
k3s kubectl -n {{ ingress_nginx_namespace }}
get svc {{ ingress_nginx_chart_name }}-controller
-o wide
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: nginx_svc
changed_when: false
- name: Show ingress-nginx service
ansible.builtin.debug:
msg: "{{ nginx_svc.stdout_lines }}"
run_once: true
- name: Deploy test IngressClass (verify)
ansible.builtin.command: >
k3s kubectl get ingressclass {{ ingress_nginx_class_name }}
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: ingress_class_check
changed_when: false
failed_when: false
- name: Show IngressClass status
ansible.builtin.debug:
msg: "IngressClass '{{ ingress_nginx_class_name }}': {{ 'OK' if ingress_class_check.rc == 0 else 'NOT FOUND' }}"
run_once: true

View File

@@ -0,0 +1,96 @@
## ingress-nginx Helm values
## Управляется Ansible (roles/ingress-nginx)
controller:
ingressClassResource:
name: "{{ ingress_nginx_class_name }}"
enabled: true
default: {{ ingress_nginx_set_default_class | lower }}
ingressClass: "{{ ingress_nginx_class_name }}"
{% if ingress_nginx_use_daemonset %}
kind: DaemonSet
{% else %}
kind: Deployment
replicaCount: {{ ingress_nginx_replica_count }}
{% endif %}
service:
type: {{ ingress_nginx_service_type }}
{% if ingress_nginx_service_type == "LoadBalancer" and ingress_nginx_load_balancer_ip %}
loadBalancerIP: "{{ ingress_nginx_load_balancer_ip }}"
{% endif %}
{% if ingress_nginx_service_type == "NodePort" %}
nodePorts:
http: {{ ingress_nginx_http_nodeport }}
https: {{ ingress_nginx_https_nodeport }}
{% endif %}
resources:
requests:
cpu: "{{ ingress_nginx_resources.requests.cpu }}"
memory: "{{ ingress_nginx_resources.requests.memory }}"
limits:
cpu: "{{ ingress_nginx_resources.limits.cpu }}"
memory: "{{ ingress_nginx_resources.limits.memory }}"
# Логирование в JSON для удобного парсинга
config:
log-format-upstream: >-
{"time":"$time_iso8601","remote_addr":"$remote_addr",
"x_forwarded_for":"$http_x_forwarded_for","request_id":"$req_id",
"remote_user":"$remote_user","bytes_sent":"$bytes_sent",
"request_time":"$request_time","status":"$status",
"vhost":"$host","request_proto":"$server_protocol",
"path":"$uri","request_query":"$args",
"request_length":"$request_length","duration":"$request_time",
"method":"$request_method","http_referrer":"$http_referer",
"http_user_agent":"$http_user_agent"}
use-forwarded-headers: "true"
compute-full-forwarded-for: "true"
use-proxy-protocol: "false"
proxy-body-size: "50m"
proxy-read-timeout: "600"
proxy-send-timeout: "600"
{% if ingress_nginx_extra_args %}
extraArgs:
{% for key, value in ingress_nginx_extra_args.items() %}
{{ key }}: "{{ value }}"
{% endfor %}
{% endif %}
metrics:
enabled: {{ ingress_nginx_metrics_enabled | lower }}
{% if ingress_nginx_metrics_enabled %}
serviceMonitor:
enabled: false # включи если есть Prometheus Operator
{% endif %}
# Tolerations для запуска на мастере и RPi
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
- key: "node-type"
operator: "Equal"
value: "raspberry-pi"
effect: "NoSchedule"
admissionWebhooks:
enabled: true
failurePolicy: Fail
defaultBackend:
enabled: true
image:
registry: registry.k8s.io
image: defaultbackend-amd64
resources:
limits:
cpu: 10m
memory: 20Mi
requests:
cpu: 10m
memory: 20Mi

View File

@@ -0,0 +1,63 @@
---
# Включить установку Istio (false = пропустить)
istio_enabled: false
istio_version: "1.22.2" # Helm chart version (совпадает с версией Istio)
istio_namespace: "istio-system"
istio_chart_repo: "https://istio-release.storage.googleapis.com/charts"
# Устанавливать Istio Ingress Gateway (LoadBalancer)
istio_install_gateway: true
# Включить mutual TLS между сервисами
istio_mtls_mode: "STRICT" # STRICT | PERMISSIVE | DISABLE
# Ресурсы istiod (control plane)
istio_pilot_resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# Ресурсы gateway
istio_gateway_resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
# Включить Prometheus-совместимый сбор метрик
istio_telemetry_enabled: true
# ─── Kiali (Service Mesh UI) ──────────────────────────────────────────────────
# Установка по желанию (истио должен быть включён)
kiali_enabled: false
kiali_version: "1.86.0" # Helm chart version
kiali_namespace: "{{ istio_namespace }}"
kiali_chart_repo: "https://kiali.org/helm-charts"
# Токен для входа в Kiali UI.
# Задай в group_vars/all/vault.yml: vault_kiali_token: "ваш-токен"
# После первой установки Ansible выведет сгенерированный токен —
# скопируй его в vault.yml для последующих запусков.
kiali_token: "{{ vault_kiali_token | default('') }}"
# Ingress для Kiali (требует ingress-nginx)
kiali_ingress_enabled: false
kiali_ingress_host: "kiali.local"
kiali_ingress_class: "nginx"
# Ресурсы Kiali
kiali_resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi

View File

@@ -0,0 +1,6 @@
---
galaxy_info:
role_name: istio
description: Deploy Istio service mesh via Helm on K3S
min_ansible_version: "2.14"
dependencies: []

View File

@@ -0,0 +1,76 @@
---
- name: Converge — istio role template tests
hosts: all
become: false
gather_facts: false
vars:
istio_enabled: true
istio_version: "1.22.2"
istio_namespace: "istio-system"
istio_mtls_mode: "STRICT"
istio_install_gateway: true
istio_telemetry_enabled: true
istio_pilot_resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
istio_gateway_resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
kiali_enabled: true
kiali_namespace: "istio-system"
kiali_auth_strategy: "token"
kiali_ingress_enabled: false
kiali_ingress_host: "kiali.local"
kiali_ingress_class: "nginx"
kiali_resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
# Vars для kiali-values.yaml.j2 (интеграция с prometheus-stack)
prometheus_stack_enabled: true
prometheus_stack_release_name: "prom"
prometheus_stack_namespace: "monitoring"
prometheus_grafana_enabled: true
grafana_admin_user: "admin"
prometheus_grafana_admin_password: "molecule-test-pass"
tasks:
- name: Render istiod Helm values
ansible.builtin.template:
src: "{{ playbook_dir }}/../../templates/istiod-values.yaml.j2"
dest: /tmp/istiod-values.yaml
mode: '0644'
- name: Render Kiali Helm values
ansible.builtin.template:
src: "{{ playbook_dir }}/../../templates/kiali-values.yaml.j2"
dest: /tmp/kiali-values.yaml
mode: '0644'
- name: Render PeerAuthentication manifest
ansible.builtin.template:
src: "{{ playbook_dir }}/../../templates/peer-authentication.yaml.j2"
dest: /tmp/peer-authentication.yaml
mode: '0644'
- name: Render Kiali token secret manifest
ansible.builtin.template:
src: "{{ playbook_dir }}/../../templates/kiali-token-secret.yaml.j2"
dest: /tmp/kiali-token-secret.yaml
mode: '0644'

View File

@@ -0,0 +1,25 @@
---
driver:
name: docker
platforms:
- name: istio-test
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: converge.yml
verify: verify.yml
config_options:
defaults:
interpreter_python: auto_silent
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint

View File

@@ -0,0 +1,107 @@
---
- name: Verify — istio role templates
hosts: all
become: false
gather_facts: false
tasks:
# ── istiod Helm values ───────────────────────────────────────────────────────
- name: Read istiod values
ansible.builtin.slurp:
src: /tmp/istiod-values.yaml
register: istiod_raw
- name: Parse istiod YAML
ansible.builtin.set_fact:
istiod: "{{ istiod_raw.content | b64decode | from_yaml }}"
- name: Assert istiod pilot resources exist
ansible.builtin.assert:
that:
- istiod.pilot is defined
- istiod.pilot.resources.requests.cpu == '100m'
- istiod.pilot.resources.limits.memory == '512Mi'
fail_msg: "istiod pilot resources настроены неверно"
- name: Assert meshConfig exists
ansible.builtin.assert:
that: istiod.meshConfig is defined
fail_msg: "meshConfig отсутствует в istiod values"
- name: Assert telemetry flag
ansible.builtin.assert:
that: istiod.meshConfig.enablePrometheusMerge == true
fail_msg: "enablePrometheusMerge должен быть true при istio_telemetry_enabled=true"
# ── Kiali Helm values ────────────────────────────────────────────────────────
- name: Read kiali values
ansible.builtin.slurp:
src: /tmp/kiali-values.yaml
register: kiali_raw
- name: Parse kiali YAML
ansible.builtin.set_fact:
kiali: "{{ kiali_raw.content | b64decode | from_yaml }}"
- name: Assert kiali auth strategy is token
ansible.builtin.assert:
that: kiali.auth.strategy == 'token'
fail_msg: "Kiali auth.strategy должен быть 'token', получено: {{ kiali.auth.strategy }}"
- name: Assert kiali external_services prometheus URL
ansible.builtin.assert:
that:
- kiali.external_services.prometheus.url is defined
- "'prom-kube-prometheus-stack-prometheus' in kiali.external_services.prometheus.url"
fail_msg: "Kiali Prometheus URL настроен неверно: {{ kiali.external_services.prometheus.url }}"
- name: Assert kiali grafana integration
ansible.builtin.assert:
that:
- kiali.external_services.grafana.enabled == true
- kiali.external_services.grafana.auth.username == 'admin'
fail_msg: "Kiali Grafana интеграция настроена неверно"
# ── PeerAuthentication ───────────────────────────────────────────────────────
- name: Read PeerAuthentication manifest
ansible.builtin.slurp:
src: /tmp/peer-authentication.yaml
register: peer_raw
- name: Parse PeerAuthentication YAML
ansible.builtin.set_fact:
peer_auth: "{{ peer_raw.content | b64decode | from_yaml }}"
- name: Assert PeerAuthentication kind
ansible.builtin.assert:
that: peer_auth.kind == 'PeerAuthentication'
fail_msg: "Неверный kind: {{ peer_auth.kind }}"
- name: Assert mTLS mode is STRICT
ansible.builtin.assert:
that: peer_auth.spec.mtls.mode == 'STRICT'
fail_msg: "mTLS mode должен быть STRICT, получено: {{ peer_auth.spec.mtls.mode }}"
# ── Kiali Token Secret ───────────────────────────────────────────────────────
- name: Read kiali token secret manifest
ansible.builtin.slurp:
src: /tmp/kiali-token-secret.yaml
register: kiali_secret_raw
- name: Parse kiali token secret YAML
ansible.builtin.set_fact:
kiali_secret: "{{ kiali_secret_raw.content | b64decode | from_yaml }}"
- name: Assert kiali secret type
ansible.builtin.assert:
that: kiali_secret.type == 'kubernetes.io/service-account-token'
fail_msg: "Неверный тип секрета: {{ kiali_secret.type }}"
- name: Assert kiali secret annotation
ansible.builtin.assert:
that: kiali_secret.metadata.annotations['kubernetes.io/service-account.name'] == 'kiali-admin'
fail_msg: "Неверная аннотация service-account"
- name: Summary
ansible.builtin.debug:
msg: "Все проверки istio/kiali прошли успешно"

274
roles/istio/tasks/main.yml Normal file
View File

@@ -0,0 +1,274 @@
---
- name: Istio — skip if not enabled
ansible.builtin.debug:
msg: "Istio отключён (istio_enabled: false). Пропускаем."
when: not istio_enabled
run_once: true
- name: Istio — install
when: istio_enabled
block:
- name: Install Helm (if needed)
ansible.builtin.include_tasks: "{{ playbook_dir }}/../roles/csi-nfs/tasks/install_helm.yml"
- name: Add Istio Helm repo
kubernetes.core.helm_repository:
name: istio
repo_url: "{{ istio_chart_repo }}"
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Create istio-system namespace
ansible.builtin.command: >
k3s kubectl create namespace {{ istio_namespace }}
--dry-run=client -o yaml | k3s kubectl apply -f -
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
- name: Install Istio base CRDs (istio/base)
kubernetes.core.helm:
name: istio-base
chart_ref: istio/base
chart_version: "{{ istio_version }}"
release_namespace: "{{ istio_namespace }}"
create_namespace: false
wait: true
timeout: "5m0s"
values:
defaultRevision: default
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Template istiod values
ansible.builtin.template:
src: istiod-values.yaml.j2
dest: /tmp/istiod-values.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Install istiod (control plane)
kubernetes.core.helm:
name: istiod
chart_ref: istio/istiod
chart_version: "{{ istio_version }}"
release_namespace: "{{ istio_namespace }}"
create_namespace: false
wait: true
timeout: "5m0s"
values_files:
- /tmp/istiod-values.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Wait for istiod to be ready
ansible.builtin.command: >
k3s kubectl -n {{ istio_namespace }}
rollout status deployment/istiod --timeout=180s
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: istiod_ready
changed_when: false
retries: 3
delay: 10
until: istiod_ready.rc == 0
- name: Install Istio Gateway
kubernetes.core.helm:
name: istio-ingressgateway
chart_ref: istio/gateway
chart_version: "{{ istio_version }}"
release_namespace: "{{ istio_namespace }}"
create_namespace: false
wait: true
timeout: "5m0s"
values:
resources:
requests:
cpu: "{{ istio_gateway_resources.requests.cpu }}"
memory: "{{ istio_gateway_resources.requests.memory }}"
limits:
cpu: "{{ istio_gateway_resources.limits.cpu }}"
memory: "{{ istio_gateway_resources.limits.memory }}"
service:
type: LoadBalancer
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
when: istio_install_gateway
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Apply default PeerAuthentication (mTLS mode)
ansible.builtin.template:
src: peer-authentication.yaml.j2
dest: /tmp/istio-peer-auth.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Apply PeerAuthentication to cluster
ansible.builtin.command: >
k3s kubectl apply -f /tmp/istio-peer-auth.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: true
- name: Show Istio status
ansible.builtin.command: >
k3s kubectl -n {{ istio_namespace }} get pods
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: istio_pods
changed_when: false
- name: Istio pods
ansible.builtin.debug:
msg: "{{ istio_pods.stdout_lines }}"
run_once: true
# ─── Kiali ────────────────────────────────────────────────────────────────────
- name: Kiali — skip if not enabled
ansible.builtin.debug:
msg: "Kiali отключён (kiali_enabled: false). Пропускаем."
when: istio_enabled and not kiali_enabled
run_once: true
- name: Kiali — install
when: istio_enabled and kiali_enabled
block:
- name: Add Kiali Helm repo
kubernetes.core.helm_repository:
name: kiali
repo_url: "{{ kiali_chart_repo }}"
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Create kiali-admin ServiceAccount
ansible.builtin.command: >
k3s kubectl create serviceaccount kiali-admin
-n {{ kiali_namespace }}
--dry-run=client -o yaml | k3s kubectl apply -f -
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
- name: Bind kiali-admin to cluster-admin
ansible.builtin.command: >
k3s kubectl create clusterrolebinding kiali-admin
--clusterrole=cluster-admin
--serviceaccount={{ kiali_namespace }}:kiali-admin
--dry-run=client -o yaml | k3s kubectl apply -f -
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
- name: Create long-lived token secret for kiali-admin
ansible.builtin.template:
src: kiali-token-secret.yaml.j2
dest: /tmp/kiali-token-secret.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Apply kiali-admin token secret
ansible.builtin.command: >
k3s kubectl apply -f /tmp/kiali-token-secret.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
- name: Wait for k8s to populate the token
ansible.builtin.command: >
k3s kubectl -n {{ kiali_namespace }}
get secret kiali-admin-token
-o jsonpath="{.data.token}"
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: kiali_token_check
until: kiali_token_check.stdout | length > 0
retries: 10
delay: 3
changed_when: false
- name: Decode Kiali login token
ansible.builtin.set_fact:
kiali_generated_token: "{{ kiali_token_check.stdout | b64decode }}"
run_once: true
- name: Template Kiali Helm values
ansible.builtin.template:
src: kiali-values.yaml.j2
dest: /tmp/kiali-values.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Deploy Kiali via Helm
kubernetes.core.helm:
name: kiali-server
chart_ref: kiali/kiali-server
chart_version: "{{ kiali_version }}"
release_namespace: "{{ kiali_namespace }}"
create_namespace: false
wait: true
timeout: "5m0s"
values_files:
- /tmp/kiali-values.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Wait for Kiali to be ready
ansible.builtin.command: >
k3s kubectl -n {{ kiali_namespace }}
rollout status deployment/kiali --timeout=180s
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: kiali_ready
changed_when: false
retries: 3
delay: 10
until: kiali_ready.rc == 0
- name: Show Kiali access info
ansible.builtin.debug:
msg:
- "══════════════════════════════════════════════════"
- " Kiali UI доступен через port-forward:"
- " kubectl -n {{ kiali_namespace }} port-forward svc/kiali 20001:20001"
- " Откройте: http://localhost:20001"
- "{% if kiali_ingress_enabled %} Или через Ingress: http://{{ kiali_ingress_host }}{% endif %}"
- ""
- " Стратегия аутентификации: token"
- " Токен для входа:"
- " {{ kiali_generated_token }}"
- ""
- " Сохрани токен в vault.yml:"
- " vault_kiali_token: <токен выше>"
- "══════════════════════════════════════════════════"
run_once: true

View File

@@ -0,0 +1,36 @@
## istiod Helm values
## Управляется Ansible (roles/istio)
pilot:
resources:
requests:
cpu: "{{ istio_pilot_resources.requests.cpu }}"
memory: "{{ istio_pilot_resources.requests.memory }}"
limits:
cpu: "{{ istio_pilot_resources.limits.cpu }}"
memory: "{{ istio_pilot_resources.limits.memory }}"
# Tolerations — можно запускать на мастерах
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
meshConfig:
accessLogFile: /dev/stdout
enableTracing: false
{% if istio_telemetry_enabled %}
defaultConfig:
proxyMetadata: {}
enablePrometheusMerge: true
{% endif %}
global:
proxy:
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi

View File

@@ -0,0 +1,10 @@
## Долгосрочный токен для ServiceAccount kiali-admin
## k8s автоматически заполняет поле .data.token
apiVersion: v1
kind: Secret
metadata:
name: kiali-admin-token
namespace: {{ kiali_namespace }}
annotations:
kubernetes.io/service-account.name: kiali-admin
type: kubernetes.io/service-account-token

View File

@@ -0,0 +1,64 @@
## Kiali Helm values
## Управляется Ansible (roles/istio)
auth:
strategy: token
deployment:
# Использовать существующий ServiceAccount kiali-admin
service_account: kiali-admin
resources:
requests:
cpu: "{{ kiali_resources.requests.cpu }}"
memory: "{{ kiali_resources.requests.memory }}"
limits:
cpu: "{{ kiali_resources.limits.cpu }}"
memory: "{{ kiali_resources.limits.memory }}"
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
{% if kiali_ingress_enabled %}
ingress:
enabled: true
class_name: "{{ kiali_ingress_class }}"
override_yaml:
spec:
rules:
- host: "{{ kiali_ingress_host }}"
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: kiali
port:
number: 20001
{% endif %}
external_services:
prometheus:
{% if prometheus_stack_enabled %}
url: "http://{{ prometheus_stack_release_name }}-kube-prometheus-stack-prometheus.{{ prometheus_stack_namespace }}:9090"
{% else %}
url: "http://prometheus-operated.monitoring:9090"
{% endif %}
grafana:
enabled: {{ prometheus_grafana_enabled | lower }}
{% if prometheus_stack_enabled and prometheus_grafana_enabled %}
url: "http://{{ prometheus_stack_release_name }}-grafana.{{ prometheus_stack_namespace }}:80"
auth:
username: "{{ grafana_admin_user }}"
password: "{{ prometheus_grafana_admin_password }}"
type: basic
{% endif %}
istio:
root_namespace: "{{ istio_namespace }}"
server:
port: 20001
web_root: /kiali

View File

@@ -0,0 +1,9 @@
## Глобальный режим mTLS для всего mesh
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: {{ istio_namespace }}
spec:
mtls:
mode: {{ istio_mtls_mode }}

View File

@@ -0,0 +1,19 @@
---
# Token for joining cluster nodes (override in vault!)
k3s_token: "changeme-use-ansible-vault"
# Master node IP (used by agents to join)
k3s_master_ip: "{{ hostvars[groups['k3s_master'][0]]['ansible_host'] }}"
# K3S API server URL
k3s_api_url: "https://{{ k3s_master_ip }}:6443"
# Write kubeconfig to local machine
k3s_fetch_kubeconfig: true
k3s_kubeconfig_local_path: "./kubeconfig"
# Raspberry Pi specific
rpi_cgroup_enable: true
rpi_cmdline_path: /boot/cmdline.txt
# For newer RPi OS (bookworm)
rpi_cmdline_path_new: /boot/firmware/cmdline.txt

View File

@@ -0,0 +1,21 @@
---
- name: Restart K3S server
ansible.builtin.systemd:
name: k3s
state: restarted
daemon_reload: true
become: "{{ k3s_become }}"
- name: Restart K3S agent
ansible.builtin.systemd:
name: k3s-agent
state: restarted
daemon_reload: true
become: "{{ k3s_become }}"
- name: Reboot Raspberry Pi
ansible.builtin.reboot:
reboot_timeout: 300
connect_timeout: 60
msg: "Rebooting after enabling cgroups for K3S"
become: "{{ k3s_become }}"

14
roles/k3s/meta/main.yml Normal file
View File

@@ -0,0 +1,14 @@
---
galaxy_info:
author: "your-name"
description: "Install and configure K3S Kubernetes cluster"
license: "MIT"
min_ansible_version: "2.12"
platforms:
- name: Ubuntu
versions: ["20.04", "22.04", "24.04"]
- name: Debian
versions: ["11", "12"]
- name: Raspbian
versions: ["11", "12"]
dependencies: []

View File

@@ -0,0 +1,56 @@
---
- name: Converge — k3s role unit tests
hosts: all
become: true
gather_facts: true
vars:
k3s_token: "molecule-test-token-abc123"
k3s_version: "v1.29.3+k3s1"
k3s_become: true
k3s_fetch_kubeconfig: false
k3s_node_labels: []
k3s_node_taints: []
k3s_cluster_cidr: "10.42.0.0/16"
k3s_service_cidr: "10.43.0.0/16"
k3s_cluster_dns: "10.43.0.10"
k3s_flannel_backend: "vxlan"
k3s_install_dir: /usr/local/bin
k3s_config_dir: /etc/rancher/k3s
k3s_data_dir: /var/lib/rancher/k3s
k3s_disable_traefik: true
k3s_disable_servicelb: false
k3s_disable_local_storage: false
k3s_extra_server_args: ""
k3s_api_url: "https://127.0.0.1:6443"
pre_tasks:
- name: Mock k3s binary (симулирует уже установленный k3s)
ansible.builtin.copy:
content: "#!/bin/bash\nexit 0\n"
dest: /usr/local/bin/k3s
mode: '0755'
- name: Create k3s server data directory
ansible.builtin.file:
path: /var/lib/rancher/k3s/server
state: directory
mode: '0700'
- name: Mock k3s node-token
ansible.builtin.copy:
content: "K10::server:molecule-test-node-token\n"
dest: /var/lib/rancher/k3s/server/node-token
mode: '0600'
tasks:
# ── Тест 1: Предварительные требования ─────────────────────────────────────
- name: Test prereqs — install packages
ansible.builtin.include_tasks: "{{ playbook_dir }}/../../tasks/prereqs.yml"
# ── Тест 2: Генерация конфигурационного файла ───────────────────────────────
- name: Test server config template rendering
ansible.builtin.template:
src: "{{ playbook_dir }}/../../templates/k3s-server-config.yaml.j2"
dest: /etc/rancher/k3s/config.yaml
mode: '0600'

View File

@@ -0,0 +1,38 @@
---
driver:
name: docker
platforms:
- name: k3s-node
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
privileged: true
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:rw
cgroupns_mode: host
command: /lib/systemd/systemd
groups:
- k3s_master
- k3s_cluster
provisioner:
name: ansible
playbooks:
prepare: prepare.yml
converge: converge.yml
verify: verify.yml
config_options:
defaults:
interpreter_python: auto_silent
inventory:
group_vars:
k3s_master:
k3s_master_ip: "{{ hostvars[groups['k3s_master'][0]]['ansible_host'] | default('127.0.0.1') }}"
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint

View File

@@ -0,0 +1,28 @@
---
- name: Prepare k3s test environment
hosts: all
become: true
gather_facts: false
tasks:
- name: Wait for systemd to start
ansible.builtin.command: systemctl is-system-running
register: systemd_running
until: systemd_running.stdout in ['running', 'degraded']
retries: 20
delay: 3
changed_when: false
failed_when: false
- name: Install Python3
ansible.builtin.raw: apt-get update && apt-get install -y python3
changed_when: true
- name: Install Ansible collections
ansible.builtin.command: >
ansible-galaxy collection install
community.general ansible.posix --upgrade
become: false
changed_when: true
delegate_to: localhost
run_once: true

View File

@@ -0,0 +1,104 @@
---
- name: Verify — k3s role
hosts: all
become: true
gather_facts: false
tasks:
# ── Проверка директорий ─────────────────────────────────────────────────────
- name: Check k3s config directory exists
ansible.builtin.stat:
path: /etc/rancher/k3s
register: config_dir
- name: Assert config directory
ansible.builtin.assert:
that: config_dir.stat.isdir
fail_msg: "Директория /etc/rancher/k3s не создана"
# ── Проверка конфигурационного файла ────────────────────────────────────────
- name: Check config file exists
ansible.builtin.stat:
path: /etc/rancher/k3s/config.yaml
register: config_file
- name: Assert config file exists
ansible.builtin.assert:
that: config_file.stat.exists
fail_msg: "Файл /etc/rancher/k3s/config.yaml не создан"
- name: Check config file permissions (0600)
ansible.builtin.assert:
that: "config_file.stat.mode == '0600'"
fail_msg: "Неверные права на config.yaml: {{ config_file.stat.mode }}"
- name: Read config file
ansible.builtin.slurp:
src: /etc/rancher/k3s/config.yaml
register: config_raw
- name: Parse config as YAML
ansible.builtin.set_fact:
k3s_config: "{{ config_raw.content | b64decode | from_yaml }}"
- name: Assert token is set
ansible.builtin.assert:
that: k3s_config.token is defined
fail_msg: "Поле 'token' отсутствует в config.yaml"
- name: Assert cluster-cidr is correct
ansible.builtin.assert:
that: k3s_config['cluster-cidr'] == '10.42.0.0/16'
fail_msg: "Неверный cluster-cidr: {{ k3s_config['cluster-cidr'] }}"
- name: Assert service-cidr is correct
ansible.builtin.assert:
that: k3s_config['service-cidr'] == '10.43.0.0/16'
fail_msg: "Неверный service-cidr: {{ k3s_config['service-cidr'] }}"
- name: Assert cluster-init is set (первый мастер)
ansible.builtin.assert:
that: k3s_config['cluster-init'] == true
fail_msg: "cluster-init должен быть true для первого мастера"
- name: Assert traefik is disabled
ansible.builtin.assert:
that: "'traefik' in k3s_config.disable"
fail_msg: "traefik должен быть в списке disable"
# ── Проверка системных пакетов ──────────────────────────────────────────────
- name: Check curl is installed
ansible.builtin.command: which curl
register: curl_check
changed_when: false
- name: Assert curl installed
ansible.builtin.assert:
that: curl_check.rc == 0
fail_msg: "curl не установлен"
- name: Check iptables is installed
ansible.builtin.command: which iptables
register: iptables_check
changed_when: false
failed_when: false
- name: Assert iptables installed
ansible.builtin.assert:
that: iptables_check.rc == 0
fail_msg: "iptables не установлен"
# ── Проверка sysctl ──────────────────────────────────────────────────────────
- name: Check ip_forward sysctl
ansible.builtin.command: sysctl net.ipv4.ip_forward
register: ip_forward
changed_when: false
- name: Assert ip_forward is 1
ansible.builtin.assert:
that: "'= 1' in ip_forward.stdout"
fail_msg: "net.ipv4.ip_forward не установлен в 1"
- name: Summary
ansible.builtin.debug:
msg: "Все проверки прошли успешно для ноды {{ inventory_hostname }}"

View File

@@ -0,0 +1,73 @@
---
# Диагностика состояния кластера
- name: Check K3S service status (master)
ansible.builtin.systemd:
name: k3s
register: k3s_service_status
become: "{{ k3s_become }}"
when: inventory_hostname in groups['k3s_master']
- name: Check K3S service status (workers)
ansible.builtin.systemd:
name: k3s-agent
register: k3s_agent_status
become: "{{ k3s_become }}"
when: inventory_hostname in groups['k3s_workers']
- name: Show service status
ansible.builtin.debug:
msg: >-
{{ inventory_hostname }}:
{{
(k3s_service_status.status.ActiveState | default(''))
if inventory_hostname in groups['k3s_master']
else (k3s_agent_status.status.ActiveState | default(''))
}}
- name: Get node list
ansible.builtin.command: k3s kubectl get nodes -o wide
register: node_list
become: "{{ k3s_become }}"
changed_when: false
run_once: true
delegate_to: "{{ groups['k3s_master'][0] }}"
- name: Show nodes
ansible.builtin.debug:
msg: "{{ node_list.stdout_lines }}"
run_once: true
- name: Get all pods (all namespaces)
ansible.builtin.command: k3s kubectl get pods -A --field-selector=status.phase!=Running
register: non_running_pods
become: "{{ k3s_become }}"
changed_when: false
run_once: true
delegate_to: "{{ groups['k3s_master'][0] }}"
failed_when: false
- name: Show non-running pods (if any)
ansible.builtin.debug:
msg: "{{ non_running_pods.stdout_lines }}"
run_once: true
when: non_running_pods.stdout_lines | length > 1
- name: Check disk space on all nodes
ansible.builtin.shell: df -h / | tail -1
register: disk_space
changed_when: false
become: "{{ k3s_become }}"
- name: Show disk usage
ansible.builtin.debug:
msg: "{{ inventory_hostname }} disk: {{ disk_space.stdout }}"
- name: Check memory usage
ansible.builtin.shell: free -h | grep Mem
register: mem_usage
changed_when: false
- name: Show memory usage
ansible.builtin.debug:
msg: "{{ inventory_hostname }} memory: {{ mem_usage.stdout }}"

View File

@@ -0,0 +1,51 @@
---
- name: Check if K3S agent is already installed
ansible.builtin.stat:
path: "{{ k3s_install_dir }}/k3s"
register: k3s_agent_binary
- name: Get node token from master
ansible.builtin.set_fact:
k3s_node_token: "{{ hostvars[groups['k3s_master'][0]]['k3s_node_token'] }}"
- name: Write K3S agent config
ansible.builtin.template:
src: k3s-agent-config.yaml.j2
dest: "{{ k3s_config_dir }}/config.yaml"
mode: '0600'
become: "{{ k3s_become }}"
notify: Restart K3S agent
- name: Download and install K3S agent
ansible.builtin.shell: |
set -o pipefail
curl -sfL https://get.k3s.io | \
INSTALL_K3S_VERSION="{{ k3s_version }}" \
INSTALL_K3S_EXEC="agent" \
K3S_URL="{{ k3s_api_url }}" \
K3S_TOKEN="{{ k3s_node_token }}" \
sh -
args:
executable: /bin/bash
become: "{{ k3s_become }}"
when: not k3s_agent_binary.stat.exists
notify: Restart K3S agent
- name: Enable and start K3S agent service
ansible.builtin.systemd:
name: k3s-agent
enabled: true
state: started
daemon_reload: true
become: "{{ k3s_become }}"
- name: Wait for node to join the cluster
ansible.builtin.command: >
k3s kubectl get node {{ inventory_hostname }}
delegate_to: "{{ groups['k3s_master'][0] }}"
become: "{{ k3s_become }}"
register: node_ready
until: "'Ready' in node_ready.stdout"
retries: 20
delay: 10
changed_when: false

View File

@@ -0,0 +1,61 @@
---
- name: Check if K3S server is already installed
ansible.builtin.stat:
path: "{{ k3s_install_dir }}/k3s"
register: k3s_binary
- name: Write K3S server config
ansible.builtin.template:
src: k3s-server-config.yaml.j2
dest: "{{ k3s_config_dir }}/config.yaml"
mode: '0600'
become: "{{ k3s_become }}"
notify: Restart K3S server
- name: Download and install K3S server
ansible.builtin.shell: |
set -o pipefail
curl -sfL https://get.k3s.io | \
INSTALL_K3S_VERSION="{{ k3s_version }}" \
INSTALL_K3S_EXEC="server" \
K3S_TOKEN="{{ k3s_token }}" \
sh -
args:
executable: /bin/bash
become: "{{ k3s_become }}"
when: not k3s_binary.stat.exists
notify: Restart K3S server
- name: Enable and start K3S server service
ansible.builtin.systemd:
name: k3s
enabled: true
state: started
daemon_reload: true
become: "{{ k3s_become }}"
- name: Wait for K3S node-token to be generated
ansible.builtin.wait_for:
path: /var/lib/rancher/k3s/server/node-token
timeout: 120
become: "{{ k3s_become }}"
- name: Read node-token
ansible.builtin.slurp:
src: /var/lib/rancher/k3s/server/node-token
register: node_token_raw
become: "{{ k3s_become }}"
- name: Set node-token fact (shared to all hosts)
ansible.builtin.set_fact:
k3s_node_token: "{{ node_token_raw['content'] | b64decode | trim }}"
- name: Wait for API server to become available
ansible.builtin.uri:
url: "{{ k3s_api_url }}/healthz"
validate_certs: false
status_code: 200
register: api_health
until: api_health.status == 200
retries: 30
delay: 5

View File

@@ -0,0 +1,27 @@
---
- name: Read kubeconfig from master
ansible.builtin.slurp:
src: /etc/rancher/k3s/k3s.yaml
register: kubeconfig_raw
become: "{{ k3s_become }}"
- name: Decode and patch server URL in kubeconfig
ansible.builtin.set_fact:
kubeconfig_patched: >-
{{
kubeconfig_raw['content'] | b64decode
| regex_replace('https://127.0.0.1:6443', k3s_api_url)
| regex_replace('https://localhost:6443', k3s_api_url)
}}
- name: Save kubeconfig locally
ansible.builtin.copy:
content: "{{ kubeconfig_patched }}"
dest: "{{ k3s_kubeconfig_local_path }}"
mode: '0600'
delegate_to: localhost
become: false
- name: Show kubeconfig location
ansible.builtin.debug:
msg: "Kubeconfig saved to {{ k3s_kubeconfig_local_path }}"

21
roles/k3s/tasks/main.yml Normal file
View File

@@ -0,0 +1,21 @@
---
- name: Include OS prerequisites
ansible.builtin.include_tasks: prereqs.yml
- name: Raspberry Pi — enable cgroups
ansible.builtin.include_tasks: rpi_cgroups.yml
when: ansible_architecture in ['armv7l', 'aarch64'] and rpi_cgroup_enable
- name: Install K3S server
ansible.builtin.include_tasks: install_server.yml
when: inventory_hostname in groups['k3s_master']
- name: Configure node labels and taints
ansible.builtin.include_tasks: node_config.yml
when: k3s_node_labels | length > 0 or k3s_node_taints | length > 0
- name: Fetch kubeconfig to local machine
ansible.builtin.include_tasks: kubeconfig.yml
when:
- k3s_fetch_kubeconfig
- inventory_hostname == groups['k3s_master'][0]

View File

@@ -0,0 +1,18 @@
---
- name: Apply node labels
ansible.builtin.command: >
k3s kubectl label node {{ inventory_hostname }}
{{ item }} --overwrite
loop: "{{ k3s_node_labels }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
become: "{{ k3s_become }}"
changed_when: true
- name: Apply node taints
ansible.builtin.command: >
k3s kubectl taint node {{ inventory_hostname }}
{{ item }} --overwrite
loop: "{{ k3s_node_taints }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
become: "{{ k3s_become }}"
changed_when: true

View File

@@ -0,0 +1,67 @@
---
- name: Update apt cache
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
become: "{{ k3s_become }}"
- name: Install required packages
ansible.builtin.apt:
name:
- curl
- ca-certificates
- apt-transport-https
- gnupg
- iptables
state: present
become: "{{ k3s_become }}"
- name: Disable swap
ansible.builtin.command: swapoff -a
become: "{{ k3s_become }}"
changed_when: false
- name: Remove swap from fstab
ansible.builtin.replace:
path: /etc/fstab
regexp: '^([^#].*?\sswap\s+sw\s+.*)$'
replace: '# \1'
become: "{{ k3s_become }}"
- name: Load required kernel modules
community.general.modprobe:
name: "{{ item }}"
state: present
loop:
- overlay
- br_netfilter
become: "{{ k3s_become }}"
- name: Persist kernel modules
ansible.builtin.copy:
dest: /etc/modules-load.d/k3s.conf
content: |
overlay
br_netfilter
mode: '0644'
become: "{{ k3s_become }}"
- name: Set sysctl params for Kubernetes networking
ansible.posix.sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
state: present
sysctl_file: /etc/sysctl.d/99-k3s.conf
reload: true
loop:
- { key: "net.bridge.bridge-nf-call-iptables", value: "1" }
- { key: "net.bridge.bridge-nf-call-ip6tables", value: "1" }
- { key: "net.ipv4.ip_forward", value: "1" }
become: "{{ k3s_become }}"
- name: Create K3S config directory
ansible.builtin.file:
path: "{{ k3s_config_dir }}"
state: directory
mode: '0755'
become: "{{ k3s_become }}"

View File

@@ -0,0 +1,47 @@
---
# Raspberry Pi requires cgroup_enable=cpuset cgroup_memory=1 cgroup_enable=memory
# in /boot/cmdline.txt (legacy) or /boot/firmware/cmdline.txt (bookworm+)
- name: Detect correct cmdline.txt path
ansible.builtin.set_fact:
rpi_cmdline_file: >-
{{
rpi_cmdline_path_new
if ansible_distribution_release in ['bookworm', 'trixie']
else rpi_cmdline_path
}}
- name: Check if cmdline.txt exists
ansible.builtin.stat:
path: "{{ rpi_cmdline_file }}"
register: cmdline_stat
become: "{{ k3s_become }}"
- name: Fallback to legacy path if new path missing
ansible.builtin.set_fact:
rpi_cmdline_file: "{{ rpi_cmdline_path }}"
when: not cmdline_stat.stat.exists
- name: Read current cmdline.txt
ansible.builtin.slurp:
src: "{{ rpi_cmdline_file }}"
register: cmdline_content
become: "{{ k3s_become }}"
- name: Set cgroup parameters fact
ansible.builtin.set_fact:
cmdline_current: "{{ cmdline_content['content'] | b64decode | trim }}"
cgroup_params: "cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1"
- name: Add cgroup params to cmdline.txt if missing
ansible.builtin.copy:
dest: "{{ rpi_cmdline_file }}"
content: "{{ cmdline_current }} {{ cgroup_params }}\n"
mode: '0755'
backup: true
become: "{{ k3s_become }}"
when: "'cgroup_memory=1' not in cmdline_current"
notify: Reboot Raspberry Pi
- name: Flush handlers to reboot if needed
ansible.builtin.meta: flush_handlers

View File

@@ -0,0 +1,89 @@
---
# Полное удаление K3S с ноды
- name: Stop and disable K3S server service
ansible.builtin.systemd:
name: k3s
state: stopped
enabled: false
become: "{{ k3s_become }}"
failed_when: false
when: inventory_hostname in groups['k3s_master']
- name: Stop and disable K3S agent service
ansible.builtin.systemd:
name: k3s-agent
state: stopped
enabled: false
become: "{{ k3s_become }}"
failed_when: false
when: inventory_hostname in groups['k3s_workers']
- name: Run K3S server uninstall script (if exists)
ansible.builtin.command: /usr/local/bin/k3s-uninstall.sh
become: "{{ k3s_become }}"
failed_when: false
changed_when: true
when: inventory_hostname in groups['k3s_master']
- name: Run K3S agent uninstall script (if exists)
ansible.builtin.command: /usr/local/bin/k3s-agent-uninstall.sh
become: "{{ k3s_become }}"
failed_when: false
changed_when: true
when: inventory_hostname in groups['k3s_workers']
- name: Remove K3S binary
ansible.builtin.file:
path: "{{ k3s_install_dir }}/k3s"
state: absent
become: "{{ k3s_become }}"
- name: Remove K3S config directory
ansible.builtin.file:
path: "{{ k3s_config_dir }}"
state: absent
become: "{{ k3s_become }}"
- name: Remove K3S data directory
ansible.builtin.file:
path: "{{ k3s_data_dir }}"
state: absent
become: "{{ k3s_become }}"
- name: Remove K3S systemd units
ansible.builtin.file:
path: "{{ item }}"
state: absent
loop:
- /etc/systemd/system/k3s.service
- /etc/systemd/system/k3s-agent.service
- /etc/systemd/system/k3s.service.env
- /etc/systemd/system/k3s-agent.service.env
become: "{{ k3s_become }}"
notify: Reload systemd
- name: Remove sysctl config
ansible.builtin.file:
path: /etc/sysctl.d/99-k3s.conf
state: absent
become: "{{ k3s_become }}"
- name: Remove kernel modules config
ansible.builtin.file:
path: /etc/modules-load.d/k3s.conf
state: absent
become: "{{ k3s_become }}"
- name: Remove local kubeconfig
ansible.builtin.file:
path: "{{ k3s_kubeconfig_local_path }}"
state: absent
delegate_to: localhost
become: false
run_once: true
- name: Reload systemd daemon
ansible.builtin.systemd:
daemon_reload: true
become: "{{ k3s_become }}"

118
roles/k3s/tasks/upgrade.yml Normal file
View File

@@ -0,0 +1,118 @@
---
# Обновление K3S на всех нодах кластера
# Порядок: сначала master, затем workers (по одному)
- name: Get current K3S version
ansible.builtin.command: k3s --version
register: k3s_current_version
become: "{{ k3s_become }}"
changed_when: false
failed_when: false
- name: Show current version
ansible.builtin.debug:
msg: "Current: {{ k3s_current_version.stdout | default('not installed') }} → Target: {{ k3s_version }}"
- name: Skip upgrade if already at target version
ansible.builtin.meta: end_host
when:
- k3s_current_version.rc == 0
- k3s_version in k3s_current_version.stdout
# ── Upgrade Master ────────────────────────────────────────────────────────────
- name: Drain master node before upgrade
ansible.builtin.command: >
k3s kubectl drain {{ inventory_hostname }}
--ignore-daemonsets
--delete-emptydir-data
--timeout=120s
become: "{{ k3s_become }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
changed_when: true
when: inventory_hostname in groups['k3s_master']
- name: Upgrade K3S server binary
ansible.builtin.shell: |
set -o pipefail
curl -sfL https://get.k3s.io | \
INSTALL_K3S_VERSION="{{ k3s_version }}" \
INSTALL_K3S_EXEC="server" \
K3S_TOKEN="{{ k3s_token }}" \
sh -
args:
executable: /bin/bash
become: "{{ k3s_become }}"
when: inventory_hostname in groups['k3s_master']
- name: Wait for master to be ready after upgrade
ansible.builtin.command: >
k3s kubectl get node {{ inventory_hostname }} -o jsonpath='{.status.conditions[-1].type}'
become: "{{ k3s_become }}"
register: master_ready
until: master_ready.stdout == "Ready"
retries: 24
delay: 10
changed_when: false
when: inventory_hostname in groups['k3s_master']
- name: Uncordon master node
ansible.builtin.command: k3s kubectl uncordon {{ inventory_hostname }}
become: "{{ k3s_become }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
changed_when: true
when: inventory_hostname in groups['k3s_master']
# ── Upgrade Workers (serial) ──────────────────────────────────────────────────
- name: Drain worker node before upgrade
ansible.builtin.command: >
k3s kubectl drain {{ inventory_hostname }}
--ignore-daemonsets
--delete-emptydir-data
--timeout=180s
become: "{{ k3s_become }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
changed_when: true
when: inventory_hostname in groups['k3s_workers']
- name: Upgrade K3S agent binary
ansible.builtin.shell: |
set -o pipefail
curl -sfL https://get.k3s.io | \
INSTALL_K3S_VERSION="{{ k3s_version }}" \
INSTALL_K3S_EXEC="agent" \
K3S_URL="{{ k3s_api_url }}" \
K3S_TOKEN="{{ hostvars[groups['k3s_master'][0]]['k3s_node_token'] }}" \
sh -
args:
executable: /bin/bash
become: "{{ k3s_become }}"
when: inventory_hostname in groups['k3s_workers']
- name: Wait for worker to rejoin after upgrade
ansible.builtin.command: >
k3s kubectl get node {{ inventory_hostname }} -o jsonpath='{.status.conditions[-1].type}'
become: "{{ k3s_become }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
register: worker_ready
until: worker_ready.stdout == "Ready"
retries: 24
delay: 10
changed_when: false
when: inventory_hostname in groups['k3s_workers']
- name: Uncordon worker node
ansible.builtin.command: k3s kubectl uncordon {{ inventory_hostname }}
become: "{{ k3s_become }}"
delegate_to: "{{ groups['k3s_master'][0] }}"
changed_when: true
when: inventory_hostname in groups['k3s_workers']
- name: Verify new version
ansible.builtin.command: k3s --version
register: k3s_new_version
become: "{{ k3s_become }}"
changed_when: false
- name: Show upgraded version
ansible.builtin.debug:
msg: "✓ {{ inventory_hostname }} upgraded to: {{ k3s_new_version.stdout }}"

View File

@@ -0,0 +1,19 @@
# K3S Agent Configuration
# Generated by Ansible — do not edit manually
server: "{{ k3s_api_url }}"
token: "{{ k3s_node_token }}"
# Node name
node-name: "{{ inventory_hostname }}"
{% if ansible_architecture in ['armv7l', 'aarch64'] %}
# Raspberry Pi / ARM — use container runtime args for stability
kubelet-arg:
- "feature-gates=MemoryManager=false"
{% endif %}
{% if k3s_extra_agent_args %}
# Extra args
{{ k3s_extra_agent_args }}
{% endif %}

View File

@@ -0,0 +1,41 @@
# K3S Server Configuration
# Generated by Ansible — do not edit manually
token: "{{ k3s_token }}"
cluster-cidr: "{{ k3s_cluster_cidr }}"
service-cidr: "{{ k3s_service_cidr }}"
cluster-dns: "{{ k3s_cluster_dns }}"
{% if k3s_cni | default('flannel') == 'flannel' %}
flannel-backend: "{{ k3s_flannel_backend }}"
{% else %}
flannel-backend: "none"
disable-network-policy: true
{% endif %}
write-kubeconfig-mode: "0644"
# HA embedded etcd: первый сервер инициализирует кластер, остальные присоединяются
{% if inventory_hostname == groups['k3s_master'][0] %}
cluster-init: true
{% else %}
server: "https://{{ hostvars[groups['k3s_master'][0]]['ansible_host'] }}:6443"
{% endif %}
{% if k3s_disable_traefik or k3s_disable_servicelb or k3s_disable_local_storage %}
disable:
{% if k3s_disable_traefik %}
- traefik
{% endif %}
{% if k3s_disable_servicelb %}
- servicelb
{% endif %}
{% if k3s_disable_local_storage %}
- local-storage
{% endif %}
{% endif %}
{% if k3s_extra_server_args %}
{{ k3s_extra_server_args }}
{% endif %}
node-name: "{{ inventory_hostname }}"

View File

@@ -0,0 +1,27 @@
---
# Версия kube-vip
kube_vip_version: "v0.8.3"
# VIP — виртуальный IP для control plane (ОБЯЗАТЕЛЬНО задать!)
kube_vip_address: "192.168.1.100"
# Сетевой интерфейс на master-ноде.
# Оставь пустым для автоопределения (ansible_default_ipv4.interface).
# Переопредели если авто не работает: kube_vip_interface: "eth0"
kube_vip_interface: ""
# Режим работы: ARP (L2, для большинства домашних сетей) или BGP (L3)
kube_vip_mode: "arp" # arp | bgp
# Включить kube-vip как LoadBalancer для Services (вместо metalLB)
kube_vip_services_enable: true
# RBAC манифест
kube_vip_rbac_url: "https://kube-vip.io/manifests/rbac.yaml"
# Образ kube-vip
kube_vip_image: "ghcr.io/kube-vip/kube-vip"
# Путь для статического пода
kube_vip_manifest_dir: /var/lib/rancher/k3s/server/manifests
kube_vip_pod_manifest: "{{ kube_vip_manifest_dir }}/kube-vip.yaml"

View File

@@ -0,0 +1,7 @@
---
- name: Restart K3S server
ansible.builtin.systemd:
name: k3s
state: restarted
daemon_reload: true
become: true

View File

@@ -0,0 +1,8 @@
---
galaxy_info:
author: "your-name"
description: "Deploy kube-vip VIP for K3S control plane and LoadBalancer services"
license: "MIT"
min_ansible_version: "2.12"
dependencies:
- role: k3s

View File

@@ -0,0 +1,62 @@
---
- name: Resolve kube-vip network interface
ansible.builtin.set_fact:
_kube_vip_iface: "{{ kube_vip_interface if kube_vip_interface | length > 0 else ansible_default_ipv4.interface | default('eth0') }}"
- name: Validate kube_vip_address is set
ansible.builtin.assert:
that:
- kube_vip_address is defined
- kube_vip_address != ""
- kube_vip_address != "192.168.1.100"
fail_msg: >
Задай kube_vip_address в group_vars/all/main.yml!
Например: kube_vip_address: "192.168.1.50"
quiet: true
tags: [kube_vip_validate]
- name: Ensure manifest directory exists
ansible.builtin.file:
path: "{{ kube_vip_manifest_dir }}"
state: directory
mode: '0755'
become: true
- name: Deploy kube-vip RBAC manifest
ansible.builtin.get_url:
url: "{{ kube_vip_rbac_url }}"
dest: "{{ kube_vip_manifest_dir }}/kube-vip-rbac.yaml"
mode: '0644'
force: true
become: true
retries: 3
delay: 5
- name: Template kube-vip DaemonSet manifest
ansible.builtin.template:
src: kube-vip-ds.yaml.j2
dest: "{{ kube_vip_manifest_dir }}/kube-vip.yaml"
mode: '0644'
become: true
notify: Restart K3S server
- name: Wait for kube-vip pods to be ready
ansible.builtin.command: >
k3s kubectl -n kube-system wait pod
-l app.kubernetes.io/name=kube-vip
--for=condition=Ready
--timeout=120s
become: true
register: kvip_wait
retries: 5
delay: 10
until: kvip_wait.rc == 0
changed_when: false
- name: Verify VIP is reachable
ansible.builtin.wait_for:
host: "{{ kube_vip_address }}"
port: 6443
timeout: 60
delegate_to: localhost
become: false

View File

@@ -0,0 +1,107 @@
---
# kube-vip DaemonSet — деплоится как auto-манифест K3S
# Сгенерировано Ansible (roles/kube-vip)
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: kube-vip-ds
namespace: kube-system
labels:
app.kubernetes.io/name: kube-vip
app.kubernetes.io/version: "{{ kube_vip_version }}"
spec:
selector:
matchLabels:
app.kubernetes.io/name: kube-vip
updateStrategy:
type: RollingUpdate
template:
metadata:
labels:
app.kubernetes.io/name: kube-vip
app.kubernetes.io/version: "{{ kube_vip_version }}"
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node-role.kubernetes.io/control-plane
operator: Exists
tolerations:
- effect: NoSchedule
operator: Exists
- effect: NoExecute
operator: Exists
hostNetwork: true
serviceAccountName: kube-vip
containers:
- name: kube-vip
image: "{{ kube_vip_image }}:{{ kube_vip_version }}"
imagePullPolicy: IfNotPresent
args:
- manager
env:
- name: vip_arp
value: "{{ 'true' if kube_vip_mode == 'arp' else 'false' }}"
- name: vip_interface
value: "{{ _kube_vip_iface }}"
- name: vip_address
value: "{{ kube_vip_address }}"
- name: port
value: "6443"
- name: vip_cidr
value: "32"
- name: cp_enable
value: "true"
- name: cp_namespace
value: "kube-system"
- name: vip_ddns
value: "false"
- name: svc_enable
value: "{{ 'true' if kube_vip_services_enable else 'false' }}"
- name: svc_leasename
value: "plndr-svcs-lock"
- name: vip_leaderelection
value: "true"
- name: vip_leasename
value: "plndr-cp-lock"
- name: vip_leaseduration
value: "5"
- name: vip_renewdeadline
value: "3"
- name: vip_retryperiod
value: "1"
- name: prometheus_server
value: ":2112"
securityContext:
capabilities:
add:
- NET_ADMIN
- NET_RAW
- SYS_TIME
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
livenessProbe:
httpGet:
path: /healthz
port: 2112
initialDelaySeconds: 15
periodSeconds: 20
{% if kube_vip_mode == 'bgp' %}
- name: bgp_enable
value: "true"
- name: bgp_routerid
value: "{{ ansible_default_ipv4.address }}"
- name: bgp_as
value: "{{ kube_vip_bgp_as | default('65000') }}"
- name: bgp_peeraddress
value: "{{ kube_vip_bgp_peer | default('') }}"
- name: bgp_peeras
value: "{{ kube_vip_bgp_peer_as | default('65000') }}"
{% endif %}

View File

@@ -0,0 +1,21 @@
---
# NFS экспорты — список точек монтирования
nfs_exports:
- path: /srv/nfs/k8s
options: "*(rw,sync,no_subtree_check,no_root_squash)"
# Разрешённая подсеть для NFS (для firewall)
nfs_allowed_network: "192.168.1.0/24"
# Пакеты NFS сервера
nfs_server_packages:
- nfs-kernel-server
- nfs-common
# Создавать директории если не существуют
nfs_create_export_dirs: true
# Права на экспортируемые директории
nfs_export_dir_mode: "0777"
nfs_export_dir_owner: "nobody"
nfs_export_dir_group: "nogroup"

View File

@@ -0,0 +1,11 @@
---
- name: Re-export NFS shares
ansible.builtin.command: exportfs -ra
become: true
changed_when: true
- name: Restart NFS server
ansible.builtin.systemd:
name: nfs-kernel-server
state: restarted
become: true

View File

@@ -0,0 +1,7 @@
---
galaxy_info:
author: "your-name"
description: "Configure NFS server for Kubernetes persistent storage"
license: "MIT"
min_ansible_version: "2.12"
dependencies: []

View File

@@ -0,0 +1,58 @@
---
- name: Install NFS server packages
ansible.builtin.apt:
name: "{{ nfs_server_packages }}"
state: present
update_cache: true
become: true
- name: Create NFS export directories
ansible.builtin.file:
path: "{{ item.path }}"
state: directory
mode: "{{ nfs_export_dir_mode }}"
owner: "{{ nfs_export_dir_owner }}"
group: "{{ nfs_export_dir_group }}"
loop: "{{ nfs_exports }}"
become: true
when: nfs_create_export_dirs
- name: Configure /etc/exports
ansible.builtin.template:
src: exports.j2
dest: /etc/exports
mode: '0644'
backup: true
become: true
notify:
- Re-export NFS shares
- Restart NFS server
- name: Enable and start NFS server
ansible.builtin.systemd:
name: nfs-kernel-server
enabled: true
state: started
become: true
- name: Allow NFS through UFW (if active)
community.general.ufw:
rule: allow
src: "{{ nfs_allowed_network }}"
port: "{{ item }}"
proto: tcp
loop:
- "2049" # NFS
- "111" # RPC portmapper
become: true
failed_when: false # UFW может быть не установлен
- name: Verify NFS exports are active
ansible.builtin.command: exportfs -v
register: nfs_exportfs
become: true
changed_when: false
- name: Show active NFS exports
ansible.builtin.debug:
msg: "{{ nfs_exportfs.stdout_lines }}"

View File

@@ -0,0 +1,9 @@
# /etc/exports — управляется Ansible (roles/nfs-server)
# Изменения вручную будут перезаписаны!
#
# Формат: <директория> <опции>
# Документация: man exports
{% for export in nfs_exports %}
{{ export.path }} {{ export.options }}
{% endfor %}

View File

@@ -0,0 +1,63 @@
---
# Включить установку kube-prometheus-stack (false = пропустить)
prometheus_stack_enabled: false
prometheus_stack_version: "60.3.0" # Helm chart version
prometheus_stack_namespace: "monitoring"
prometheus_stack_release_name: "prom"
prometheus_stack_chart_repo: "https://prometheus-community.github.io/helm-charts"
prometheus_stack_chart_name: "kube-prometheus-stack"
# Grafana
prometheus_grafana_enabled: true
# Логин и пароль администратора Grafana.
# Рекомендуется задавать через Ansible Vault:
# group_vars/all/vault.yml:
# vault_grafana_user: "admin"
# vault_grafana_password: "ваш-пароль"
grafana_admin_user: "{{ vault_grafana_user | default('admin') }}"
prometheus_grafana_admin_password: "{{ vault_grafana_password | default('admin') }}"
prometheus_grafana_ingress_enabled: false
prometheus_grafana_ingress_host: "grafana.local"
prometheus_grafana_ingress_class: "nginx"
# Prometheus
prometheus_retention_days: 7
prometheus_storage_size: "10Gi" # Размер PVC для данных Prometheus
prometheus_storage_class: "" # "" = использовать default StorageClass (nfs-client)
# Grafana PVC
grafana_storage_enabled: true
grafana_storage_size: "5Gi" # Размер PVC для дашбордов и плагинов Grafana
grafana_storage_class: "" # "" = использовать default StorageClass
# Alertmanager
prometheus_alertmanager_enabled: true
prometheus_alertmanager_storage_size: "2Gi"
# Node exporter (метрики хостов)
prometheus_node_exporter_enabled: true
# kube-state-metrics
prometheus_kube_state_metrics_enabled: true
# Ресурсы Prometheus
prometheus_resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
# Ресурсы Grafana
grafana_resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi

View File

@@ -0,0 +1,6 @@
---
galaxy_info:
role_name: prometheus-stack
description: Deploy kube-prometheus-stack (Prometheus + Grafana + Alertmanager) via Helm on K3S
min_ansible_version: "2.14"
dependencies: []

View File

@@ -0,0 +1,54 @@
---
- name: Converge — prometheus-stack template tests
hosts: all
become: false
gather_facts: false
vars:
prometheus_stack_enabled: true
prometheus_stack_namespace: "monitoring"
prometheus_stack_release_name: "prom"
prometheus_stack_chart_name: "kube-prometheus-stack"
prometheus_grafana_enabled: true
grafana_admin_user: "admin"
prometheus_grafana_admin_password: "molecule-test-pass"
prometheus_grafana_ingress_enabled: false
prometheus_grafana_ingress_host: "grafana.local"
prometheus_grafana_ingress_class: "nginx"
grafana_storage_enabled: true
grafana_storage_size: "5Gi"
grafana_storage_class: ""
prometheus_retention_days: 7
prometheus_storage_size: "10Gi"
prometheus_storage_class: ""
prometheus_alertmanager_enabled: true
prometheus_alertmanager_storage_size: "2Gi"
prometheus_node_exporter_enabled: true
prometheus_kube_state_metrics_enabled: true
prometheus_resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 2Gi
grafana_resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 300m
memory: 256Mi
tasks:
- name: Render kube-prometheus-stack Helm values
ansible.builtin.template:
src: "{{ playbook_dir }}/../../templates/prometheus-stack-values.yaml.j2"
dest: /tmp/prometheus-stack-values.yaml
mode: '0644'

View File

@@ -0,0 +1,25 @@
---
driver:
name: docker
platforms:
- name: prom-test
image: geerlingguy/docker-ubuntu2204-ansible:latest
pre_build_image: true
provisioner:
name: ansible
playbooks:
converge: converge.yml
verify: verify.yml
config_options:
defaults:
interpreter_python: auto_silent
verifier:
name: ansible
lint: |
set -e
yamllint .
ansible-lint

View File

@@ -0,0 +1,80 @@
---
- name: Verify — prometheus-stack templates
hosts: all
become: false
gather_facts: false
tasks:
- name: Read rendered Helm values
ansible.builtin.slurp:
src: /tmp/prometheus-stack-values.yaml
register: values_raw
- name: Parse YAML
ansible.builtin.set_fact:
v: "{{ values_raw.content | b64decode | from_yaml }}"
# ── Grafana ─────────────────────────────────────────────────────────────────
- name: Assert grafana block exists
ansible.builtin.assert:
that: v.grafana is defined
fail_msg: "Блок grafana отсутствует в values"
- name: Assert grafana adminUser
ansible.builtin.assert:
that: v.grafana.adminUser == 'admin'
fail_msg: "grafana.adminUser неверный: {{ v.grafana.adminUser }}"
- name: Assert grafana adminPassword is set
ansible.builtin.assert:
that: v.grafana.adminPassword | length > 0
fail_msg: "grafana.adminPassword не задан"
- name: Assert grafana persistence is enabled
ansible.builtin.assert:
that:
- v.grafana.persistence is defined
- v.grafana.persistence.enabled == true
- v.grafana.persistence.size == '5Gi'
fail_msg: "grafana.persistence настроена неверно: {{ v.grafana.persistence }}"
# ── Prometheus ──────────────────────────────────────────────────────────────
- name: Assert prometheus block exists
ansible.builtin.assert:
that: v.prometheus.prometheusSpec is defined
fail_msg: "Блок prometheus.prometheusSpec отсутствует"
- name: Assert prometheus retention
ansible.builtin.assert:
that: v.prometheus.prometheusSpec.retention == '7d'
fail_msg: "Неверный retention: {{ v.prometheus.prometheusSpec.retention }}"
- name: Assert prometheus PVC storage
ansible.builtin.assert:
that:
- v.prometheus.prometheusSpec.storageSpec is defined
- v.prometheus.prometheusSpec.storageSpec.volumeClaimTemplate.spec.resources.requests.storage == '10Gi'
fail_msg: "Prometheus PVC настроен неверно"
# ── Alertmanager ────────────────────────────────────────────────────────────
- name: Assert alertmanager is enabled
ansible.builtin.assert:
that: v.alertmanager.enabled == true
fail_msg: "alertmanager.enabled должен быть true"
- name: Assert alertmanager storage
ansible.builtin.assert:
that:
- v.alertmanager.alertmanagerSpec.storage is defined
- v.alertmanager.alertmanagerSpec.storage.volumeClaimTemplate.spec.resources.requests.storage == '2Gi'
fail_msg: "Alertmanager PVC настроен неверно"
# ── Node Exporter & kube-state-metrics ──────────────────────────────────────
- name: Assert nodeExporter enabled
ansible.builtin.assert:
that: v.nodeExporter.enabled == true
fail_msg: "nodeExporter.enabled должен быть true"
- name: Summary
ansible.builtin.debug:
msg: "Все проверки prometheus-stack прошли успешно"

View File

@@ -0,0 +1,112 @@
---
- name: Prometheus Stack — skip if not enabled
ansible.builtin.debug:
msg: "kube-prometheus-stack отключён (prometheus_stack_enabled: false). Пропускаем."
when: not prometheus_stack_enabled
run_once: true
- name: Prometheus Stack — install
when: prometheus_stack_enabled
block:
- name: Install Helm (if needed)
ansible.builtin.include_tasks: "{{ playbook_dir }}/../roles/csi-nfs/tasks/install_helm.yml"
- name: Add prometheus-community Helm repo
kubernetes.core.helm_repository:
name: prometheus-community
repo_url: "{{ prometheus_stack_chart_repo }}"
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Update Helm repos
ansible.builtin.command: helm repo update
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Create monitoring namespace
ansible.builtin.command: >
k3s kubectl create namespace {{ prometheus_stack_namespace }}
--dry-run=client -o yaml | k3s kubectl apply -f -
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
changed_when: false
- name: Template kube-prometheus-stack values
ansible.builtin.template:
src: prometheus-stack-values.yaml.j2
dest: /tmp/prometheus-stack-values.yaml
mode: '0644'
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
- name: Deploy kube-prometheus-stack via Helm
kubernetes.core.helm:
name: "{{ prometheus_stack_release_name }}"
chart_ref: "prometheus-community/{{ prometheus_stack_chart_name }}"
chart_version: "{{ prometheus_stack_version }}"
release_namespace: "{{ prometheus_stack_namespace }}"
create_namespace: true
wait: true
timeout: "10m0s"
values_files:
- /tmp/prometheus-stack-values.yaml
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
environment:
KUBECONFIG: /etc/rancher/k3s/k3s.yaml
- name: Wait for Prometheus to be ready
ansible.builtin.command: >
k3s kubectl -n {{ prometheus_stack_namespace }}
rollout status deployment/{{ prometheus_stack_release_name }}-grafana
--timeout=180s
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: grafana_ready
changed_when: false
retries: 3
delay: 15
until: grafana_ready.rc == 0
- name: Get Grafana admin password (from secret)
ansible.builtin.command: >
k3s kubectl -n {{ prometheus_stack_namespace }}
get secret {{ prometheus_stack_release_name }}-grafana
-o jsonpath="{.data.admin-password}"
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: grafana_secret
changed_when: false
- name: Show Grafana access info
ansible.builtin.debug:
msg:
- "Grafana URL: http://{{ hostvars[groups['k3s_master'][0]]['ansible_host'] }}:3000 (NodePort) или через Ingress"
- "Admin user: admin"
- "Admin password: {{ grafana_secret.stdout | b64decode }}"
run_once: true
- name: Show monitoring namespace pods
ansible.builtin.command: >
k3s kubectl -n {{ prometheus_stack_namespace }} get pods
become: true
delegate_to: "{{ groups['k3s_master'][0] }}"
run_once: true
register: prom_pods
changed_when: false
- name: Monitoring pods
ansible.builtin.debug:
msg: "{{ prom_pods.stdout_lines }}"
run_once: true

View File

@@ -0,0 +1,119 @@
## kube-prometheus-stack Helm values
## Управляется Ansible (roles/prometheus-stack)
grafana:
enabled: {{ prometheus_grafana_enabled | lower }}
adminUser: "{{ grafana_admin_user }}"
adminPassword: "{{ prometheus_grafana_admin_password }}"
resources:
requests:
cpu: "{{ grafana_resources.requests.cpu }}"
memory: "{{ grafana_resources.requests.memory }}"
limits:
cpu: "{{ grafana_resources.limits.cpu }}"
memory: "{{ grafana_resources.limits.memory }}"
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
{% if prometheus_grafana_ingress_enabled %}
ingress:
enabled: true
ingressClassName: "{{ prometheus_grafana_ingress_class }}"
hosts:
- "{{ prometheus_grafana_ingress_host }}"
paths:
- /
{% else %}
service:
type: NodePort
nodePort: 32000
{% endif %}
# Готовые дашборды
defaultDashboardsEnabled: true
defaultDashboardsTimezone: utc
persistence:
enabled: {{ grafana_storage_enabled | lower }}
type: pvc
accessModes:
- ReadWriteOnce
size: "{{ grafana_storage_size }}"
{% if grafana_storage_class %}
storageClassName: "{{ grafana_storage_class }}"
{% endif %}
prometheus:
prometheusSpec:
retention: "{{ prometheus_retention_days }}d"
resources:
requests:
cpu: "{{ prometheus_resources.requests.cpu }}"
memory: "{{ prometheus_resources.requests.memory }}"
limits:
cpu: "{{ prometheus_resources.limits.cpu }}"
memory: "{{ prometheus_resources.limits.memory }}"
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
storageSpec:
volumeClaimTemplate:
spec:
{% if prometheus_storage_class %}
storageClassName: "{{ prometheus_storage_class }}"
{% endif %}
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: "{{ prometheus_storage_size }}"
# Собирать метрики со всех namespaces
serviceMonitorSelectorNilUsesHelmValues: false
podMonitorSelectorNilUsesHelmValues: false
ruleSelectorNilUsesHelmValues: false
alertmanager:
enabled: {{ prometheus_alertmanager_enabled | lower }}
alertmanagerSpec:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
storage:
volumeClaimTemplate:
spec:
{% if prometheus_storage_class %}
storageClassName: "{{ prometheus_storage_class }}"
{% endif %}
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: "{{ prometheus_alertmanager_storage_size }}"
nodeExporter:
enabled: {{ prometheus_node_exporter_enabled | lower }}
# DaemonSet — запускается на всех нодах включая мастера
tolerations:
- operator: "Exists"
kube-state-metrics:
enabled: {{ prometheus_kube_state_metrics_enabled | lower }}
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"
# Не устанавливать дополнительный Prometheus Operator если уже есть
prometheusOperator:
tolerations:
- key: "node-role.kubernetes.io/control-plane"
operator: "Exists"
effect: "NoSchedule"