From 2eb6ddf321e18787c5077eb6c3eefd68a7c435f2 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Fri, 15 Dec 2023 23:32:14 +0100 Subject: [PATCH] Initial commit Signed-off-by: Andrei Kvapil --- LICENSE | 201 +++++++++++++++++++++++++++++++++++++ README.md | 33 +++++++ talos-bootsrtap | 257 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 491 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100755 talos-bootsrtap diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9fbd137 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# talos-bootstrap + +An interactive script for bootstrapping Kubernetes clusters on Talos OS. + +*Example: bootstrap full-feature Kubernetes cluster in 5 minutes*: +[![screencast](https://raw.githubusercontent.com/aenix-io/talos-bootstrap/2cc7b82065747e373cd914c87c8cd5c6582c5c6c/images/627123.gif)](https://asciinema.org/a/gwK85Tdr577GsxjXWo7otPFjv) + +# Installation + +Install dependencies: +- `talosctl` +- `dialog` +- `nmap` + +Install talos-bootstrap: + +``` +curl -LO https://github.com/aenix-io/talos-bootstrap/raw/master/talos-bootstrap +chmod +x ./talos-bootstrap +sudo mv ./talos-bootstrap /usr/local/bin/talos-bootstrap +``` + +# Usage + +- Boot your nodes with Talos in maintenance mode. + (booting from [ISO](https://www.talos.dev/v1.5/talos-guides/install/bare-metal-platforms/iso/) or PXE using [matchbox](https://www.talos.dev/latest/talos-guides/install/bare-metal-platforms/matchbox/) is the best option) +- Create a directory for holding your cluster configuration. +- Run `talos-bootstrap` command for every node in your cluster. + +# Copyright + +Andrei Kvapil +Licensed under Apache 2.0 License diff --git a/talos-bootsrtap b/talos-bootsrtap new file mode 100755 index 0000000..f5aad77 --- /dev/null +++ b/talos-bootsrtap @@ -0,0 +1,257 @@ +#!/bin/sh +# +# Copyright 2023 Andrei Kvapil. All rights reserved. +# SPDX-License-Identifier: Apache2.0 +# +# This code should (try to) follow Google's Shell Style Guide +# - https://google.github.io/styleguide/shell.xml +# + +should_bootstrap=0 +if [ ! -f controlplane.yaml ]; then + should_bootstrap=1 + # Screen: Enter cluster name + default_cluster_name=$(basename "$PWD") + if [ -z "$default_cluster_name" ] || [ "$default_cluster_name" = "/" ]; then + default_cluster_name=talos + fi + cluster_name=$(dialog --title talos-bootstrap --inputbox "Enter cluster name:" 8 40 "$default_cluster_name" 3>&1 1>&2 2>&3) || exit 0 +fi + +# Screen: Enter networks to scan +default_scan_networks=$(ip -o route | awk '$3 !~ /^(docker|cni)/ && $2 == "dev" {print $1}' | awk '$1=$1' RS=" " OFS=" ") +scan_networks=$(dialog --title talos-bootstrap --inputbox "Enter networks to scan:" 8 80 "$default_scan_networks" 3>&1 1>&2 2>&3) || exit 0 +scan_networks=$(echo "$scan_networks" | awk -F, '{$1=$1}1' OFS=' ') + +node_list_file=$(mktemp) + +# Screen: Seatching Talos nodes +{ + printf "%s\nXXX\n%s\n%s\nXXX\n" "10" "Searching Talos nodes in $scan_networks..." + candidate_nodes=$(nmap -Pn -n -p 50000 $scan_networks -vv | awk '/Discovered open port/ {print $NF}') + + #echo found: + #printf " - %s\n" $candidate_nodes + + printf "%s\nXXX\n%s\n%s\nXXX\n" "40" "Filtering nodes in maintenance mode..." + nodes= + for node in $candidate_nodes; do + if talosctl -n "$node" get info -i >/dev/null 2>/dev/null; then + nodes="$nodes $node" + fi + done + + #echo filtered: + #printf " - %s\n" $nodes + + printf "%s\nXXX\n%s\n%s\nXXX\n" "60" "Collecting information about the nodes..." + node_list=$( + seen= + for node in $nodes; do + mac=$(talosctl -n "$node" get hardwareaddresses.net.talos.dev first -i -o jsonpath={.spec.hardwareAddr}) + case " $seen " in *" $mac "*) continue ;; esac # remove duplicated nodes + seen="$seen $mac" + name="$node" + hostname=$(talosctl -n "$node" get hostname -i -o jsonpath='{.spec.hostname}') + if [ -n "$hostname" ]; then + name="$name ($hostname)" + fi + manufacturer=$(talosctl -n "$node" get cpu -i -o jsonpath={.spec.manufacturer} | head -n1) + cpu=$(talosctl -n "$node" get cpu -i -o jsonpath={.spec.threadCount} -i | awk '{sum+=$1;} END{print sum "-core";}') + ram=$(talosctl -n "$node" get ram -o jsonpath={.spec.sizeMiB} -i | awk '{sum+=$1;} END{print sum/1024 "GB";}') + disks=$(talosctl -n "$node" disks -i | awk -F' +' 'NR>1 {print $1 ":" $9}' | awk -F/ '$2 == "dev" {print $3}' | awk 'gsub(" ", "", $0)' | awk '$1=$1' RS="," OFS=",") + echo "\"$name\"" "\"$mac, $cpu ${manufacturer:-CPU}, RAM: $ram, Disks: [$disks]\"" + done + ) + + echo "$node_list" > "$node_list_file" +} | dialog --title talos-bootstrap --gauge "Please wait" 10 70 0 3>&1 1>&2 2>&3 || exit 0 + +node_list=$(cat "$node_list_file") + +if [ -z "$node_list" ]; then + dialog --title talos-bootstrap --msgbox "No Talos nodes in maintenance mode found! + +Searched networks: $scan_networks" 10 60 + exit 1 +fi + +# Screen: Node list +node=$(echo "$node_list" | dialog --title talos-bootstrap --menu "Select node to bootstrap" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 +# cut hostname +node=$(echo "$node" | awk '{print $1}') + +# Screen: Select role +role=$(dialog --title talos-bootstrap --radiolist "Select role" 15 60 4 \ + "controlplane" "Responsible for running cluster components" ON \ + "worker" "Responsible for running workloads" OFF 3>&1 1>&2 2>&3) || exit 0 + +# Screen: Select hostname +default_hostname=$(talosctl -n "$node" get hostname -i -o jsonpath='{.spec.hostname}') +hostname=$(dialog --title talos-bootstrap --inputbox "Enter hostname:" 8 40 "$default_hostname" 3>&1 1>&2 2>&3) || exit 0 + +# Screen: Select disk to install +disks_list=$(talosctl -n "$node" disks -i | awk 'NR>1 {printf "\"" $1 "\""; $1=""; print " \"" $0 "\""}') +disk=$(echo "$disks_list" | dialog --title talos-bootstrap --menu "Select disk to install" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 + +# Screen: Select interface +link_list=$(talosctl -n "$node" get link -i | awk -F' +' 'NR>1 && $4 ~ /^(ID|eno|eth|enp|enx)/ {print $4 "|" $(NF-2)}') +address_list=$(talosctl -n "$node" get addresses -i | awk 'NR>1 {print $NF " " $(NF-1)}') || exit 0 + +interface_list=$( + for link_mac in $link_list; do + link="${link_mac%%|*}" + mac="${link_mac#*|}" + ips=$(echo "$address_list" | awk "\$1 == \"$link\" {print \$2}" | awk '$1=$1' RS=, OFS=,) + details="$mac" + if [ -n "$ips" ]; then + details="$mac ($ips)" + fi + echo "\"$link\" \"$details\"" + done +) + +default_mac=$(talosctl -n "$node" get hardwareaddresses.net.talos.dev first -i -o jsonpath={.spec.hardwareAddr}) +default_interface=$(echo "$link_list" | awk -F'|' "\$2 == \"$default_mac\" {print \$1}") + +interface=$(echo "$interface_list" | dialog --title talos-bootstrap --default-item "$default_interface" --menu "Select interface:" 0 0 0 --file /dev/stdin 3>&1 1>&2 2>&3) || exit 0 + +# Screen: Configure networks +default_addresses=$(talosctl -n "$node" get nodeaddress default -i -o jsonpath={.spec.addresses[*]} | awk '$1=$1' RS=, OFS=,) +addresses=$(dialog --title talos-bootstrap --inputbox "Enter addresses:" 8 40 "$default_addresses" 3>&1 1>&2 2>&3) || exit 0 +addresses=$(echo "$addresses" | awk '{$1=$1}1' OFS=",") + +# Screen: Configure default gateway +default_gateway=$(talosctl -n "$node" get routes -i -o jsonpath={.spec.gateway} | grep -v '^$' -m1) +gateway=$(dialog --title talos-bootstrap --inputbox "Enter gateway:" 8 40 "$default_gateway" 3>&1 1>&2 2>&3) || exit 0 + +# Screen: Configure DNS servers +default_dns_servers=$(talosctl -n "$node" get resolvers resolvers -i -o jsonpath='{.spec.dnsServers[*]}' | awk '$1=$1' RS=" " OFS=" ") +dns_servers=$(dialog --title talos-bootstrap --inputbox "Enter DNS servers:" 8 80 "$default_dns_servers" 3>&1 1>&2 2>&3) || exit 0 +dns_servers=$(echo "$dns_servers" | awk '{$1=$1}1' OFS=",") + +if [ $role = controlplane ]; then + # Screen: Configure VIP + default_vip_addres="" + if [ -f controlplane.yaml ]; then + default_vip_addres=$(awk '/vip:$/ {while(getline) {if ($1 == "ip:") {print $2; exit}}}' controlplane.yaml) + fi + vip_address=$(dialog --title talos-bootstrap --inputbox "Enter virtual shared IP address, or leave blank to skip:" 8 40 "$default_vip_addres" 3>&1 1>&2 2>&3) || exit 0 +fi + +# Screen: Configure Kubernetes endpoint +default_k8s_endpoint=https://${vip_address:-${gateway}0}:6443 +if [ -f controlplane.yaml ]; then + default_k8s_endpoint=$(awk '$1 == "endpoint:" && $2 ~ /^http/ {print $2; exit}' controlplane.yaml) +fi +k8s_endpoint=$(dialog --title talos-bootstrap --inputbox "Enter Kubernetes endpoint:" 8 40 "$default_k8s_endpoint" 3>&1 1>&2 2>&3) || exit 0 + +# Screen: Confirm configuration +machine_config=$(cat < "$file" + +dialog --title talos-bootstrap --ok-label "Install" --extra-button --extra-label "Cancel" --textbox "$file" 0 0 || exit 0 +rm -f "$file" +trap '' EXIT + +# Swap IP addresses +bootstrap_ip=$node +node=$(echo "$addresses" | awk -F/ '{print $1}') + +# Screen: Installation process +{ + printf "%s\nXXX\n%s\n%s\nXXX\n" "0" "Generating configuration..." + if [ ! -f secrets.yaml ]; then + talosctl gen secrets >/dev/null 2>&1 + fi + talosctl gen config "$cluster_name" --with-secrets=secrets.yaml "$k8s_endpoint" --config-patch="$machine_config" --force >/dev/null 2>&1 + printf "%s\nXXX\n%s\n%s\nXXX\n" "1" "Applying configuration..." + talosctl --talosconfig=talosconfig apply -e "$bootstrap_ip" -n "$bootstrap_ip" -f controlplane.yaml -i >/dev/null 2>&1 + + printf "%s\nXXX\n%s\n%s\nXXX\n" "10" "Installing..." + + old_is_up=1 + old_is_pingable=1 + new_is_pingable=0 + new_is_up=0 + until [ "$new_is_up" = 1 ] ; do + sleep 0.2 + if [ "$new_is_pingable" = 0 ]; then + if [ "$old_is_up" = 1 ]; then + timeout 1 talosctl --talosconfig=talosconfig -e "$node" -n "$node" get info -i >/dev/null 2>&1 + if [ $? = 124 ]; then + old_is_up=0 + fi + else + if ! ping -W1 -c1 "$node" >/dev/null 2>&1; then + if ! ping -W1 -c1 "$node" >/dev/null 2>&1; then # TODO dirty hack + old_is_pingable=0 + fi + fi + fi + fi + + if [ "$old_is_pingable" = 0 ]; then + if ping -W1 -c1 "$node" >/dev/null 2>&1; then + new_is_pingable=1 + fi + fi + + if timeout 1 talosctl --talosconfig=talosconfig -e "$node" -n "$node" get info >/dev/null 2>&1; then + new_is_up=1 + fi + + case $old_is_up$old_is_pingable$new_is_pingable in + 110) printf "%s\nXXX\n%s\n%s\nXXX\n" "20" "Installing... (port is open at $node)" ;; + 010) printf "%s\nXXX\n%s\n%s\nXXX\n" "50" "Rebooting... (node is pingable at $node)" ;; + 000) printf "%s\nXXX\n%s\n%s\nXXX\n" "70" "Rebooting... (node is not pingable)" ;; + 001) printf "%s\nXXX\n%s\n%s\nXXX\n" "80" "Rebooting... (node is pingable again at $node)" ;; + esac + done +} | dialog --title talos-bootstrap --gauge "Please wait" 10 70 0 3>&1 1>&2 2>&3 + +# Save kubeconfig +if [ ! -f kubeconfig ]; then + KUBECONFIG=kubeconfig talosctl --talosconfig=talosconfig -e "$node" -n "$node" kubeconfig -f +fi + +if [ "$should_bootstrap" = 1 ]; then + talosctl --talosconfig=talosconfig -e "$node" -n "$node" bootstrap +fi + +# Screen: Complete installation +dialog --title talos-bootstrap --msgbox "Installation finished! + +You will now be directed to the dashboard" 0 0 + +# Screen: Talos dashboard +talosctl --talosconfig=talosconfig -e "$node" -n "$node" dashboard +clear