Files
K3S/docker/entrypoint.sh
2026-04-27 08:40:08 +03:00

497 lines
21 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────────────────────
# Entrypoint для Ansible Runner контейнера
# Выполняет: настройку SSH, vault-пароля, и запуск ansible-playbook
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color
log() { echo -e "${BLUE}[runner]${NC} $*"; }
ok() { echo -e "${GREEN}[runner]${NC}$*"; }
warn() { echo -e "${YELLOW}[runner]${NC}$*"; }
err() { echo -e "${RED}[runner]${NC}$*" >&2; }
# ── Баннер ────────────────────────────────────────────────────────────────────
print_banner() {
echo -e "${CYAN}"
echo "╔══════════════════════════════════════════════════════╗"
echo "║ K3S Ansible Runner 🚀 ║"
echo "║ Ansible $(ansible --version | head -1 | awk '{print $3}') • Helm $(helm version --short) • kubectl $(kubectl version --client -o json 2>/dev/null | jq -r .clientVersion.gitVersion 2>/dev/null || echo 'n/a')"
echo "╚══════════════════════════════════════════════════════╝"
echo -e "${NC}"
}
# ── Справка ───────────────────────────────────────────────────────────────────
print_help() {
echo -e "${BOLD}Использование:${NC}"
echo ""
echo " Через Makefile (рекомендуется):"
echo " make install — полный стек"
echo " make install-k3s — только K3S"
echo " make install-kubevip — только kube-vip"
echo " make addon-nfs — NFS сервер + CSI Driver"
echo " make addon-ingress-nginx — ingress-nginx"
echo " make health — диагностика"
echo " make upgrade VERSION=v1.30.0+k3s1"
echo ""
echo " Напрямую через docker run:"
echo " docker run --rm -it \\"
echo " -v \$(pwd):/ansible \\"
echo " -v ~/.ssh:/root/.ssh:ro \\"
echo " -e VAULT_PASSWORD=мой-пароль \\"
echo " k3s-ansible install"
echo ""
echo -e "${BOLD}Переменные окружения:${NC}"
echo " VAULT_PASSWORD — пароль Ansible Vault"
echo " VAULT_PASSWORD_FILE — путь к файлу с паролем (альтернатива)"
echo " ANSIBLE_TAGS — запустить только указанные теги"
echo " ANSIBLE_SKIP_TAGS — пропустить указанные теги"
echo " EXTRA_VARS — дополнительные переменные (-e)"
echo " ANSIBLE_VERBOSITY — уровень verbose (0-4, default: 0)"
echo ""
echo -e "${BOLD}Доступные команды:${NC}"
echo " bootstrap — создать пользователя + задеплоить SSH ключ"
echo " k8s-user — создать k8s пользователя + разложить SSH ключи"
echo " mdadm — найти RAID массив и смонтировать в /storage"
echo " chrony — установить chrony + настроить timezone"
echo " k3s-certs — установить systemd таймер ротации сертификатов"
echo " install — core кластер (K3S + kube-vip + certs)"
echo " install-full — core + аддоны по флагам из group_vars/all/addons.yml"
echo " install-addons — только аддоны (флаги из addons.yml)"
echo " install-k3s — только K3S"
echo " install-kubevip — только kube-vip"
echo " addon <name> — установить аддон из addons/<name>/playbook.yml"
echo " nfs-server | csi-nfs | ingress-nginx | cert-manager"
echo " istio | prometheus-stack | metrics-server"
echo " argocd | longhorn | kubernetes-dashboard"
echo " add-node <node> — добавить k3s ноду (мастер или воркер)"
echo " remove-node <node> — безопасно удалить k3s ноду"
echo " add-etcd-node <node> — добавить etcd ноду в кластер (external режим)"
echo " remove-etcd-node <node> — удалить etcd ноду из кластера (external режим)"
echo " etcd-backup — создать снимок etcd"
echo " etcd-restore <snap> — восстановить etcd"
echo " etcd-list — список снимков etcd"
echo " upgrade — обновить K3S (нужен VERSION=)"
echo " uninstall — удалить весь стек"
echo " health — диагностика"
echo " verify — проверка стека"
echo " ping — проверить SSH до всех нод"
echo " shell — интерактивный bash"
echo " ansible-playbook — прямой вызов ansible-playbook"
echo " ansible — прямой вызов ansible"
}
# ── Настройка SSH ─────────────────────────────────────────────────────────────
setup_ssh() {
log "Настройка SSH..."
# Создаём директорию если не существует
mkdir -p /root/.ssh
chmod 700 /root/.ssh 2>/dev/null || true
# Конфигурация SSH клиента (если директория доступна на запись)
if touch /root/.ssh/.runner_write_test 2>/dev/null; then
rm -f /root/.ssh/.runner_write_test
cat > /root/.ssh/config << 'SSHEOF'
Host *
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 60s
ServerAliveInterval 30
ServerAliveCountMax 3
ConnectTimeout 30
SSHEOF
chmod 600 /root/.ssh/config 2>/dev/null || true
else
warn "/root/.ssh примонтирован read-only — пропускаю запись SSH config"
fi
# Исправляем права на ключи если они примонтированы
if ls /root/.ssh/*.pem 2>/dev/null || ls /root/.ssh/id_* 2>/dev/null; then
chmod 600 /root/.ssh/id_* 2>/dev/null || true
chmod 600 /root/.ssh/*.pem 2>/dev/null || true
ok "SSH ключи найдены"
else
warn "SSH ключи не найдены в /root/.ssh — убедись что они примонтированы"
fi
}
# ── Настройка Vault пароля ────────────────────────────────────────────────────
setup_vault() {
local vault_file="/tmp/.vault_pass"
if [[ -n "${VAULT_PASSWORD:-}" ]]; then
echo "${VAULT_PASSWORD}" > "${vault_file}"
chmod 600 "${vault_file}"
export ANSIBLE_VAULT_PASSWORD_FILE="${vault_file}"
ok "Vault пароль установлен из переменной VAULT_PASSWORD"
elif [[ -n "${VAULT_PASSWORD_FILE:-}" ]] && [[ -f "${VAULT_PASSWORD_FILE}" ]]; then
export ANSIBLE_VAULT_PASSWORD_FILE="${VAULT_PASSWORD_FILE}"
ok "Vault пароль загружен из файла: ${VAULT_PASSWORD_FILE}"
elif [[ -f "/ansible/.vault_pass" ]]; then
export ANSIBLE_VAULT_PASSWORD_FILE="/ansible/.vault_pass"
ok "Vault пароль найден в /ansible/.vault_pass"
else
warn "Vault пароль не задан. Используй переменную VAULT_PASSWORD или файл .vault_pass"
warn "Если vault не используется — это нормально"
fi
}
# ── Сборка аргументов ansible-playbook ───────────────────────────────────────
build_ansible_args() {
local args=()
# Файл vault-пароля
if [[ -n "${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then
args+=("--vault-password-file" "${ANSIBLE_VAULT_PASSWORD_FILE}")
fi
# Уровень verbose
local verbosity="${ANSIBLE_VERBOSITY:-0}"
if [[ "${verbosity}" -gt 0 ]]; then
args+=("-$(printf 'v%.0s' $(seq 1 "${verbosity}"))")
fi
# Теги
if [[ -n "${ANSIBLE_TAGS:-}" ]]; then
args+=("--tags" "${ANSIBLE_TAGS}")
fi
# Пропустить теги
if [[ -n "${ANSIBLE_SKIP_TAGS:-}" ]]; then
args+=("--skip-tags" "${ANSIBLE_SKIP_TAGS}")
fi
# Дополнительные переменные
if [[ -n "${EXTRA_VARS:-}" ]]; then
args+=("-e" "${EXTRA_VARS}")
fi
echo "${args[@]:-}"
}
# ── Запуск playbook ───────────────────────────────────────────────────────────
run_playbook() {
local playbook="$1"
shift
local extra_args=("$@")
# shellcheck disable=SC2207
local ansible_args=($(build_ansible_args))
log "Запуск: ansible-playbook ${playbook} ${ansible_args[*]} ${extra_args[*]}"
echo ""
exec ansible-playbook "${playbook}" \
"${ansible_args[@]}" \
"${extra_args[@]}"
}
# ─────────────────────────────────────────────────────────────────────────────
# MAIN
# ─────────────────────────────────────────────────────────────────────────────
print_banner
setup_ssh
setup_vault
COMMAND="${1:-help}"
shift || true
case "${COMMAND}" in
# ── Bootstrap ─────────────────────────────────────────────────────────────
bootstrap)
log "Bootstrap нод: создание пользователя + деплой SSH ключа..."
exec ansible-playbook playbooks/bootstrap.yml "$@"
;;
# ── k8s-user ──────────────────────────────────────────────────────────────
k8s-user)
log "Создание k8s пользователя и деплой SSH ключей..."
run_playbook playbooks/k8s-user.yml "$@"
;;
# ── mdadm ─────────────────────────────────────────────────────────────────
mdadm)
log "Настройка mdadm RAID и монтирование /storage..."
run_playbook playbooks/mdadm.yml "$@"
;;
# ── chrony ────────────────────────────────────────────────────────────────
chrony)
log "Настройка chrony и синхронизации времени..."
run_playbook playbooks/site.yml --tags chrony "$@"
;;
# ── k3s-certs ─────────────────────────────────────────────────────────────
k3s-certs)
log "Установка systemd таймера ротации сертификатов K3S..."
run_playbook playbooks/k3s-certs.yml "$@"
;;
# ── Аддоны ────────────────────────────────────────────────────────────────
addon)
ADDON="${1:-}"
shift || true
if [[ -z "${ADDON}" ]]; then
err "Укажи аддон: addon argocd | addon longhorn | addon kubernetes-dashboard"
echo ""
echo "Доступные аддоны:"
ls /ansible/addons/ 2>/dev/null | sed 's/^/ /' || echo " (нет аддонов)"
exit 1
fi
PLAYBOOK="/ansible/addons/${ADDON}/playbook.yml"
if [[ ! -f "${PLAYBOOK}" ]]; then
err "Аддон не найден: addons/${ADDON}/playbook.yml"
echo ""
echo "Доступные аддоны:"
ls /ansible/addons/ 2>/dev/null | sed 's/^/ /' || echo " (нет аддонов)"
exit 1
fi
log "Устанавливаю аддон: ${ADDON}"
run_playbook "${PLAYBOOK}" "$@"
;;
# ── Core установка ────────────────────────────────────────────────────────
install)
log "Разворачиваю K3S core кластер (K3S + kube-vip + сертификаты)..."
run_playbook playbooks/site.yml "$@"
;;
install-full)
log "Полный стек: core + аддоны по addons.yml..."
run_playbook playbooks/site.yml "$@"
run_playbook playbooks/addons.yml "$@"
;;
# ── Аддоны по флагам ──────────────────────────────────────────────────────
install-addons)
log "Устанавливаю аддоны по флагам из group_vars/all/addons.yml..."
run_playbook playbooks/addons.yml "$@"
;;
install-k3s)
log "Устанавливаю K3S cluster..."
run_playbook playbooks/site.yml --tags k3s "$@"
;;
install-kubevip)
log "Устанавливаю kube-vip..."
run_playbook playbooks/site.yml --tags kube_vip "$@"
;;
add-node)
if [[ -z "${1:-}" ]]; then
err "Укажи ноду: add-node <nodename>"
exit 1
fi
log "Добавляю ноду ${1} в кластер..."
exec ansible-playbook playbooks/add-node.yml -e "node_to_add=${1}" "${@:2}"
;;
remove-node)
if [[ -z "${1:-}" ]]; then
err "Укажи ноду: remove-node <nodename>"
exit 1
fi
log "Удаляю ноду ${1} из кластера..."
exec ansible-playbook playbooks/remove-node.yml -e "node_to_remove=${1}" "${@:2}"
;;
# ── Управление etcd нодами (external режим) ───────────────────────────────
add-etcd-node)
if [[ -z "${1:-}" ]]; then
err "Укажи ноду: add-etcd-node <nodename>"
echo ""
echo " Нода должна быть в [etcd_nodes] в inventory/hosts.ini"
exit 1
fi
log "Добавляю etcd ноду ${1}..."
exec ansible-playbook playbooks/add-etcd-node.yml -e "node_to_add=${1}" "${@:2}"
;;
remove-etcd-node)
if [[ -z "${1:-}" ]]; then
err "Укажи ноду: remove-etcd-node <nodename>"
exit 1
fi
log "Удаляю etcd ноду ${1} из кластера..."
exec ansible-playbook playbooks/remove-etcd-node.yml -e "node_to_remove=${1}" "${@:2}"
;;
etcd-backup)
log "Создаю снимок etcd..."
exec ansible-playbook playbooks/etcd-backup.yml "$@"
;;
etcd-restore)
if [[ -z "${1:-}" ]]; then
err "Укажи снимок: etcd-restore <snapshot.db>"
exit 1
fi
log "Восстанавливаю etcd из ${1}..."
exec ansible-playbook playbooks/etcd-restore.yml -e "etcd_restore_snapshot=${1}" "${@:2}"
;;
etcd-list)
log "Список снимков etcd..."
exec ansible-playbook playbooks/etcd-restore.yml --tags list "$@"
;;
upgrade)
if [[ -z "${VERSION:-}" ]]; then
err "Нужна переменная VERSION. Пример: make upgrade VERSION=v1.30.0+k3s1"
exit 1
fi
log "Обновляю K3S до ${VERSION}..."
run_playbook playbooks/upgrade.yml -e "k3s_version=${VERSION}" "$@"
;;
uninstall)
warn "Удаление всего стека! Данные будут потеряны."
run_playbook playbooks/uninstall.yml -e "confirm_uninstall=yes" "$@"
;;
health)
log "Диагностика кластера..."
run_playbook playbooks/healthcheck.yml "$@"
;;
verify)
log "Проверка полного стека..."
run_playbook playbooks/site.yml --tags verify "$@"
;;
ping)
log "Проверяю SSH доступность всех нод..."
# shellcheck disable=SC2207
local ansible_args=($(build_ansible_args))
exec ansible all -m ping "${ansible_args[@]}" "$@"
;;
# ── Molecule тестирование ролей ───────────────────────────────────────────
molecule)
ROLE="${1:-}"
shift || true
if [[ -z "${ROLE}" ]]; then
err "Укажи роль: molecule k3s | molecule prometheus-stack | molecule istio"
echo ""
echo " Пример: make molecule-k3s"
exit 1
fi
if [[ ! -d "/ansible/roles/${ROLE}" ]]; then
err "Роль не найдена: /ansible/roles/${ROLE}"
exit 1
fi
log "Тестирую роль: ${ROLE}"
export JUNIT_OUTPUT_DIR="${JUNIT_OUTPUT_DIR:-/tmp/molecule-junit}"
export ANSIBLE_CALLBACKS_ENABLED="${ANSIBLE_CALLBACKS_ENABLED:-junit}"
mkdir -p "${JUNIT_OUTPUT_DIR}"
cd "/ansible/roles/${ROLE}"
exec molecule "${@:-test}"
;;
# ── Molecule тестирование аддонов ─────────────────────────────────────────
molecule-addon)
ADDON="${1:-}"
shift || true
if [[ -z "${ADDON}" ]]; then
err "Укажи аддон: molecule-addon technitium-dns | molecule-addon authelia"
echo ""
echo "Доступные аддоны с molecule тестами:"
find /ansible/addons -name "molecule.yml" 2>/dev/null \
| sed 's|/ansible/addons/||; s|/role/molecule.*||' | sort | sed 's/^/ /'
exit 1
fi
ADDON_MOLECULE_DIR="/ansible/addons/${ADDON}/role"
if [[ ! -d "${ADDON_MOLECULE_DIR}/molecule" ]]; then
err "Molecule тесты не найдены: ${ADDON_MOLECULE_DIR}/molecule/"
exit 1
fi
log "Тестирую аддон: ${ADDON}"
export JUNIT_OUTPUT_DIR="${JUNIT_OUTPUT_DIR:-/tmp/molecule-junit}"
export ANSIBLE_CALLBACKS_ENABLED="${ANSIBLE_CALLBACKS_ENABLED:-junit}"
mkdir -p "${JUNIT_OUTPUT_DIR}"
cd "${ADDON_MOLECULE_DIR}"
exec molecule "${@:-test}"
;;
# ── Molecule кластерный тест (3 master + 2 worker) ────────────────────────
molecule-cluster)
log "Тестирую topology кластера (3 master + 2 worker)..."
export JUNIT_OUTPUT_DIR="${JUNIT_OUTPUT_DIR:-/tmp/molecule-junit}"
export ANSIBLE_CALLBACKS_ENABLED="${ANSIBLE_CALLBACKS_ENABLED:-junit}"
mkdir -p "${JUNIT_OUTPUT_DIR}"
cd /ansible
exec molecule test -s cluster "${@}"
;;
molecule-lint)
log "Запуск линтинга (yamllint + ansible-lint)..."
cd /ansible
yamllint .
ansible-lint
ok "Линтинг прошёл"
;;
# ── Molecule HTML отчёт ───────────────────────────────────────────────────
molecule-report)
XML_DIR="${1:-/tmp/molecule-junit}"
OUTPUT="${2:-/tmp/molecule-report.html}"
log "Генерирую HTML отчёт из ${XML_DIR}..."
python3 /ansible/scripts/molecule-report.py \
--xml-dir "${XML_DIR}" \
--output "${OUTPUT}"
ok "Отчёт: ${OUTPUT}"
;;
# ── Прямые вызовы ─────────────────────────────────────────────────────────
ansible-playbook)
exec ansible-playbook "$@"
;;
ansible)
exec ansible "$@"
;;
helm)
exec helm "$@"
;;
kubectl)
exec kubectl "$@"
;;
# ── Shell ─────────────────────────────────────────────────────────────────
shell|bash|sh)
log "Запуск интерактивного shell..."
exec /bin/bash "$@"
;;
# ── Помощь ────────────────────────────────────────────────────────────────
help|--help|-h)
print_help
;;
*)
err "Неизвестная команда: ${COMMAND}"
echo ""
print_help
exit 1
;;
esac