#!/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' RED='\033[0;31m' NC='\033[0m' # No Color # функции ask_yes_no, check_command, detect_os и т.д. 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 } # Функция для определения ОС detect_os() { if [[ "$OSTYPE" == "linux-gnu"* ]]; then if [[ -f /etc/os-release ]]; then . /etc/os-release echo "$ID" elif [[ -f /etc/redhat-release ]]; then echo "rhel" elif [[ -f /etc/debian_version ]]; then echo "debian" else echo "linux" fi elif [[ "$OSTYPE" == "darwin"* ]]; then echo "macos" else echo "unknown" fi } # Функция для проверки установки утилиты check_command() { local cmd="$1" local name="$2" if command -v "$cmd" &> /dev/null; then echo -e "${GREEN}✓ $name установлен${NC}" return 0 else echo -e "${RED}✗ $name не установлен${NC}" return 1 fi } # Функция для проверки установки brew (для macOS) check_brew() { if ! command -v brew &> /dev/null; then echo -e "${RED}✗ brew не установлен${NC}" echo -e "${YELLOW}Установите Homebrew командой:/bin/bash -c \"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"${NC}" exit 1 fi } # Функция для установки talosctl install_talosctl() { local os=$(detect_os) echo -e "\n${YELLOW}Установка talosctl...${NC}" case "$os" in "ubuntu"|"debian") echo "Для Ubuntu/Debian:" echo "curl -Lo /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/latest/download/talosctl-linux-amd64" echo "chmod +x /usr/local/bin/talosctl" ;; "rhel"|"centos"|"fedora"|"rocky"|"alma") echo "Для RHEL/CentOS/Fedora:" echo "curl -Lo /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/latest/download/talosctl-linux-amd64" echo "chmod +x /usr/local/bin/talosctl" ;; "macos") check_brew echo "Для macOS:" echo "brew install talosctl" echo "Или вручную:" echo "curl -Lo /usr/local/bin/talosctl https://github.com/siderolabs/talos/releases/latest/download/talosctl-darwin-amd64" echo "chmod +x /usr/local/bin/talosctl" ;; *) echo "Скачайте talosctl с https://github.com/siderolabs/talos/releases" ;; esac } # Функция для установки kubectl install_kubectl() { local os=$(detect_os) echo -e "\n${YELLOW}Установка kubectl...${NC}" case "$os" in "ubuntu"|"debian") echo "Для Ubuntu/Debian:" echo "sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl" echo "sudo curl -fsSLo /usr/share/keyrings/kubernetes-archive-keyring.gpg https://packages.cloud.google.com/apt/doc/apt-key.gpg" echo "echo \"deb [signed-by=/usr/share/keyrings/kubernetes-archive-keyring.gpg] https://apt.kubernetes.io/ kubernetes-xenial main\" | sudo tee /etc/apt/sources.list.d/kubernetes.list" echo "sudo apt-get update && sudo apt-get install -y kubectl" ;; "rhel"|"centos"|"fedora"|"rocky"|"alma") echo "Для RHEL/CentOS/Fedora:" echo "cat < 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 - name: drbd_transport_tcp - name: dm-thin-pool 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}" WORKER_IPS=() 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") WORKER_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 - name: drbd_transport_tcp - name: dm-thin-pool EOF if [[ "$USE_ZFS" == "y" ]]; then cat >> "$FILENAME" << EOF - name: zfs EOF fi if [[ "$USE_SPL" == "y" ]]; then cat >> "$FILENAME" << EOF - name: spl EOF fi if [[ "$USE_VFIO_PCI" == "y" ]]; then cat >> "$FILENAME" << EOF - name: vfio_pci EOF fi if [[ "$USE_VFIO_IOMMU_TYPE1" == "y" ]]; then cat >> "$FILENAME" << EOF - name: vfio_iommu_type1 EOF fi if [[ "$USE_OPENVSWITCH" == "y" ]]; then cat >> "$FILENAME" << EOF - name: openvswitch EOF fi 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 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 WORKER_IP=${WORKER_IPS[$((i-1))]} 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 # --- Применение конфигов и bootstrap --- echo -e "\n${GREEN}--- Применение конфигов и bootstrap ---${NC}" cd .. FIRST_CP_IP=${CP_IPS[0]} FIRST_CP_IP_CLEAN=$(echo "$FIRST_CP_IP" | cut -d'/' -f1) read -p "Применить конфиг на первом control-plane ($FIRST_CP_IP_CLEAN)? [Enter для продолжения]" talosctl apply-config --insecure -n $FIRST_CP_IP_CLEAN --file config/cp1.yaml echo -e "${GREEN}Применен конфиг на первом control-plane ($FIRST_CP_IP_CLEAN)${NC}" echo -e "${YELLOW}--------------------------------------------------${NC}" echo -e "${RED} Обязательно дождитесь ребута и предложения в логе о bootstrap ${NC}" echo -e "${GREEN}--------------------------------------------------${NC}" read -p "Выполнить bootstrap на первом control-plane ($FIRST_CP_IP_CLEAN)? [Enter для продолжения]" talosctl bootstrap --nodes $FIRST_CP_IP_CLEAN --endpoints $FIRST_CP_IP_CLEAN --talosconfig=config/talosconfig echo -e "${GREEN}Выполнен bootstrap на первом control-plane ($FIRST_CP_IP_CLEAN)${NC}" echo -e "${YELLOW}--------------------------------------------------${NC}" echo -e "${RED}Обазятельно дождитесь полного завершения bootstrap${NC}" echo -e "${YELLOW}--------------------------------------------------${NC}" echo "Остальные ноды можно применять без ожидания" echo -e "${GREEN}--------------------------------------------------${NC}" if (( CP_COUNT > 1 )); then for i in $(seq 2 $CP_COUNT); do CP_IP_CLEAN=$(echo "${CP_IPS[$((i-1))]}" | cut -d'/' -f1) read -p "Применить конфиг на control-plane $i ($CP_IP_CLEAN)? [Enter для продолжения]" talosctl apply-config --insecure -n $CP_IP_CLEAN --file config/cp${i}.yaml done fi if (( WORKER_COUNT > 0 )); then for i in $(seq 1 $WORKER_COUNT); do WORKER_IP=${WORKER_IPS[$((i-1))]} read -p "Применить конфиг на worker-$i ($WORKER_IP)? [Enter для продолжения]" talosctl apply-config --insecure -n $WORKER_IP --file config/worker${i}.yaml done fi # Выгрузка kubeconfig KUBECONFIG_ENDPOINT="" if [[ "$USE_VIP" == "y" && -n "$VIP_IP" ]]; then KUBECONFIG_ENDPOINT=$VIP_IP else KUBECONFIG_ENDPOINT=$FIRST_CP_IP_CLEAN fi talosctl kubeconfig ~/.kube/${CLUSTER_NAME}.yaml --nodes $KUBECONFIG_ENDPOINT --endpoints $KUBECONFIG_ENDPOINT --talosconfig config/talosconfig echo "Работа скрипта завершена"