#!/bin/bash ### Дефолты ### IMAGE=factory.talos.dev/metal-installer/956b9107edd250304169d2e7a765cdd4e0c31f9097036e2e113b042e6c01bb98:v1.10.4 DEFAULT_K8S_VERSION=1.33.2 CONFIG_DIR="config" # Цвета для вывода GREEN='\033[0;32m' YELLOW='\033[1;33m' NC='\033[0m' # No Color # Helper function for checking uniqueness in an array contains_element () { local e match="$1" shift for e; do [[ "$e" == "$match" ]] && return 0; done return 1 } # Function for asking yes/no questions ask_yes_no() { local prompt="$1" local default="$2" local answer while true; do read -p "$prompt" answer answer=${answer:-$default} answer=$(echo "$answer" | tr '[:upper:]' '[:lower:]') # convert to lowercase case "$answer" in y|yes) echo "y" return 0 ;; n|no) echo "n" return 0 ;; *) echo -e "${YELLOW}Некорректный ввод. Введите 'y'/'yes' или 'n'/'no'.${NC}" ;; esac done } echo -e "${GREEN}--- Интерактивный конфигуратор Talos K8s ---${NC}" # --- Вопросы пользователю --- # Список вопросов для прогресса QUESTIONS=( "Введите имя кластера [talos-demo]: " "Введите версию Kubernetes [${DEFAULT_K8S_VERSION}]: " "Введите имя сетевого адаптера (например, ens18 или eth0) [ens18]: " "Введите количество control plane (нечетное, макс 7) [1]: " "Введите количество worker-нод (макс 15, мин 0) [3]: " "Введите общий gateway (например, 192.168.23.1): " "Введите маску сети (например, 24) [24]: " "Введите первый DNS сервер: (например, 8.8.8.8) [8.8.8.8]: " "Введите второй DNS сервер: (например, 8.8.4.4) [8.8.4.4]: " "Введите первый NTP сервер [1.ru.pool.ntp.org]: " "Введите второй NTP сервер [2.ru.pool.ntp.org]: " "Введите третий NTP сервер [3.ru.pool.ntp.org]: " "Нужен ли VIP адрес? (y/n) [y]: " "Введите VIP адрес (например, 192.168.23.20): " "Нужен ли внешний балансировщик? (y/n) [n]: " "Введите IP адрес внешнего балансировщика: " "Введите диск для установки базовой ос [/dev/sda]: " "Нужна ли поддержка drbd? (y/n) [y]: " "Нужна ли поддержка zfs? (y/n) [n]: " "Нужна ли поддержка spl? (y/n) [n]: " "Нужна ли поддержка vfio_pci? (y/n) [n]: " "Нужна ли поддержка vfio_iommu_type1? (y/n) [n]: " "Нужна ли поддержка openvswitch? (y/n) [n]: " "Хотите ли вы использовать зеркала timeweb.cloud и gcr.io для docker.io? (y/n) [y]: " "Нужно ли установить maxPods: 512? (110 подов на ноду по умолчанию) (y/n) [n]: " ) QUESTION_IDX=0 QUESTION_TOTAL=${#QUESTIONS[@]} # Функция для вывода вопроса с прогрессом ask_with_progress() { local prompt="$1" ((QUESTION_IDX++)) read -p "[$QUESTION_IDX из $QUESTION_TOTAL] $prompt" "$2" } ask_yes_no_progress() { local prompt="$1" local default="$2" local varname="$3" ((QUESTION_IDX++)) local answer while true; do read -p "[$QUESTION_IDX из $QUESTION_TOTAL] $prompt" answer answer=${answer:-$default} answer=$(echo "$answer" | tr '[:upper:]' '[:lower:]') case "$answer" in y|yes) eval "$varname=\"y\"" return 0 ;; n|no) eval "$varname=\"n\"" return 0 ;; *) echo -e "${YELLOW}Некорректный ввод. Введите 'y'/'yes' или 'n'/'no'.${NC}" ;; esac done } # Имя кластера ask_with_progress "Введите имя кластера [talos-demo]: " CLUSTER_NAME CLUSTER_NAME=${CLUSTER_NAME:-talos-demo} # Версия Kubernetes ask_with_progress "Введите версию Kubernetes [${DEFAULT_K8S_VERSION}]: " K8S_VERSION K8S_VERSION=${K8S_VERSION:-${DEFAULT_K8S_VERSION}} # Имя сетевого адаптера ask_with_progress "Введите имя сетевого адаптера (например, ens18 или eth0) [ens18]: " INTERFACE_NAME INTERFACE_NAME=${INTERFACE_NAME:-ens18} # Количество control plane while true; do ask_with_progress "Введите количество control plane (нечетное, макс 7) [1]: " CP_COUNT CP_COUNT=${CP_COUNT:-1} if (( CP_COUNT % 2 != 0 && CP_COUNT > 0 && CP_COUNT <= 7 )); then break else echo -e "${YELLOW}Некорректное значение. Введите нечетное число от 1 до 7.${NC}" fi done # Количество worker-нод while true; do ask_with_progress "Введите количество worker-нод (макс 15, мин 0) [3]: " WORKER_COUNT WORKER_COUNT=${WORKER_COUNT:-3} if (( WORKER_COUNT >= 0 && WORKER_COUNT <= 15 )); then break else echo -e "${YELLOW}Некорректное значение. Введите число от 0 до 15.${NC}" fi done # Общий шлюз while true; do ask_with_progress "Введите общий gateway (например, 192.168.23.1): " GATEWAY if [[ -n "$GATEWAY" ]]; then USED_IPS+=("$GATEWAY") break else echo -e "${YELLOW}Gateway не может быть пустым.${NC}" fi done # Маска сети while true; do ask_with_progress "Введите маску сети (например, 24) [24]: " NETMASK NETMASK=${NETMASK:-24} if [[ -n "$NETMASK" ]]; then break else echo -e "${YELLOW}Маска сети не может быть пустой.${NC}" fi done # DNS-серверы while true; do ask_with_progress "Введите первый DNS сервер: (например, 8.8.8.8) [8.8.8.8]: " DNS1 DNS1=${DNS1:-8.8.8.8} if [[ -n "$DNS1" ]]; then break else echo -e "${YELLOW}DNS сервер не может быть пустым.${NC}" fi done while true; do ask_with_progress "Введите второй DNS сервер: (например, 8.8.4.4) [8.8.4.4]: " DNS2 DNS2=${DNS2:-8.8.4.4} if [[ -n "$DNS2" ]]; then break else echo -e "${YELLOW}DNS сервер не может быть пустым.${NC}" fi done # NTP-серверы ask_with_progress "Введите первый NTP сервер [1.ru.pool.ntp.org]: " NTP1 NTP1=${NTP1:-1.ru.pool.ntp.org} ask_with_progress "Введите второй NTP сервер [2.ru.pool.ntp.org]: " NTP2 NTP2=${NTP2:-2.ru.pool.ntp.org} ask_with_progress "Введите третий NTP сервер [3.ru.pool.ntp.org]: " NTP3 NTP3=${NTP3:-3.ru.pool.ntp.org} # VIP адрес VIP_IP="" USE_VIP="n" if (( CP_COUNT > 1 )); then ask_yes_no_progress "Нужен ли VIP адрес? (y/n) [y]: " "y" USE_VIP if [[ "$USE_VIP" == "y" ]]; then while true; do ask_with_progress "Введите VIP адрес (например, 192.168.23.20): " VIP_IP if [[ -z "$VIP_IP" ]]; then echo -e "${YELLOW}VIP адрес не может быть пустым.${NC}" continue fi if contains_element "$VIP_IP" "${USED_IPS[@]}"; then echo -e "${YELLOW}Этот IP адрес уже используется. Введите уникальный адрес.${NC}" else USED_IPS+=("$VIP_IP") break fi done fi fi # Внешний балансировщик EXT_BALANCER_IP="" ask_yes_no_progress "Нужен ли внешний балансировщик? (y/n) [n]: " "n" USE_EXT_BALANCER if [[ "$USE_EXT_BALANCER" == "y" ]]; then while true; do ask_with_progress "Введите IP адрес внешнего балансировщика: " EXT_BALANCER_IP_INPUT if [[ -z "$EXT_BALANCER_IP_INPUT" ]]; then echo -e "${YELLOW}IP адрес не может быть пустым.${NC}" continue fi if contains_element "$EXT_BALANCER_IP_INPUT" "${USED_IPS[@]}"; then echo -e "${YELLOW}Этот IP адрес уже используется. Введите уникальный адрес.${NC}" else EXT_BALANCER_IP=$EXT_BALANCER_IP_INPUT USED_IPS+=("$EXT_BALANCER_IP") break fi done fi # Диск ask_with_progress "Введите диск для установки базовой ос [/dev/sda]: " DISK DISK=${DISK:-/dev/sda} # Вопросы по модулям ядра ask_yes_no_progress "Нужна ли поддержка drbd? (y/n) [y]: " "y" USE_DRBD ask_yes_no_progress "Нужна ли поддержка zfs? (y/n) [n]: " "n" USE_ZFS ask_yes_no_progress "Нужна ли поддержка spl? (y/n) [n]: " "n" USE_SPL ask_yes_no_progress "Нужна ли поддержка vfio_pci? (y/n) [n]: " "n" USE_VFIO_PCI ask_yes_no_progress "Нужна ли поддержка vfio_iommu_type1? (y/n) [n]: " "n" USE_VFIO_IOMMU_TYPE1 ask_yes_no_progress "Нужна ли поддержка openvswitch? (y/n) [n]: " "n" USE_OPENVSWITCH # Использовать зеркало mirror.gcr.io? ask_yes_no_progress "Хотите ли вы использовать зеркала timeweb.cloud и gcr.io для docker.io? (y/n) [y]: " "y" USE_MIRROR_GCR # Вопрос: нужен ли maxPods: 512 для kubelet? ask_yes_no_progress "Нужно ли установить maxPods: 512? (110 подов на ноду по умолчанию) (y/n) [n]: " "n" USE_KUBELET_MAXPODS # --- Генерация конфигурационных файлов --- mkdir -p "$CONFIG_DIR" PATCH_FILE="$CONFIG_DIR/patch.yaml" # --- Создание patch.yaml --- # Записываем первую часть файла cat > "$PATCH_FILE" << EOF machine: EOF # Добавляем certSANs если есть внешний балансировщик if [[ -n "$EXT_BALANCER_IP" ]]; then cat >> "$PATCH_FILE" << EOF certSANs: - ${EXT_BALANCER_IP} EOF fi # Добавляем блок kernel для drbd, если нужно if [[ "$USE_DRBD" == "y" ]] && (( WORKER_COUNT == 0 )); then cat >> "$PATCH_FILE" << EOF kernel: modules: - name: drbd parameters: - usermode_helper=disabled EOF if [[ "$USE_ZFS" == "y" ]]; then cat >> "$PATCH_FILE" << EOF - name: zfs EOF fi if [[ "$USE_SPL" == "y" ]]; then cat >> "$PATCH_FILE" << EOF - name: spl EOF fi if [[ "$USE_VFIO_PCI" == "y" ]]; then cat >> "$PATCH_FILE" << EOF - name: vfio_pci EOF fi if [[ "$USE_VFIO_IOMMU_TYPE1" == "y" ]]; then cat >> "$PATCH_FILE" << EOF - name: vfio_iommu_type1 EOF fi if [[ "$USE_OPENVSWITCH" == "y" ]]; then cat >> "$PATCH_FILE" << EOF - name: openvswitch EOF fi fi # Добавляем основную часть machine и начало cluster cat >> "$PATCH_FILE" << EOF network: nameservers: - ${DNS1} - ${DNS2} install: disk: ${DISK} image: ${IMAGE} EOF if [[ "$USE_MIRROR_GCR" == "y" ]]; then cat >> "$PATCH_FILE" << EOF registries: mirrors: docker.io: endpoints: - https://dockerhub.timeweb.cloud - https://mirror.gcr.io EOF fi cat >> "$PATCH_FILE" << EOF time: servers: - ${NTP1} - ${NTP2} - ${NTP3} cluster: EOF # Добавляем allowSchedulingOnControlPlanes и финальную часть if (( WORKER_COUNT == 0 )); then echo -e "\n${YELLOW}Воркеры отсутствуют. Разрешение на запуск подов на control plane...${NC}" cat >> "$PATCH_FILE" << EOF allowSchedulingOnControlPlanes: true network: cni: name: none proxy: disabled: true EOF else cat >> "$PATCH_FILE" << EOF network: cni: name: none proxy: disabled: true EOF fi CP_IPS=() # Генерация патчей для control plane echo -e "\n${GREEN}--- Настройка Control Plane нод ---${NC}" for i in $(seq 1 $CP_COUNT); do while true; do read -p "Введите IP адрес для control plane $i (например, 192.168.23.5${i}): " CP_IP if [[ -z "$CP_IP" ]]; then echo -e "${YELLOW}IP адрес не может быть пустым.${NC}" continue fi if contains_element "$CP_IP" "${USED_IPS[@]}"; then echo -e "${YELLOW}Этот IP адрес уже используется. Введите уникальный адрес.${NC}" else CP_IPS+=("$CP_IP") USED_IPS+=("$CP_IP") break fi done HOSTNAME="cp-$i" FILENAME="$CONFIG_DIR/cp$i.patch" # Создание базового патча cat > "$FILENAME" << EOF machine: EOF if [[ "$USE_KUBELET_MAXPODS" == "y" ]]; then cat >> "$FILENAME" << EOF kubelet: extraConfig: maxPods: 512 EOF fi cat >> "$FILENAME" << EOF network: hostname: $HOSTNAME interfaces: - interface: $INTERFACE_NAME dhcp: false addresses: - $CP_IP/$NETMASK EOF # Добавление VIP, если он используется if [[ "$USE_VIP" == "y" && -n "$VIP_IP" ]]; then cat >> "$FILENAME" << EOF vip: ip: $VIP_IP EOF fi # Добавление маршрутов cat >> "$FILENAME" << EOF routes: - network: 0.0.0.0/0 gateway: $GATEWAY EOF echo "Создан файл: $FILENAME" done # Генерация патчей для worker-нод if (( WORKER_COUNT > 0 )); then echo -e "\n${GREEN}--- Настройка Worker нод ---${NC}" for i in $(seq 1 $WORKER_COUNT); do while true; do read -p "Введите IP адрес для worker $i (например, 192.168.23.10${i}): " WORKER_IP if [[ -z "$WORKER_IP" ]]; then echo -e "${YELLOW}IP адрес не может быть пустым.${NC}" continue fi if contains_element "$WORKER_IP" "${USED_IPS[@]}"; then echo -e "${YELLOW}Этот IP адрес уже используется. Введите уникальный адрес.${NC}" else USED_IPS+=("$WORKER_IP") break fi done HOSTNAME="worker-$i" FILENAME="$CONFIG_DIR/worker$i.patch" cat > "$FILENAME" << EOF machine: network: hostname: $HOSTNAME interfaces: - deviceSelector: physical: true dhcp: false addresses: - $WORKER_IP/$NETMASK routes: - network: 0.0.0.0/0 gateway: $GATEWAY EOF # Добавляем drbd если выбрано if [[ "$USE_DRBD" == "y" ]]; then cat >> "$FILENAME" << EOF kernel: modules: - name: drbd parameters: - usermode_helper=disabled EOF fi echo "Создан файл: $FILENAME" done fi # --- Вывод команд для выполнения --- echo -e "\n${YELLOW}--------------------------------------------------${NC}" echo -e "${GREEN}Конфигурация завершена. Собираю файлы в кучу:${NC}" echo -e "${YELLOW}--------------------------------------------------${NC}" # Генерация секретов talosctl gen secrets -o $CONFIG_DIR/secrets.yaml # Определение эндпоинта ENDPOINT_IP="" if [[ "$USE_VIP" == "y" && -n "$VIP_IP" ]]; then ENDPOINT_IP=$VIP_IP else FIRST_CP_FULL_IP=${CP_IPS[0]} ENDPOINT_IP=$(echo "$FIRST_CP_FULL_IP" | cut -d'/' -f1) fi # Генерация основной конфигурации cd $CONFIG_DIR echo "talosctl gen config --kubernetes-version $K8S_VERSION --with-secrets secrets.yaml $CLUSTER_NAME https://${ENDPOINT_IP}:6443 --config-patch @patch.yaml" talosctl gen config --kubernetes-version $K8S_VERSION --with-secrets secrets.yaml $CLUSTER_NAME https://${ENDPOINT_IP}:6443 --config-patch @patch.yaml # Применение патчей к control plane for i in $(seq 1 $CP_COUNT); do talosctl machineconfig patch controlplane.yaml --patch @cp$i.patch --output cp$i.yaml echo "Создан файл: $CONFIG_DIR/cp$i.yaml" done # Применение патчей к worker-нодам if (( WORKER_COUNT > 0 )); then for i in $(seq 1 $WORKER_COUNT); do talosctl machineconfig patch worker.yaml --patch @worker$i.patch --output worker$i.yaml echo "Создан файл: $CONFIG_DIR/worker$i.yaml" done fi # Обновление talosconfig с endpoints echo -e "\n${GREEN}--- Обновление talosconfig ---${NC}" # Создаем массив endpoints ENDPOINTS=() # Добавляем все control plane IPs for cp_ip in "${CP_IPS[@]}"; do ENDPOINTS+=("$cp_ip") done # Добавляем VIP если есть if [[ "$USE_VIP" == "y" && -n "$VIP_IP" ]]; then ENDPOINTS+=("$VIP_IP") fi # Добавляем внешний балансировщик если есть if [[ "$USE_EXT_BALANCER" == "y" && -n "$EXT_BALANCER_IP" ]]; then ENDPOINTS+=("$EXT_BALANCER_IP") fi # Объединяем endpoints через запятую с пробелом ENDPOINTS_STRING=$(IFS="," ; echo "${ENDPOINTS[*]}") # Обновляем talosconfig if [[ -f "talosconfig" ]]; then TMP_CONFIG=$(mktemp) while IFS= read -r line; do if [[ "$line" == *"endpoints: []"* ]]; then echo "${line/endpoints: []/endpoints: [$ENDPOINTS_STRING]}" >> "$TMP_CONFIG" else echo "$line" >> "$TMP_CONFIG" fi done < "talosconfig" mv "$TMP_CONFIG" "talosconfig" echo -e "${GREEN}Обновлен talosconfig с endpoints: [$ENDPOINTS_STRING]${NC}" else echo -e "${YELLOW}Файл talosconfig не найден${NC}" fi cd .. echo "Работа скрипта завершена"