refactor: перемещение плейбуков в playbooks/, ротация сертификатов, сохранение ключей локально

Организация плейбуков:
- все .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
This commit is contained in:
Sergey Antropoff
2026-04-24 07:00:18 +03:00
parent 408779a379
commit 437d0cce34
22 changed files with 340 additions and 49 deletions

3
.gitignore vendored
View File

@@ -16,6 +16,9 @@ id_rsa
id_ed25519 id_ed25519
id_ecdsa id_ecdsa
# Локально сохранённые ключи k8s пользователя (публичный ключ — ок коммитить)
keys/k8s_id_rsa
# Docker артефакты # Docker артефакты
.docker/ .docker/
.claude/ .claude/

View File

@@ -47,7 +47,7 @@ DOCKER_RUN := docker run --rm -it \
$(IMAGE_NAME) $(IMAGE_NAME)
.PHONY: help setup build rebuild \ .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 install-k3s install-cni install-kubevip install-nfs install-ingress \
install-cert-manager install-istio install-monitoring \ install-cert-manager install-istio install-monitoring \
add-node remove-node \ add-node remove-node \
@@ -145,11 +145,11 @@ ping: _check_env _check_image ## Проверить SSH доступность
check: _check_env _check_image ## Dry-run: проверить плейбук без изменений check: _check_env _check_image ## Dry-run: проверить плейбук без изменений
@printf "$(CYAN)Dry-run...$(NC)\n" @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 ## Проверить синтаксис всех плейбуков lint: _check_image ## Проверить синтаксис всех плейбуков
@printf "$(CYAN)Проверка синтаксиса...$(NC)\n" @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" @printf "$(GREEN)✓ Синтаксис корректен$(NC)\n"
# ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════
@@ -163,7 +163,7 @@ lint: _check_image ## Проверить синтаксис всех плейб
bootstrap: _check_env _check_image ## Первый запуск: создать пользователя и задеплоить SSH ключ на все ноды bootstrap: _check_env _check_image ## Первый запуск: создать пользователя и задеплоить SSH ключ на все ноды
@printf "$(CYAN)$(BOLD)Bootstrap нод (пользователь + SSH ключ)...$(NC)\n" @printf "$(CYAN)$(BOLD)Bootstrap нод (пользователь + SSH ключ)...$(NC)\n"
@printf "$(YELLOW)Нужны: host_vars/<node>/vault.yml с bootstrap_user и bootstrap_password$(NC)\n" @printf "$(YELLOW)Нужны: host_vars/<node>/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)",) $(if $(NODE),-e "node_to_bootstrap=$(NODE)",)
vault-bootstrap-create: _check_image ## Создать vault с bootstrap credentials для ноды (NODE=master01) 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) k8s-user: _check_env _check_image ## Создать k8s пользователя + разложить SSH ключи на все ноды (cluster + lab_hosts)
@printf "$(CYAN)$(BOLD)Настройка k8s пользователя и SSH ключей...$(NC)\n" @printf "$(CYAN)$(BOLD)Настройка k8s пользователя и SSH ключей...$(NC)\n"
@printf "$(YELLOW)Нужны: host_vars/<node>/vault.yml с bootstrap_user и bootstrap_password для lab_hosts$(NC)\n" @printf "$(YELLOW)Нужны: host_vars/<node>/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),) $(if $(NODE),-e "node_to_limit=$(NODE)" --limit $(NODE),)
mdadm: _check_env _check_image ## Найти RAID массив и смонтировать в /storage (mdadm_enabled: true) mdadm: _check_env _check_image ## Найти RAID массив и смонтировать в /storage (mdadm_enabled: true)
@printf "$(CYAN)Настройка mdadm RAID...$(NC)\n" @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),) $(if $(NODE),--limit $(NODE),)
install: _check_env _check_image ## Развернуть полный стек (K3S + kube-vip + NFS + ingress) 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) install-cni: _check_env _check_image ## Установить CNI плагин (задай K3S_CNI=calico|cilium)
@printf "$(CYAN)Устанавливаю CNI ($(or $(K3S_CNI),flannel))...$(NC)\n" @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 install-kubevip: _check_env _check_image ## Установить только kube-vip
@printf "$(CYAN)Устанавливаю kube-vip...$(NC)\n" @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 install-cert-manager: _check_env _check_image ## Установить cert-manager + ClusterIssuer
@printf "$(CYAN)Устанавливаю cert-manager...$(NC)\n" @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) install-istio: _check_env _check_image ## Установить Istio + Kiali (нужен istio_enabled: true в vars)
@printf "$(CYAN)Устанавливаю Istio + Kiali...$(NC)\n" @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) install-monitoring: _check_env _check_image ## Установить Prometheus + Grafana (нужен prometheus_stack_enabled: true)
@printf "$(CYAN)Устанавливаю kube-prometheus-stack...$(NC)\n" @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; \ exit 1; \
fi fi
@printf "$(CYAN)Добавляю ноду $(NODE) в кластер...$(NC)\n" @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 remove-node: _check_env _check_image ## Удалить ноду: make remove-node NODE=worker04
@if [ -z "$(NODE)" ]; then \ @if [ -z "$(NODE)" ]; then \
@@ -259,7 +264,7 @@ remove-node: _check_env _check_image ## Удалить ноду: make remove-nod
exit 1; \ exit 1; \
fi fi
@printf "$(RED)$(BOLD)Удаление ноды $(NODE) из кластера...$(NC)\n" @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 # РЕЗЕРВНОЕ КОПИРОВАНИЕ 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]) etcd-backup: _check_env _check_image ## Создать снимок etcd (make etcd-backup [SNAPSHOT=name] [ETCD_COPY=true])
@printf "$(CYAN)Создаю снимок etcd...$(NC)\n" @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 $(SNAPSHOT),-e etcd_backup_name=$(SNAPSHOT),) \
$(if $(ETCD_COPY),-e etcd_backup_copy_to_local=$(ETCD_COPY),) $(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; \ exit 1; \
fi fi
@printf "$(RED)$(BOLD)ВНИМАНИЕ: восстановление etcd перезапишет данные кластера!$(NC)\n" @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)" \ -e "etcd_restore_snapshot=$(SNAPSHOT)" \
$(if $(FORCE),-e etcd_restore_force=true,) $(if $(FORCE),-e etcd_restore_force=true,)
etcd-list-snapshots: _check_env _check_image ## Показать доступные снимки etcd etcd-list-snapshots: _check_env _check_image ## Показать доступные снимки etcd
@printf "$(CYAN)Список снимков etcd...$(NC)\n" @printf "$(CYAN)Список снимков etcd...$(NC)\n"
$(DOCKER_RUN) ansible-playbook etcd-restore.yml --tags list $(DOCKER_RUN) ansible-playbook playbooks/etcd-restore.yml --tags list
# ═══════════════════════════════════════════════════════════════════════════════ # ═══════════════════════════════════════════════════════════════════════════════
# ОБНОВЛЕНИЕ # ОБНОВЛЕНИЕ

View File

@@ -61,6 +61,7 @@ print_help() {
echo " bootstrap — создать пользователя + задеплоить SSH ключ" echo " bootstrap — создать пользователя + задеплоить SSH ключ"
echo " k8s-user — создать k8s пользователя + разложить SSH ключи" echo " k8s-user — создать k8s пользователя + разложить SSH ключи"
echo " mdadm — найти RAID массив и смонтировать в /storage" echo " mdadm — найти RAID массив и смонтировать в /storage"
echo " k3s-certs — установить systemd таймер ротации сертификатов"
echo " install — полный стек (site.yml)" echo " install — полный стек (site.yml)"
echo " install-k3s — только K3S" echo " install-k3s — только K3S"
echo " install-kubevip — только kube-vip" echo " install-kubevip — только kube-vip"
@@ -203,50 +204,56 @@ case "${COMMAND}" in
# ── Bootstrap ───────────────────────────────────────────────────────────── # ── Bootstrap ─────────────────────────────────────────────────────────────
bootstrap) bootstrap)
log "Bootstrap нод: создание пользователя + деплой SSH ключа..." log "Bootstrap нод: создание пользователя + деплой SSH ключа..."
exec ansible-playbook bootstrap.yml "$@" exec ansible-playbook playbooks/bootstrap.yml "$@"
;; ;;
# ── k8s-user ────────────────────────────────────────────────────────────── # ── k8s-user ──────────────────────────────────────────────────────────────
k8s-user) k8s-user)
log "Создание k8s пользователя и деплой SSH ключей..." log "Создание k8s пользователя и деплой SSH ключей..."
run_playbook k8s-user.yml "$@" run_playbook playbooks/k8s-user.yml "$@"
;; ;;
# ── mdadm ───────────────────────────────────────────────────────────────── # ── mdadm ─────────────────────────────────────────────────────────────────
mdadm) mdadm)
log "Настройка mdadm RAID и монтирование /storage..." 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) install)
log "Разворачиваю полный K3S стек..." log "Разворачиваю полный K3S стек..."
run_playbook site.yml "$@" run_playbook playbooks/site.yml "$@"
;; ;;
install-k3s) install-k3s)
log "Устанавливаю K3S cluster..." log "Устанавливаю K3S cluster..."
run_playbook site.yml --tags k3s "$@" run_playbook playbooks/site.yml --tags k3s "$@"
;; ;;
install-kubevip) install-kubevip)
log "Устанавливаю kube-vip..." log "Устанавливаю kube-vip..."
run_playbook site.yml --tags kube_vip "$@" run_playbook playbooks/site.yml --tags kube_vip "$@"
;; ;;
install-nfs) install-nfs)
log "Устанавливаю NFS Server + CSI Driver..." log "Устанавливаю NFS Server + CSI Driver..."
run_playbook site.yml --tags nfs,csi_nfs "$@" run_playbook playbooks/site.yml --tags nfs,csi_nfs "$@"
;; ;;
install-ingress) install-ingress)
log "Устанавливаю ingress-nginx..." log "Устанавливаю ingress-nginx..."
run_playbook site.yml --tags ingress_nginx "$@" run_playbook playbooks/site.yml --tags ingress_nginx "$@"
;; ;;
install-cert-manager) install-cert-manager)
log "Устанавливаю 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) add-node)
@@ -255,7 +262,7 @@ case "${COMMAND}" in
exit 1 exit 1
fi fi
log "Добавляю ноду ${2} в кластер..." 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) remove-node)
@@ -264,12 +271,12 @@ case "${COMMAND}" in
exit 1 exit 1
fi fi
log "Удаляю ноду ${2} из кластера..." 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) etcd-backup)
log "Создаю снимок etcd..." log "Создаю снимок etcd..."
exec ansible-playbook etcd-backup.yml "$@" exec ansible-playbook playbooks/etcd-backup.yml "$@"
;; ;;
etcd-restore) etcd-restore)
@@ -278,12 +285,12 @@ case "${COMMAND}" in
exit 1 exit 1
fi fi
log "Восстанавливаю etcd из ${2}..." 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) etcd-list)
log "Список снимков etcd..." log "Список снимков etcd..."
exec ansible-playbook etcd-restore.yml --tags list "$@" exec ansible-playbook playbooks/etcd-restore.yml --tags list "$@"
;; ;;
upgrade) upgrade)
@@ -292,22 +299,22 @@ case "${COMMAND}" in
exit 1 exit 1
fi fi
log "Обновляю K3S до ${VERSION}..." log "Обновляю K3S до ${VERSION}..."
run_playbook upgrade.yml -e "k3s_version=${VERSION}" "$@" run_playbook playbooks/upgrade.yml -e "k3s_version=${VERSION}" "$@"
;; ;;
uninstall) uninstall)
warn "Удаление всего стека! Данные будут потеряны." warn "Удаление всего стека! Данные будут потеряны."
run_playbook uninstall.yml -e "confirm_uninstall=yes" "$@" run_playbook playbooks/uninstall.yml -e "confirm_uninstall=yes" "$@"
;; ;;
health) health)
log "Диагностика кластера..." log "Диагностика кластера..."
run_playbook healthcheck.yml "$@" run_playbook playbooks/healthcheck.yml "$@"
;; ;;
verify) verify)
log "Проверка полного стека..." log "Проверка полного стека..."
run_playbook site.yml --tags verify "$@" run_playbook playbooks/site.yml --tags verify "$@"
;; ;;
ping) ping)

View File

@@ -206,3 +206,23 @@ k8s_service_user_key_bits: 4096
k8s_service_user_key_comment: "k8s@cluster" k8s_service_user_key_comment: "k8s@cluster"
k8s_service_user_ssh_dir: ".ssh" k8s_service_user_ssh_dir: ".ssh"
k8s_service_user_sudo: true 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"

22
playbooks/k3s-certs.yml Normal file
View File

@@ -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

View File

@@ -5,12 +5,13 @@
# Последовательность: # Последовательность:
# 1. Создать пользователя k8s + sudo на всех нодах кластера # 1. Создать пользователя k8s + sudo на всех нодах кластера
# 2. Сгенерировать RSA 4096 ключевую пару на первом мастере (один раз) # 2. Сгенерировать RSA 4096 ключевую пару на первом мастере (один раз)
# 3. Разложить ключи на все ноды кластера (SSH в любую сторону) # 3. Сохранить ключи локально в ./keys/
# 4. Обновить /etc/hosts на нодах кластера # 4. Разложить ключи на все ноды кластера (SSH в любую сторону)
# 5. То же самое для lab_hosts (через пароль из vault) # 5. Обновить /etc/hosts на нодах кластера
# 6. То же самое для lab_hosts (через пароль из vault)
# #
# Запуск: ansible-playbook k8s-user.yml --ask-vault-pass # Запуск: ansible-playbook playbooks/k8s-user.yml --ask-vault-pass
# Только кластер: ansible-playbook k8s-user.yml --limit k3s_cluster # Только кластер: ansible-playbook playbooks/k8s-user.yml --limit k3s_cluster
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# ── 1. Создать пользователя k8s на всех нодах кластера ─────────────────────── # ── 1. Создать пользователя k8s на всех нодах кластера ───────────────────────
@@ -28,27 +29,66 @@
become: true become: true
tasks: tasks:
- name: Generate RSA key pair and store facts - 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 - name: Distribute k8s SSH keys to all cluster nodes
hosts: k3s_cluster hosts: k3s_cluster
gather_facts: false gather_facts: false
become: true become: true
tasks: tasks:
- name: Deploy keys to node - 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 - name: Update /etc/hosts on cluster nodes
hosts: k3s_cluster hosts: k3s_cluster
gather_facts: false gather_facts: false
become: true become: true
tasks: tasks:
- name: Update hosts file - 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/<host>/vault.yml # Подключение через логин/пароль из host_vars/<host>/vault.yml
- name: Setup k8s user on lab hosts - name: Setup k8s user on lab hosts
hosts: lab_hosts hosts: lab_hosts
@@ -64,10 +104,16 @@
-o PubkeyAuthentication=no -o PubkeyAuthentication=no
tasks: tasks:
- name: Create k8s user on lab host - 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 - 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 - 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

View File

@@ -8,8 +8,8 @@
# 4. CSI NFS Driver (StorageClass для PVC) # 4. CSI NFS Driver (StorageClass для PVC)
# 5. ingress-nginx (Ingress controller через Helm) # 5. ingress-nginx (Ingress controller через Helm)
# #
# Запуск: ansible-playbook site.yml --ask-vault-pass # Запуск: ansible-playbook playbooks/site.yml --ask-vault-pass
# Только отдельный компонент: ansible-playbook site.yml --tags kube_vip # Только отдельный компонент: ansible-playbook playbooks/site.yml --tags kube_vip
# ───────────────────────────────────────────────────────────────────────────── # ─────────────────────────────────────────────────────────────────────────────
# ── 1. K3S Cluster ──────────────────────────────────────────────────────────── # ── 1. K3S Cluster ────────────────────────────────────────────────────────────
@@ -136,3 +136,12 @@
changed_when: false changed_when: false
- ansible.builtin.debug: - ansible.builtin.debug:
msg: "{{ ic.stdout_lines }}" 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

View File

@@ -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"

View File

@@ -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

View File

@@ -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 }}"

View File

@@ -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

View File

@@ -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

View File

@@ -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