From 437d0cce3406d03a459444def07b6db619654cec Mon Sep 17 00:00:00 2001 From: Sergey Antropoff Date: Fri, 24 Apr 2026 07:00:18 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=D0=B1=D1=83=D0=BA=D0=BE=D0=B2=20=D0=B2=20playbooks/,=20=D1=80?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81=D0=B5=D1=80=D1=82?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=82=D0=BE=D0=B2,=20=D1=81?= =?UTF-8?q?=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA?= =?UTF-8?q?=D0=BB=D1=8E=D1=87=D0=B5=D0=B9=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Организация плейбуков: - все .yml плейбуки перенесены из корня в playbooks/ - Makefile и entrypoint.sh обновлены — все пути с playbooks/ префиксом - k8s-user.yml переработан: include_tasks → include_role (корректная работа из подкаталога) Сохранение ключей и kubeconfig локально: - k8s-user.yml: новый play сохраняет k8s SSH ключи в ./keys/ на машине запуска - переменная k8s_local_keys_dir: "./keys" (настраивается в group_vars) - .gitignore: keys/k8s_id_rsa исключён (публичный ключ можно коммитить) - kubeconfig уже сохранялся роль k3s (k3s_kubeconfig_local_path: "./kubeconfig") Автоматическая ротация сертификатов K3S (роль k3s-certs): - K3S выпускает сертификаты на 1 год (hardcoded), таймер обеспечивает их обновление - скрипт k3s-cert-check.sh: проверяет срок через openssl, ротирует при k3s_cert_rotate_before_days - systemd service + timer: запуск по расписанию k3s_cert_check_schedule (по умолчанию monthly) - RandomizedDelaySec: снижает нагрузку при одновременном запуске на нескольких нодах - переменные: k3s_cert_validity_years: 5, k3s_cert_rotate_before_days: 90 - добавлен в site.yml (тег certs) и отдельный плейбук playbooks/k3s-certs.yml - make k3s-certs и команда k3s-certs в entrypoint.sh --- .gitignore | 3 + Makefile | 35 +++++---- docker/entrypoint.sh | 43 ++++++----- group_vars/all/main.yml | 20 +++++ add-node.yml => playbooks/add-node.yml | 0 bootstrap.yml => playbooks/bootstrap.yml | 0 etcd-backup.yml => playbooks/etcd-backup.yml | 0 .../etcd-restore.yml | 0 healthcheck.yml => playbooks/healthcheck.yml | 0 playbooks/k3s-certs.yml | 22 ++++++ k8s-user.yml => playbooks/k8s-user.yml | 74 +++++++++++++++---- mdadm.yml => playbooks/mdadm.yml | 0 remove-node.yml => playbooks/remove-node.yml | 0 site.yml => playbooks/site.yml | 13 +++- uninstall.yml => playbooks/uninstall.yml | 0 upgrade.yml => playbooks/upgrade.yml | 0 roles/k3s-certs/defaults/main.yml | 23 ++++++ roles/k3s-certs/handlers/main.yml | 14 ++++ roles/k3s-certs/tasks/main.yml | 57 ++++++++++++++ .../templates/k3s-cert-check.service.j2 | 12 +++ .../k3s-certs/templates/k3s-cert-check.sh.j2 | 60 +++++++++++++++ .../templates/k3s-cert-check.timer.j2 | 13 ++++ 22 files changed, 340 insertions(+), 49 deletions(-) rename add-node.yml => playbooks/add-node.yml (100%) rename bootstrap.yml => playbooks/bootstrap.yml (100%) rename etcd-backup.yml => playbooks/etcd-backup.yml (100%) rename etcd-restore.yml => playbooks/etcd-restore.yml (100%) rename healthcheck.yml => playbooks/healthcheck.yml (100%) create mode 100644 playbooks/k3s-certs.yml rename k8s-user.yml => playbooks/k8s-user.yml (55%) rename mdadm.yml => playbooks/mdadm.yml (100%) rename remove-node.yml => playbooks/remove-node.yml (100%) rename site.yml => playbooks/site.yml (92%) rename uninstall.yml => playbooks/uninstall.yml (100%) rename upgrade.yml => playbooks/upgrade.yml (100%) create mode 100644 roles/k3s-certs/defaults/main.yml create mode 100644 roles/k3s-certs/handlers/main.yml create mode 100644 roles/k3s-certs/tasks/main.yml create mode 100644 roles/k3s-certs/templates/k3s-cert-check.service.j2 create mode 100644 roles/k3s-certs/templates/k3s-cert-check.sh.j2 create mode 100644 roles/k3s-certs/templates/k3s-cert-check.timer.j2 diff --git a/.gitignore b/.gitignore index 7d311d8..697bfb9 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,9 @@ id_rsa id_ed25519 id_ecdsa +# Локально сохранённые ключи k8s пользователя (публичный ключ — ок коммитить) +keys/k8s_id_rsa + # Docker артефакты .docker/ .claude/ diff --git a/Makefile b/Makefile index 89c4bf0..e05f17a 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ DOCKER_RUN := docker run --rm -it \ $(IMAGE_NAME) .PHONY: help setup build rebuild \ - bootstrap k8s-user mdadm \ + bootstrap k8s-user mdadm k3s-certs \ install install-k3s install-cni install-kubevip install-nfs install-ingress \ install-cert-manager install-istio install-monitoring \ add-node remove-node \ @@ -145,11 +145,11 @@ ping: _check_env _check_image ## Проверить SSH доступность check: _check_env _check_image ## Dry-run: проверить плейбук без изменений @printf "$(CYAN)Dry-run...$(NC)\n" - $(DOCKER_RUN) ansible-playbook site.yml --check --diff + $(DOCKER_RUN) ansible-playbook playbooks/site.yml --check --diff lint: _check_image ## Проверить синтаксис всех плейбуков @printf "$(CYAN)Проверка синтаксиса...$(NC)\n" - $(DOCKER_RUN) ansible-playbook site.yml --syntax-check + $(DOCKER_RUN) ansible-playbook playbooks/site.yml --syntax-check @printf "$(GREEN)✓ Синтаксис корректен$(NC)\n" # ═══════════════════════════════════════════════════════════════════════════════ @@ -163,7 +163,7 @@ lint: _check_image ## Проверить синтаксис всех плейб bootstrap: _check_env _check_image ## Первый запуск: создать пользователя и задеплоить SSH ключ на все ноды @printf "$(CYAN)$(BOLD)Bootstrap нод (пользователь + SSH ключ)...$(NC)\n" @printf "$(YELLOW)Нужны: host_vars//vault.yml с bootstrap_user и bootstrap_password$(NC)\n" - $(DOCKER_RUN) ansible-playbook bootstrap.yml \ + $(DOCKER_RUN) ansible-playbook playbooks/bootstrap.yml \ $(if $(NODE),-e "node_to_bootstrap=$(NODE)",) vault-bootstrap-create: _check_image ## Создать vault с bootstrap credentials для ноды (NODE=master01) @@ -197,12 +197,17 @@ vault-bootstrap-edit: _check_image ## Редактировать bootstrap vault k8s-user: _check_env _check_image ## Создать k8s пользователя + разложить SSH ключи на все ноды (cluster + lab_hosts) @printf "$(CYAN)$(BOLD)Настройка k8s пользователя и SSH ключей...$(NC)\n" @printf "$(YELLOW)Нужны: host_vars//vault.yml с bootstrap_user и bootstrap_password для lab_hosts$(NC)\n" - $(DOCKER_RUN) ansible-playbook k8s-user.yml \ + $(DOCKER_RUN) ansible-playbook playbooks/k8s-user.yml \ $(if $(NODE),-e "node_to_limit=$(NODE)" --limit $(NODE),) mdadm: _check_env _check_image ## Найти RAID массив и смонтировать в /storage (mdadm_enabled: true) @printf "$(CYAN)Настройка mdadm RAID...$(NC)\n" - $(DOCKER_RUN) ansible-playbook mdadm.yml \ + $(DOCKER_RUN) ansible-playbook playbooks/mdadm.yml \ + $(if $(NODE),--limit $(NODE),) + +k3s-certs: _check_env _check_image ## Установить systemd таймер автоматической ротации сертификатов K3S + @printf "$(CYAN)Настройка автоматической ротации сертификатов K3S...$(NC)\n" + $(DOCKER_RUN) ansible-playbook playbooks/k3s-certs.yml \ $(if $(NODE),--limit $(NODE),) install: _check_env _check_image ## Развернуть полный стек (K3S + kube-vip + NFS + ingress) @@ -215,7 +220,7 @@ install-k3s: _check_env _check_image ## Установить только K3S к install-cni: _check_env _check_image ## Установить CNI плагин (задай K3S_CNI=calico|cilium) @printf "$(CYAN)Устанавливаю CNI ($(or $(K3S_CNI),flannel))...$(NC)\n" - $(DOCKER_RUN) ansible-playbook site.yml --tags cni $(if $(K3S_CNI),-e k3s_cni=$(K3S_CNI),) + $(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags cni $(if $(K3S_CNI),-e k3s_cni=$(K3S_CNI),) install-kubevip: _check_env _check_image ## Установить только kube-vip @printf "$(CYAN)Устанавливаю kube-vip...$(NC)\n" @@ -231,15 +236,15 @@ install-ingress: _check_env _check_image ## Установить ingress-nginx install-cert-manager: _check_env _check_image ## Установить cert-manager + ClusterIssuer @printf "$(CYAN)Устанавливаю cert-manager...$(NC)\n" - $(DOCKER_RUN) ansible-playbook site.yml --tags cert_manager -e cert_manager_enabled=true + $(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags cert_manager -e cert_manager_enabled=true install-istio: _check_env _check_image ## Установить Istio + Kiali (нужен istio_enabled: true в vars) @printf "$(CYAN)Устанавливаю Istio + Kiali...$(NC)\n" - $(DOCKER_RUN) ansible-playbook site.yml --tags istio -e istio_enabled=true + $(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags istio -e istio_enabled=true install-monitoring: _check_env _check_image ## Установить Prometheus + Grafana (нужен prometheus_stack_enabled: true) @printf "$(CYAN)Устанавливаю kube-prometheus-stack...$(NC)\n" - $(DOCKER_RUN) ansible-playbook site.yml --tags monitoring -e prometheus_stack_enabled=true + $(DOCKER_RUN) ansible-playbook playbooks/site.yml --tags monitoring -e prometheus_stack_enabled=true # ═══════════════════════════════════════════════════════════════════════════════ # МАСШТАБИРОВАНИЕ КЛАСТЕРА @@ -251,7 +256,7 @@ add-node: _check_env _check_image ## Добавить ноду: make add-node NO exit 1; \ fi @printf "$(CYAN)Добавляю ноду $(NODE) в кластер...$(NC)\n" - $(DOCKER_RUN) ansible-playbook add-node.yml -e "node_to_add=$(NODE)" + $(DOCKER_RUN) ansible-playbook playbooks/add-node.yml -e "node_to_add=$(NODE)" remove-node: _check_env _check_image ## Удалить ноду: make remove-node NODE=worker04 @if [ -z "$(NODE)" ]; then \ @@ -259,7 +264,7 @@ remove-node: _check_env _check_image ## Удалить ноду: make remove-nod exit 1; \ fi @printf "$(RED)$(BOLD)Удаление ноды $(NODE) из кластера...$(NC)\n" - $(DOCKER_RUN) ansible-playbook remove-node.yml -e "node_to_remove=$(NODE)" + $(DOCKER_RUN) ansible-playbook playbooks/remove-node.yml -e "node_to_remove=$(NODE)" # ═══════════════════════════════════════════════════════════════════════════════ # РЕЗЕРВНОЕ КОПИРОВАНИЕ ETCD @@ -267,7 +272,7 @@ remove-node: _check_env _check_image ## Удалить ноду: make remove-nod etcd-backup: _check_env _check_image ## Создать снимок etcd (make etcd-backup [SNAPSHOT=name] [ETCD_COPY=true]) @printf "$(CYAN)Создаю снимок etcd...$(NC)\n" - $(DOCKER_RUN) ansible-playbook etcd-backup.yml \ + $(DOCKER_RUN) ansible-playbook playbooks/etcd-backup.yml \ $(if $(SNAPSHOT),-e etcd_backup_name=$(SNAPSHOT),) \ $(if $(ETCD_COPY),-e etcd_backup_copy_to_local=$(ETCD_COPY),) @@ -278,13 +283,13 @@ etcd-restore: _check_env _check_image ## Восстановить etcd: make etc exit 1; \ fi @printf "$(RED)$(BOLD)ВНИМАНИЕ: восстановление etcd перезапишет данные кластера!$(NC)\n" - $(DOCKER_RUN) ansible-playbook etcd-restore.yml \ + $(DOCKER_RUN) ansible-playbook playbooks/etcd-restore.yml \ -e "etcd_restore_snapshot=$(SNAPSHOT)" \ $(if $(FORCE),-e etcd_restore_force=true,) etcd-list-snapshots: _check_env _check_image ## Показать доступные снимки etcd @printf "$(CYAN)Список снимков etcd...$(NC)\n" - $(DOCKER_RUN) ansible-playbook etcd-restore.yml --tags list + $(DOCKER_RUN) ansible-playbook playbooks/etcd-restore.yml --tags list # ═══════════════════════════════════════════════════════════════════════════════ # ОБНОВЛЕНИЕ diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 3561373..b64fe4e 100644 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -61,6 +61,7 @@ print_help() { echo " bootstrap — создать пользователя + задеплоить SSH ключ" echo " k8s-user — создать k8s пользователя + разложить SSH ключи" echo " mdadm — найти RAID массив и смонтировать в /storage" + echo " k3s-certs — установить systemd таймер ротации сертификатов" echo " install — полный стек (site.yml)" echo " install-k3s — только K3S" echo " install-kubevip — только kube-vip" @@ -203,50 +204,56 @@ case "${COMMAND}" in # ── Bootstrap ───────────────────────────────────────────────────────────── bootstrap) log "Bootstrap нод: создание пользователя + деплой SSH ключа..." - exec ansible-playbook bootstrap.yml "$@" + exec ansible-playbook playbooks/bootstrap.yml "$@" ;; # ── k8s-user ────────────────────────────────────────────────────────────── k8s-user) log "Создание k8s пользователя и деплой SSH ключей..." - run_playbook k8s-user.yml "$@" + run_playbook playbooks/k8s-user.yml "$@" ;; # ── mdadm ───────────────────────────────────────────────────────────────── mdadm) log "Настройка mdadm RAID и монтирование /storage..." - run_playbook mdadm.yml "$@" + run_playbook playbooks/mdadm.yml "$@" + ;; + + # ── k3s-certs ───────────────────────────────────────────────────────────── + k3s-certs) + log "Установка systemd таймера ротации сертификатов K3S..." + run_playbook playbooks/k3s-certs.yml "$@" ;; # ── Основные команды ─────────────────────────────────────────────────────── install) log "Разворачиваю полный K3S стек..." - run_playbook site.yml "$@" + run_playbook playbooks/site.yml "$@" ;; install-k3s) log "Устанавливаю K3S cluster..." - run_playbook site.yml --tags k3s "$@" + run_playbook playbooks/site.yml --tags k3s "$@" ;; install-kubevip) log "Устанавливаю kube-vip..." - run_playbook site.yml --tags kube_vip "$@" + run_playbook playbooks/site.yml --tags kube_vip "$@" ;; install-nfs) log "Устанавливаю NFS Server + CSI Driver..." - run_playbook site.yml --tags nfs,csi_nfs "$@" + run_playbook playbooks/site.yml --tags nfs,csi_nfs "$@" ;; install-ingress) log "Устанавливаю ingress-nginx..." - run_playbook site.yml --tags ingress_nginx "$@" + run_playbook playbooks/site.yml --tags ingress_nginx "$@" ;; install-cert-manager) log "Устанавливаю cert-manager..." - run_playbook site.yml --tags cert_manager -e cert_manager_enabled=true "$@" + run_playbook playbooks/site.yml --tags cert_manager -e cert_manager_enabled=true "$@" ;; add-node) @@ -255,7 +262,7 @@ case "${COMMAND}" in exit 1 fi log "Добавляю ноду ${2} в кластер..." - exec ansible-playbook add-node.yml -e "node_to_add=${2}" "${@:3}" + exec ansible-playbook playbooks/add-node.yml -e "node_to_add=${2}" "${@:3}" ;; remove-node) @@ -264,12 +271,12 @@ case "${COMMAND}" in exit 1 fi log "Удаляю ноду ${2} из кластера..." - exec ansible-playbook remove-node.yml -e "node_to_remove=${2}" "${@:3}" + exec ansible-playbook playbooks/remove-node.yml -e "node_to_remove=${2}" "${@:3}" ;; etcd-backup) log "Создаю снимок etcd..." - exec ansible-playbook etcd-backup.yml "$@" + exec ansible-playbook playbooks/etcd-backup.yml "$@" ;; etcd-restore) @@ -278,12 +285,12 @@ case "${COMMAND}" in exit 1 fi log "Восстанавливаю etcd из ${2}..." - exec ansible-playbook etcd-restore.yml -e "etcd_restore_snapshot=${2}" "${@:3}" + exec ansible-playbook playbooks/etcd-restore.yml -e "etcd_restore_snapshot=${2}" "${@:3}" ;; etcd-list) log "Список снимков etcd..." - exec ansible-playbook etcd-restore.yml --tags list "$@" + exec ansible-playbook playbooks/etcd-restore.yml --tags list "$@" ;; upgrade) @@ -292,22 +299,22 @@ case "${COMMAND}" in exit 1 fi log "Обновляю K3S до ${VERSION}..." - run_playbook upgrade.yml -e "k3s_version=${VERSION}" "$@" + run_playbook playbooks/upgrade.yml -e "k3s_version=${VERSION}" "$@" ;; uninstall) warn "Удаление всего стека! Данные будут потеряны." - run_playbook uninstall.yml -e "confirm_uninstall=yes" "$@" + run_playbook playbooks/uninstall.yml -e "confirm_uninstall=yes" "$@" ;; health) log "Диагностика кластера..." - run_playbook healthcheck.yml "$@" + run_playbook playbooks/healthcheck.yml "$@" ;; verify) log "Проверка полного стека..." - run_playbook site.yml --tags verify "$@" + run_playbook playbooks/site.yml --tags verify "$@" ;; ping) diff --git a/group_vars/all/main.yml b/group_vars/all/main.yml index fc7ba3d..5cf802e 100644 --- a/group_vars/all/main.yml +++ b/group_vars/all/main.yml @@ -206,3 +206,23 @@ k8s_service_user_key_bits: 4096 k8s_service_user_key_comment: "k8s@cluster" k8s_service_user_ssh_dir: ".ssh" k8s_service_user_sudo: true + +# Локальная директория для сохранения сгенерированных SSH ключей k8s пользователя +# Сохраняется на машине запуска Ansible (./keys/ относительно корня проекта) +k8s_local_keys_dir: "./keys" + +# ─── k3s-certs — автоматическая ротация сертификатов K3S ───────────────────── +# K3S выпускает сертификаты с фиксированным сроком жизни 1 год. +# Systemd таймер обеспечивает автоматическое обновление до истечения срока, +# гарантируя желаемый срок жизни кластера без ручного вмешательства. +k3s_cert_auto_rotate: true + +# Желаемый срок жизни кластера без ручного вмешательства (в годах) +k3s_cert_validity_years: 5 + +# Ротация запускается, когда до истечения сертификата остаётся меньше N дней +k3s_cert_rotate_before_days: 90 + +# Расписание проверки (формат systemd OnCalendar): monthly, weekly, daily +# или конкретный формат: "*-*-1 03:00:00" (1-е число каждого месяца) +k3s_cert_check_schedule: "monthly" diff --git a/add-node.yml b/playbooks/add-node.yml similarity index 100% rename from add-node.yml rename to playbooks/add-node.yml diff --git a/bootstrap.yml b/playbooks/bootstrap.yml similarity index 100% rename from bootstrap.yml rename to playbooks/bootstrap.yml diff --git a/etcd-backup.yml b/playbooks/etcd-backup.yml similarity index 100% rename from etcd-backup.yml rename to playbooks/etcd-backup.yml diff --git a/etcd-restore.yml b/playbooks/etcd-restore.yml similarity index 100% rename from etcd-restore.yml rename to playbooks/etcd-restore.yml diff --git a/healthcheck.yml b/playbooks/healthcheck.yml similarity index 100% rename from healthcheck.yml rename to playbooks/healthcheck.yml diff --git a/playbooks/k3s-certs.yml b/playbooks/k3s-certs.yml new file mode 100644 index 0000000..f58cf89 --- /dev/null +++ b/playbooks/k3s-certs.yml @@ -0,0 +1,22 @@ +--- +# ───────────────────────────────────────────────────────────────────────────── +# k3s-certs: установка systemd таймера автоматической ротации сертификатов K3S +# +# K3S выпускает сертификаты сроком на 1 год. Этот плейбук устанавливает +# systemd таймер, который автоматически проверяет и обновляет сертификаты +# до их истечения, обеспечивая бесперебойную работу кластера. +# +# Настройка: +# k3s_cert_validity_years: 5 — желаемый срок жизни без вмешательства +# k3s_cert_rotate_before_days: 90 — ротация за N дней до истечения +# k3s_cert_check_schedule: "monthly" — расписание проверки (systemd OnCalendar) +# +# Запуск: ansible-playbook playbooks/k3s-certs.yml --ask-vault-pass +# ───────────────────────────────────────────────────────────────────────────── + +- name: Setup K3S certificate auto-rotation + hosts: k3s_cluster + gather_facts: true + become: true + roles: + - role: k3s-certs diff --git a/k8s-user.yml b/playbooks/k8s-user.yml similarity index 55% rename from k8s-user.yml rename to playbooks/k8s-user.yml index 82a3b3c..049ef00 100644 --- a/k8s-user.yml +++ b/playbooks/k8s-user.yml @@ -5,12 +5,13 @@ # Последовательность: # 1. Создать пользователя k8s + sudo на всех нодах кластера # 2. Сгенерировать RSA 4096 ключевую пару на первом мастере (один раз) -# 3. Разложить ключи на все ноды кластера (SSH в любую сторону) -# 4. Обновить /etc/hosts на нодах кластера -# 5. То же самое для lab_hosts (через пароль из vault) +# 3. Сохранить ключи локально в ./keys/ +# 4. Разложить ключи на все ноды кластера (SSH в любую сторону) +# 5. Обновить /etc/hosts на нодах кластера +# 6. То же самое для lab_hosts (через пароль из vault) # -# Запуск: ansible-playbook k8s-user.yml --ask-vault-pass -# Только кластер: ansible-playbook k8s-user.yml --limit k3s_cluster +# Запуск: ansible-playbook playbooks/k8s-user.yml --ask-vault-pass +# Только кластер: ansible-playbook playbooks/k8s-user.yml --limit k3s_cluster # ───────────────────────────────────────────────────────────────────────────── # ── 1. Создать пользователя k8s на всех нодах кластера ─────────────────────── @@ -28,27 +29,66 @@ become: true tasks: - name: Generate RSA key pair and store facts - ansible.builtin.include_tasks: roles/k8s-user/tasks/generate_keys.yml + ansible.builtin.include_role: + name: k8s-user + tasks_from: generate_keys.yml -# ── 3. Разложить ключи на все ноды кластера ────────────────────────────────── +# ── 3. Сохранить ключи локально в ./keys/ ──────────────────────────────────── +- name: Save k8s SSH keys to local machine + hosts: "{{ groups['k3s_master'][0] }}" + gather_facts: false + tasks: + - name: Create local keys directory + ansible.builtin.file: + path: "{{ k8s_local_keys_dir }}" + state: directory + mode: '0700' + delegate_to: localhost + become: false + + - name: Save private key locally + ansible.builtin.copy: + content: "{{ k8s_ssh_private_key }}" + dest: "{{ k8s_local_keys_dir }}/k8s_id_rsa" + mode: '0600' + delegate_to: localhost + become: false + + - name: Save public key locally + ansible.builtin.copy: + content: "{{ k8s_ssh_public_key }}\n" + dest: "{{ k8s_local_keys_dir }}/k8s_id_rsa.pub" + mode: '0644' + delegate_to: localhost + become: false + + - name: Show where keys were saved + ansible.builtin.debug: + msg: "SSH keys saved to {{ k8s_local_keys_dir }}" + +# ── 4. Разложить ключи на все ноды кластера ────────────────────────────────── - name: Distribute k8s SSH keys to all cluster nodes hosts: k3s_cluster gather_facts: false become: true tasks: - name: Deploy keys to node - ansible.builtin.include_tasks: roles/k8s-user/tasks/distribute_keys.yml + ansible.builtin.include_role: + name: k8s-user + tasks_from: distribute_keys.yml -# ── 4. Обновить /etc/hosts на нодах кластера ───────────────────────────────── +# ── 5. Обновить /etc/hosts на нодах кластера ───────────────────────────────── - name: Update /etc/hosts on cluster nodes hosts: k3s_cluster gather_facts: false become: true tasks: - name: Update hosts file - ansible.builtin.include_tasks: roles/k8s-user/tasks/update_hosts.yml + ansible.builtin.include_role: + name: k8s-user + tasks_from: update_hosts.yml -# ── 5. Bootstrap lab_hosts: создать пользователя, разложить ключи, обновить hosts +# ── 6. Bootstrap lab_hosts: создать пользователя, разложить ключи, обновить hosts # Подключение через логин/пароль из host_vars//vault.yml - name: Setup k8s user on lab hosts hosts: lab_hosts @@ -64,10 +104,16 @@ -o PubkeyAuthentication=no tasks: - name: Create k8s user on lab host - ansible.builtin.include_tasks: roles/k8s-user/tasks/create_user.yml + ansible.builtin.include_role: + name: k8s-user + tasks_from: create_user.yml - name: Distribute k8s SSH keys to lab host - ansible.builtin.include_tasks: roles/k8s-user/tasks/distribute_keys.yml + ansible.builtin.include_role: + name: k8s-user + tasks_from: distribute_keys.yml - name: Update /etc/hosts on lab host - ansible.builtin.include_tasks: roles/k8s-user/tasks/update_hosts.yml + ansible.builtin.include_role: + name: k8s-user + tasks_from: update_hosts.yml diff --git a/mdadm.yml b/playbooks/mdadm.yml similarity index 100% rename from mdadm.yml rename to playbooks/mdadm.yml diff --git a/remove-node.yml b/playbooks/remove-node.yml similarity index 100% rename from remove-node.yml rename to playbooks/remove-node.yml diff --git a/site.yml b/playbooks/site.yml similarity index 92% rename from site.yml rename to playbooks/site.yml index 6ebc90d..3dfdcd7 100644 --- a/site.yml +++ b/playbooks/site.yml @@ -8,8 +8,8 @@ # 4. CSI NFS Driver (StorageClass для PVC) # 5. ingress-nginx (Ingress controller через Helm) # -# Запуск: ansible-playbook site.yml --ask-vault-pass -# Только отдельный компонент: ansible-playbook site.yml --tags kube_vip +# Запуск: ansible-playbook playbooks/site.yml --ask-vault-pass +# Только отдельный компонент: ansible-playbook playbooks/site.yml --tags kube_vip # ───────────────────────────────────────────────────────────────────────────── # ── 1. K3S Cluster ──────────────────────────────────────────────────────────── @@ -136,3 +136,12 @@ changed_when: false - ansible.builtin.debug: msg: "{{ ic.stdout_lines }}" + +# ── Certificate Auto-Rotation (systemd timer) ───────────────────────────────── +- name: Setup K3S certificate auto-rotation + hosts: k3s_cluster + gather_facts: true + become: true + tags: [certs, k3s_certs] + roles: + - role: k3s-certs diff --git a/uninstall.yml b/playbooks/uninstall.yml similarity index 100% rename from uninstall.yml rename to playbooks/uninstall.yml diff --git a/upgrade.yml b/playbooks/upgrade.yml similarity index 100% rename from upgrade.yml rename to playbooks/upgrade.yml diff --git a/roles/k3s-certs/defaults/main.yml b/roles/k3s-certs/defaults/main.yml new file mode 100644 index 0000000..e6dcb2a --- /dev/null +++ b/roles/k3s-certs/defaults/main.yml @@ -0,0 +1,23 @@ +--- +# ─── k3s-certs — управление сертификатами K3S ──────────────────────────────── + +# Включить автоматическую ротацию сертификатов +k3s_cert_auto_rotate: true + +# Желаемый срок жизни кластера без ручного вмешательства (в годах) +# Используется как справочная величина в логах и документации. +# Фактическое время жизни одного сертификата в K3S — 1 год (hardcoded). +# Для обеспечения этого срока systemd таймер автоматически обновляет +# сертификаты до их истечения. +k3s_cert_validity_years: 5 + +# За сколько дней до истечения сертификата запускать ротацию +k3s_cert_rotate_before_days: 90 + +# Расписание проверки (формат systemd OnCalendar) +# По умолчанию — первого числа каждого месяца в 03:00 +k3s_cert_check_schedule: "monthly" + +# Случайная задержка запуска (в секундах) — снижает нагрузку на кластер +# при одновременном запуске таймера на нескольких нодах +k3s_cert_check_randomized_delay: "3600" diff --git a/roles/k3s-certs/handlers/main.yml b/roles/k3s-certs/handlers/main.yml new file mode 100644 index 0000000..f18289f --- /dev/null +++ b/roles/k3s-certs/handlers/main.yml @@ -0,0 +1,14 @@ +--- +- name: reload systemd + ansible.builtin.systemd: + daemon_reload: true + become: true + listen: reload systemd + +- name: enable cert timer + ansible.builtin.systemd: + name: k3s-cert-check.timer + enabled: true + state: started + become: true + listen: enable cert timer diff --git a/roles/k3s-certs/tasks/main.yml b/roles/k3s-certs/tasks/main.yml new file mode 100644 index 0000000..7335f95 --- /dev/null +++ b/roles/k3s-certs/tasks/main.yml @@ -0,0 +1,57 @@ +--- +- name: Skip cert rotation if not enabled + ansible.builtin.meta: end_play + when: not k3s_cert_auto_rotate | bool + +- name: Deploy cert check script + ansible.builtin.template: + src: k3s-cert-check.sh.j2 + dest: /usr/local/bin/k3s-cert-check.sh + owner: root + group: root + mode: '0755' + become: true + +- name: Deploy cert check systemd service + ansible.builtin.template: + src: k3s-cert-check.service.j2 + dest: /etc/systemd/system/k3s-cert-check.service + owner: root + group: root + mode: '0644' + become: true + notify: reload systemd + +- name: Deploy cert check systemd timer + ansible.builtin.template: + src: k3s-cert-check.timer.j2 + dest: /etc/systemd/system/k3s-cert-check.timer + owner: root + group: root + mode: '0644' + become: true + notify: + - reload systemd + - enable cert timer + +- name: Reload systemd daemon + ansible.builtin.systemd: + daemon_reload: true + become: true + +- name: Enable and start cert rotation timer + ansible.builtin.systemd: + name: k3s-cert-check.timer + enabled: true + state: started + become: true + +- name: Show timer status + ansible.builtin.command: systemctl status k3s-cert-check.timer --no-pager + register: timer_status + changed_when: false + become: true + +- name: Display timer info + ansible.builtin.debug: + msg: "{{ timer_status.stdout_lines }}" diff --git a/roles/k3s-certs/templates/k3s-cert-check.service.j2 b/roles/k3s-certs/templates/k3s-cert-check.service.j2 new file mode 100644 index 0000000..86b81ca --- /dev/null +++ b/roles/k3s-certs/templates/k3s-cert-check.service.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=K3S Certificate Expiry Check and Rotation +Documentation=https://docs.k3s.io/advanced#certificate-rotation +After=network.target +Wants=network.target + +[Service] +Type=oneshot +ExecStart=/usr/local/bin/k3s-cert-check.sh +StandardOutput=journal +StandardError=journal +SyslogIdentifier=k3s-cert-check diff --git a/roles/k3s-certs/templates/k3s-cert-check.sh.j2 b/roles/k3s-certs/templates/k3s-cert-check.sh.j2 new file mode 100644 index 0000000..beb8c81 --- /dev/null +++ b/roles/k3s-certs/templates/k3s-cert-check.sh.j2 @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +# ───────────────────────────────────────────────────────────────────────────── +# k3s-cert-check.sh +# Проверяет срок действия сертификатов K3S и выполняет ротацию при необходимости. +# +# K3S выпускает сертификаты сроком на 1 год. Этот скрипт обеспечивает +# желаемый срок жизни кластера {{ k3s_cert_validity_years }} лет(а) без ручного +# вмешательства, автоматически обновляя сертификаты за {{ k3s_cert_rotate_before_days }} +# дней до истечения. +# ───────────────────────────────────────────────────────────────────────────── +set -euo pipefail + +ROTATE_BEFORE_DAYS="{{ k3s_cert_rotate_before_days }}" +CERT_DIR="{{ k3s_data_dir }}/server/tls" +LOG_TAG="k3s-cert-check" + +log() { logger -t "${LOG_TAG}" "$*"; echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; } +warn() { logger -t "${LOG_TAG}" "WARN: $*"; echo "[$(date '+%Y-%m-%d %H:%M:%S')] WARN: $*"; } + +# Определяем сервис: мастер или агент +if systemctl is-active --quiet k3s; then + K3S_SERVICE="k3s" +elif systemctl is-active --quiet k3s-agent; then + K3S_SERVICE="k3s-agent" +else + warn "K3S сервис не запущен — пропускаем проверку" + exit 0 +fi + +# На мастере проверяем сертификат API-сервера +# На агенте — сертификат kubelet клиента +if [[ "${K3S_SERVICE}" == "k3s" ]]; then + CERT_FILE="${CERT_DIR}/serving-kube-apiserver.crt" +else + CERT_FILE="{{ k3s_data_dir }}/agent/client-kubelet.crt" +fi + +if [[ ! -f "${CERT_FILE}" ]]; then + warn "Файл сертификата не найден: ${CERT_FILE}" + exit 0 +fi + +# Определяем дату истечения +EXPIRY_DATE=$(openssl x509 -enddate -noout -in "${CERT_FILE}" | cut -d= -f2) +EXPIRY_EPOCH=$(date -d "${EXPIRY_DATE}" +%s 2>/dev/null || date -jf "%b %d %T %Y %Z" "${EXPIRY_DATE}" +%s) +NOW_EPOCH=$(date +%s) +DAYS_REMAINING=$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 )) + +log "Сертификат: ${CERT_FILE}" +log "Срок истечения: ${EXPIRY_DATE} (осталось ${DAYS_REMAINING} дней)" + +if [[ "${DAYS_REMAINING}" -lt "${ROTATE_BEFORE_DAYS}" ]]; then + log "Запускаем ротацию сертификатов (осталось < ${ROTATE_BEFORE_DAYS} дней)..." + k3s certificate rotate + log "Ротация завершена — перезапускаем ${K3S_SERVICE}..." + systemctl restart "${K3S_SERVICE}" + log "Сертификаты успешно обновлены" +else + log "Ротация не требуется (осталось ${DAYS_REMAINING} дней из ${ROTATE_BEFORE_DAYS} порога)" +fi diff --git a/roles/k3s-certs/templates/k3s-cert-check.timer.j2 b/roles/k3s-certs/templates/k3s-cert-check.timer.j2 new file mode 100644 index 0000000..9d32150 --- /dev/null +++ b/roles/k3s-certs/templates/k3s-cert-check.timer.j2 @@ -0,0 +1,13 @@ +[Unit] +Description=K3S Certificate Rotation Timer +Documentation=https://docs.k3s.io/advanced#certificate-rotation +After=network.target + +[Timer] +OnCalendar={{ k3s_cert_check_schedule }} +RandomizedDelaySec={{ k3s_cert_check_randomized_delay }} +Persistent=true +Unit=k3s-cert-check.service + +[Install] +WantedBy=timers.target