Compare commits

..

3 Commits

Author SHA1 Message Date
Dreaded_X 8adee3cdbe Added tailscale
Also routes the whole subnet of the cluster over tailscale so it can act
as an entry point to my home network even when not at home.
2025-11-11 04:15:38 +01:00
Dreaded_X b7bf638475 Access node parameters through node instead of directly 2025-11-11 04:14:38 +01:00
Dreaded_X 4c4783953e Also load config settings from secrets.yaml 2025-11-11 04:14:33 +01:00
72 changed files with 528 additions and 438 deletions
+5 -1
View File
@@ -5,4 +5,8 @@ indent_style = tab
[*.yaml] [*.yaml]
indent_style = space indent_style = space
indent_size = 2 indent_size = 4
[{*.py,tools/render}]
indent_style = space
indent_size = 4
+1 -1
View File
@@ -1,2 +1,2 @@
*.key filter=git-crypt diff=git-crypt _secrets.yaml filter=git-crypt diff=git-crypt
secrets.yaml filter=git-crypt diff=git-crypt secrets.yaml filter=git-crypt diff=git-crypt
-1
View File
@@ -1,4 +1,3 @@
.ipxe/ .ipxe/
rendered/ rendered/
configs/ configs/
.vagrant/
-67
View File
@@ -1,67 +0,0 @@
default_install_hook_types:
- pre-commit
- commit-msg
default_stages:
- pre-commit
repos:
- repo: meta
hooks:
- id: check-hooks-apply
- id: check-useless-excludes
- repo: builtin
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-added-large-files
- id: check-merge-conflict
- id: check-executables-have-shebangs
- repo: https://github.com/python-jsonschema/check-jsonschema
rev: 0.37.1
hooks:
- id: check-jsonschema
files: ^talos/patches/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json",
]
- id: check-jsonschema
files: ^talos/nodes/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json",
]
- id: check-jsonschema
files: ^talos/clusters/.*\.y(a?)ml$
args:
[
"--schemafile",
"https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json",
]
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.1.0
hooks:
- id: prettier
- repo: https://github.com/crate-ci/typos
rev: v1.40.0
hooks:
- id: typos
- repo: https://github.com/sirwart/ripsecrets
rev: v0.1.11
hooks:
- id: ripsecrets-system
- repo: https://github.com/crate-ci/committed
rev: v1.1.8
hooks:
- id: committed
-2
View File
@@ -1,2 +0,0 @@
secrets.yaml
*.key
-6
View File
@@ -65,9 +65,3 @@ Upgrading talos or changing the schematic:
```bash ```bash
talosctl upgrade --nodes <node_id> --image factory.talos.dev/metal-installer/<schematic_id>:<version> talosctl upgrade --nodes <node_id> --image factory.talos.dev/metal-installer/<schematic_id>:<version>
``` ```
To upgrade kubernetes or inline manifests, first apply the updated controlplane configs, then run:
```bash
talosctl upgrade-k8s
```
Vendored
-28
View File
@@ -1,28 +0,0 @@
Vagrant.configure("2") do |config|
config.vm.define "talos-vm" do |vm|
vm.vm.network :private_network,
:type => "dhcp",
:libvirt__network_address => "192.168.1.0",
:libvirt__netmask => "255.255.255.0",
# :libvirt__dhcp_bootp_file => "ipxe.pxe"
:libvirt__dhcp_bootp_file => "http://192.168.1.1:8000/ipxe.pxe"
vm.vm.hostname = "talos"
vm.vm.provider :libvirt do |domain|
domain.cpus = 6
domain.memory = 16 * 1024
domain.storage :file, :size => '100G', :type => 'raw'
domain.mgmt_attach = false
domain.boot "hd"
domain.boot "network"
domain.sysinfo = {
"system": {
"serial": "talos-vm"
}
}
end
end
end
-2
View File
@@ -1,2 +0,0 @@
style = "conventional"
ignore_author_re = "Flux"
+6
View File
@@ -0,0 +1,6 @@
server:
tftpIp: 192.168.1.1
httpUrl: http://192.168.1.1:8000
tailscale:
loginServer: https://headscale.huizinga.dev
-2
View File
@@ -1,2 +0,0 @@
[env]
VAGRANT_DEFAULT_PROVIDER = "libvirt"
+31
View File
@@ -0,0 +1,31 @@
schematicId: !schematic default
arch: amd64
talosVersion: v1.11.3
kubernesVersion: v1.34.1
kernelArgs:
- talos.platform=metal
- console=tty0
- init_on_alloc=1
- init_on_free=1
- slab_nomerge
- pti=on
- consoleblank=0
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- selinux=1
- lockdown=confidentiality
extraKernelArgs: []
dns:
- 1.1.1.1
- 8.8.8.8
ntp: nl.pool.ntp.org
install: true
autoInstall: false
patches:
- !patch hostname
- !patch install-disk
- !patch network
- !patch vip
- !patch tailscale
patchesControlPlane:
- !patch allow-control-plane-workloads
+7
View File
@@ -0,0 +1,7 @@
netmask: 255.255.252.0
gateway: 10.0.0.1
installDisk: /dev/sda
cluster:
name: hellas
controlPlaneIp: 10.0.2.1
secretsFile: !realpath _secrets.yaml
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
serial: 5CZ7NX2
interface: enp2s0
ip: 10.0.0.202
type: "controlplane"
+4
View File
@@ -0,0 +1,4 @@
serial: F3PKRH2
interface: enp3s0
ip: 10.0.0.201
type: "controlplane"
+4
View File
@@ -0,0 +1,4 @@
serial: J33CHY2
interface: enp2s0
ip: 10.0.0.203
type: "controlplane"
+8
View File
@@ -0,0 +1,8 @@
netmask: 255.255.255.0
gateway: 192.168.1.1
installDisk: /dev/vda
autoInstall: true
cluster:
name: testing
controlPlaneIp: 192.168.1.100
secretsFile: !realpath _secrets.yaml
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
serial: talos-vm
interface: enp1s0
ip: 192.168.1.2
type: "controlplane"
@@ -0,0 +1,2 @@
cluster:
allowSchedulingOnControlPlanes: true
+3
View File
@@ -0,0 +1,3 @@
machine:
network:
hostname: {{node.hostname}}
+3
View File
@@ -0,0 +1,3 @@
machine:
install:
disk: {{node.installDisk}}
+10
View File
@@ -0,0 +1,10 @@
machine:
network:
interfaces:
- interface: {{node.interface}}
dhcp: false
addresses:
- {{node.ip}}
routes:
- network: 0.0.0.0/0
gateway: {{node.gateway}}
+7
View File
@@ -0,0 +1,7 @@
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: tailscale
environment:
- TS_AUTHKEY={{ config.tailscale.authKey }}
- TS_EXTRA_ARGS=--login-server {{ config.tailscale.loginServer }}
- TS_ROUTES={{ helper.tailscale_subnet(node.gateway, node.netmask) }}
+6
View File
@@ -0,0 +1,6 @@
machine:
network:
interfaces:
- interface: {{node.interface}}
vip:
ip: {{node.cluster.controlPlaneIp}}
+6
View File
@@ -0,0 +1,6 @@
PyYAML==6.0.3
requests==2.32.5
Jinja2==3.1.6
GitPython==3.1.45
mergedeep==1.3.4
netaddr==1.3.0
+8
View File
@@ -0,0 +1,8 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
- siderolabs/tailscale
BIN
View File
Binary file not shown.
-52
View File
@@ -1,52 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
version:
kubernetes: 1.35.3
talos: 1.12.6
base:
kernelArgs:
- talos.platform=metal
- console=tty0
- init_on_alloc=1
- init_on_free=1
- slab_nomerge
- pti=on
- consoleblank=0
- nvme_core.io_timeout=4294967295
- printk.devkmsg=on
- selinux=1
- lockdown=confidentiality
patches:
all:
- system/hostname.yaml
- system/install-disk.yaml
- system/network.yaml.jinja
- system/ntp.yaml
- system/dns.yaml.jinja
- networking/vip.yaml
- networking/tailscale.yaml
- networking/cilium.yaml
- spegel.yaml
- storage/longhorn.yaml
- storage/longhorn/user-volume.yaml
- storage/local-path-provisioner/user-volume.yaml
- storage/limit-ephemeral.yaml
- metrics/all.yaml
controlPlane:
- system/allow-control-plane-workloads.yaml
- sops.yaml
- flux/cluster-variables.yaml
- metrics/control-plane.yaml
- networking/gateway-api.yaml
default:
arch: amd64
schematic: default.yaml
network:
dns:
- 1.1.1.1
- 8.8.8.8
tailscale:
server: https://headscale.huizinga.dev
ntp: nl.pool.ntp.org
install:
auto: true
-20
View File
@@ -1,20 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
clusterEnv: staging
controlPlaneIp: 192.168.1.100
secretsFile: testing/secrets.yaml
nodes:
- testing/talos-vm
- testing/phobos
default:
network:
interface: ens5
netmask: 255.255.255.0
gateway: 192.168.1.1
tailscale:
authKey:
file: testing/tailscale.key
sops:
file: testing/age.key
install:
disk: /dev/vda
-20
View File
@@ -1,20 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/cluster.json
clusterEnv: production
controlPlaneIp: 10.0.2.1
secretsFile: titan/secrets.yaml
nodes:
- titan/hyperion
- titan/helios
- titan/selene
default:
network:
netmask: 255.255.252.0
gateway: 10.0.0.1
tailscale:
authKey:
file: testing/tailscale.key
sops:
file: titan/age.key
install:
disk: /dev/sda
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: worker
network:
ip: 192.168.178.77
netmask: 255.255.255.0
gateway: 192.168.178.1
install:
disk: /dev/sda
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: talos-vm
network:
ip: 192.168.1.2
tailscale:
advertiseRoutes: true
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: 5CZ7NX2
network:
interface: enp2s0
ip: 10.0.0.202
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: F3PKRH2
network:
interface: enp3s0
ip: 10.0.0.201
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://git.huizinga.dev/infra/crete/raw/branch/main/schemas/node.json
type: controlPlane
install:
serial: J33CHY2
network:
interface: enp2s0
ip: 10.0.0.203
-18
View File
@@ -1,18 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
inlineManifests:
- name: cluster-variables
contents: |
---
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cluster-variables
namespace: flux-system
data:
cluster_env: {{ cluster.clusterEnv }}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
kubelet:
extraArgs:
rotate-server-certificates: "true"
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
extraManifests:
- https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/main/deploy/standalone-install.yaml
- https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
-12
View File
@@ -1,12 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
features:
hostDNS:
# This option is enabled by default and causes issues with cilium
forwardKubeDNSToHost: false
cluster:
network:
cni:
name: none
proxy:
disabled: true
@@ -1,4 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
extraManifests:
- https://github.com/kubernetes-sigs/gateway-api/releases/download/v1.4.1/standard-install.yaml
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: ExtensionServiceConfig
name: tailscale
environment:
- TS_AUTHKEY={{ node.network.tailscale.authKey }}
- TS_EXTRA_ARGS={% if node.network.tailscale.server %}--login-server {{ node.network.tailscale.server }}{% endif +%}
- TS_ROUTES={% if node.network.tailscale.advertiseRoutes %}{{node.network.ip}}/{{ node.network.netmask | to_prefix }}{% endif %}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: Layer2VIPConfig
name: "{{ cluster.controlPlaneIp }}"
link: "{{ node.network.interface }}"
-18
View File
@@ -1,18 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
inlineManifests:
- name: sops-key
contents: |
apiVersion: v1
kind: Namespace
metadata:
name: flux-system
---
apiVersion: v1
kind: Secret
metadata:
name: sops-gpg
namespace: flux-system
stringData:
age.agekey: |
{{ node.sops | indent(6*2) }}
-8
View File
@@ -1,8 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
files:
- path: /etc/cri/conf.d/20-customization.part
op: create
content: |
[plugins."io.containerd.cri.v1.images"]
discard_unpacked_layers = false
@@ -1,6 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: VolumeConfig
name: EPHEMERAL
provisioning:
maxSize: 30GB
@@ -1,9 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: local-path-provisioner
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 10GB
-11
View File
@@ -1,11 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
kubelet:
extraMounts:
- destination: /var/lib/longhorn
type: bind
source: /var/lib/longhorn
options:
- bind
- rshared
- rw
@@ -1,9 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: UserVolumeConfig
name: longhorn
provisioning:
diskSelector:
match: system_disk
grow: true
maxSize: 2000GB
-17
View File
@@ -1,17 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
# This is only needed on nodes that will have storage
sysctls:
vm.nr_hugepages: "1024"
nodeLabels:
openebs.io/engine: mayastor
# This is needed on ALL nodes
kubelet:
extraMounts:
- destination: /var/local
type: bind
source: /var/local
options:
- bind
- rshared
- rw
@@ -1,3 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
cluster:
allowSchedulingOnControlPlanes: true
-7
View File
@@ -1,7 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: ResolverConfig
nameservers:
{% for dns in node.network.dns %}
- address: {{ dns }}
{% endfor %}
-5
View File
@@ -1,5 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: HostnameConfig
hostname: "{{node.hostname}}"
auto: "off"
-4
View File
@@ -1,4 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
machine:
install:
disk: "{{node.install.disk}}"
-10
View File
@@ -1,10 +0,0 @@
# yaml-language-server: $schema=https://raw.githubusercontent.com/siderolabs/talos/refs/heads/release-1.12/website/content/v1.12/schemas/config.schema.json
apiVersion: v1alpha1
kind: LinkConfig
name: "{{node.network.interface}}"
up: true
mtu: 9000
addresses:
- address: "{{node.network.ip}}/{{ node.network.netmask | to_prefix }}"
routes:
- gateway: "{{node.network.gateway}}"
-5
View File
@@ -1,5 +0,0 @@
apiVersion: v1alpha1
kind: TimeSyncConfig
ntp:
servers:
- "{{ node.ntp }}"
-8
View File
@@ -1,8 +0,0 @@
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/intel-ucode
- siderolabs/i915
- siderolabs/tailscale
-9
View File
@@ -1,9 +0,0 @@
overlay:
name: rpi_generic
image: siderolabs/sbc-raspberrypi
customization:
systemExtensions:
officialExtensions:
- siderolabs/iscsi-tools
- siderolabs/util-linux-tools
- siderolabs/tailscale
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+11 -9
View File
@@ -1,4 +1,3 @@
{% set httpUrl = "http://192.168.1.1:8000" -%}
#!ipxe #!ipxe
dhcp dhcp
@@ -8,15 +7,18 @@ echo Starting ${serial}
goto node_${serial} || exit goto node_${serial} || exit
# Default behavior (non install mode) is to exit iPXE script # Default behavior (non install mode) is to exit iPXE script
{% for cluster in clusters%} {% for node in nodes %}
{% for node in cluster.nodes %} {%- if node.install -%}
{%- if node.install.serial -%} # {{ node.filename }}
# {{ cluster.name }}/{{ node.hostname }} :node_{{ node.serial }}
:node_{{ node.install.serial }} {% set ipArg = "ip=" ~ [node.ip, "" , node.gateway, node.netmask, node.hostname, node.interface, "", node.dns[0], node.dns[1], node.ntp]|join(":") -%}
{% set kernelArgs = [ipArg, node.kernelArgs|join(" "), node.extraKernelArgs|join(" ")] -%}
{% if node.autoInstall %}
{% do kernelArgs.append("talos.config=" ~ config.server.httpUrl ~ "/configs/" ~ node.filename ~ ".yaml") %}
{% endif %}
imgfree imgfree
kernel https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/kernel-{{ node.arch }} {{ node.kernelArgs|join(" ") }} {% if node.install.auto %}talos.config={{httpUrl}}/configs/{{cluster.name}}/{{node.hostname}}.yaml{% endif +%} kernel https://pxe.factory.talos.dev/image/{{ node.schematicId }}/{{ node.talosVersion }}/kernel-{{ node.arch }} {{ kernelArgs|join(" ") }}
initrd https://pxe.factory.talos.dev/image/{{ node.schematic }}/v{{ cluster.version.talos }}/initramfs-{{ node.arch }}.xz initrd https://pxe.factory.talos.dev/image/{{ node.schematicId }}/{{ node.talosVersion }}/initramfs-{{ node.arch }}.xz
boot boot
{% endif %} {% endif %}
{% endfor %} {% endfor %}
{% endfor %}
+1 -1
View File
@@ -1,4 +1,4 @@
{% set tftpIp = "192.168.1.1" -%} {% set tftpIp = config.server.tftpIp -%}
enable-tftp enable-tftp
tftp-root=/tftproot tftp-root=/tftproot
+39
View File
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -euo pipefail
CONFIGS={{ root }}/configs
# Generate the configuration for each node
{% for node in nodes -%}
talosctl gen config {{ node.cluster.name }} https://{{ node.cluster.controlPlaneIp }}:6443 -f \
--with-secrets {{ node.cluster.secretsFile }} \
--talos-version {{ node.talosVersion }} \
--kubernetes-version {{ node.kubernesVersion }} \
--output-types {{ node.type }} \
--install-image factory.talos.dev/metal-installer/{{ node.schematicId }}:{{ node.talosVersion }} \
{% for patch in node.patches -%}
{# The double call to tojson is needed to properly escape the patch (object -> json -> string) -#}
--config-patch {{ patch|tojson|tojson }} \
{% endfor -%}
{% for patch in node.patchesControlPlane -%}
--config-patch-control-plane {{ patch|tojson|tojson }} \
{% endfor -%}
--with-docs=false \
--with-examples=false \
-o ${CONFIGS}/{{ node.filename }}.yaml
{% endfor %}
# Generate the talosconfig file for each cluster
{% for cluster in clusters -%}
talosctl gen config {{ cluster.name }} https://{{ cluster.controlPlaneIp }}:6443 -f \
--with-secrets {{ cluster.secretsFile }} \
--output-types talosconfig \
-o ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}
# Create merged talosconfig
TALOSCONFIG=${CONFIGS}/talosconfig
rm -f ${TALOSCONFIG}
{% for cluster in clusters -%}
talosctl config --talosconfig=${CONFIGS}/{{ cluster.name }}/talosconfig endpoint {{ cluster.controlPlaneIp }}
talosctl config --talosconfig=${TALOSCONFIG} merge ${CONFIGS}/{{ cluster.name }}/talosconfig
{% endfor %}
+5 -1
View File
@@ -1,2 +1,6 @@
export TALOSCONFIG={{ root }}/configs/talosconfig export TALOSCONFIG={{ root }}/configs/talosconfig
export KUBECONFIG={{ clusters|map(attribute='name')|kubeconfig|join(":") }} {% set paths = [] %}
{%- for cluster in clusters -%}
{%- do paths.append(root ~ "/configs/" ~ cluster.name ~ "/kubeconfig") -%}
{% endfor -%}
export KUBECONFIG={{ paths|join(":") }}
Executable
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
# Adapted from: https://enix.io/en/blog/pxe-talos/
import functools
import json
import pathlib
import sys
import git
import requests
import yaml
from jinja2 import Environment, FileSystemLoader, StrictUndefined, Template
from mergedeep import merge
from netaddr import IPAddress
REPO = git.Repo(sys.path[0], search_parent_directories=True)
assert REPO.working_dir is not None
ROOT = pathlib.Path(REPO.working_dir)
NODES = ROOT.joinpath("nodes")
SCHEMATICS = ROOT.joinpath("schematics")
RENDERED = ROOT.joinpath("rendered")
EXTENSIONS = ["jinja2.ext.do"]
PATCHES = Environment(
loader=FileSystemLoader(ROOT.joinpath("patches")),
undefined=StrictUndefined,
extensions=EXTENSIONS,
)
TEMPLATES = Environment(
loader=FileSystemLoader(ROOT.joinpath("templates")),
undefined=StrictUndefined,
extensions=EXTENSIONS,
)
def render_templates(node: dict, args: dict):
class Inner(json.JSONEncoder):
def default(self, o):
if isinstance(o, Template):
try:
rendered = o.render(args | {"node": node})
except Exception as e:
e.add_note(f"While rendering for: {node['hostname']}")
raise e
# Parse the rendered yaml
return yaml.safe_load(rendered)
return super().default(o)
return Inner
def tailscale_subnet(gateway: str, netmask: str):
netmask_bits = IPAddress(netmask).netmask_bits()
return f"{IPAddress(gateway) & IPAddress(netmask)}/{netmask_bits}"
@functools.cache
def get_schematic_id(schematic: str):
"""Lookup the schematic id associated with a given schematic"""
r = requests.post("https://factory.talos.dev/schematics", data=schematic)
r.raise_for_status()
data = r.json()
return data["id"]
def schematic_constructor(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
"""Load specified schematic file and get the assocatied schematic id"""
schematic_name = loader.construct_yaml_str(node)
try:
schematic = SCHEMATICS.joinpath(schematic_name).with_suffix(".yaml").read_text()
return get_schematic_id(schematic)
except Exception:
raise yaml.MarkedYAMLError("Failed to load schematic", node.start_mark)
def template_constructor(environment: Environment):
def inner(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
patch_name = loader.construct_scalar(node)
try:
template = environment.get_template(f"{patch_name}.yaml")
return template
except Exception:
raise yaml.MarkedYAMLError("Failed to load patch", node.start_mark)
return inner
def realpath_constructor(directory: pathlib.Path):
def inner(loader: yaml.SafeLoader, node: yaml.nodes.ScalarNode):
try:
realpath = directory.joinpath(loader.construct_scalar(node)).resolve(
strict=True
)
return str(realpath)
except Exception:
raise yaml.MarkedYAMLError("Failed to get real path", node.start_mark)
return inner
def get_loader(directory: pathlib.Path):
"""Add special constructors to yaml loader"""
loader = yaml.SafeLoader
loader.add_constructor("!realpath", realpath_constructor(directory))
loader.add_constructor("!schematic", schematic_constructor)
loader.add_constructor("!patch", template_constructor(PATCHES))
return loader
@functools.cache
def get_defaults(directory: pathlib.Path, root: pathlib.Path):
"""Compute the defaults from the provided directory and parents."""
try:
with open(directory.joinpath("_defaults.yaml")) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader(directory))
except OSError:
yml_data = {}
# Stop recursion when reaching root directory
if directory != root:
return get_defaults(directory.parent, root) | yml_data
else:
return yml_data
def walk_files(root: pathlib.Path):
"""Get all files that do not start with and underscore"""
for dirpath, _dirnames, filenames in root.walk():
for fn in filenames:
if not fn.startswith("_"):
yield dirpath.joinpath(fn)
def main():
with open(ROOT.joinpath("config.yaml")) as fyaml:
config = yaml.safe_load(fyaml)
with open(ROOT.joinpath("secrets.yaml")) as fyaml:
merge(config, yaml.safe_load(fyaml))
print(config)
template_args = {
"config": config,
"root": ROOT,
"helper": {"tailscale_subnet": tailscale_subnet},
}
nodes = []
for fullname in walk_files(NODES):
filename = str(fullname.relative_to(NODES).parent) + "/" + fullname.stem
with open(fullname) as fyaml:
yml_data = yaml.load(fyaml, Loader=get_loader(fullname.parent))
yml_data = get_defaults(fullname.parent, NODES) | yml_data
yml_data["hostname"] = fullname.stem
yml_data["filename"] = filename
nodes.append(yml_data)
# Quick and dirty way to resolve all the templates using a custom encoder
nodes = list(
map(
lambda node: json.loads(
json.dumps(node, cls=render_templates(node, template_args))
),
nodes,
)
)
# Get all clusters
# NOTE: This assumes that all nodes in the cluster use the same definition for the cluster
clusters = [
dict(s) for s in set(frozenset(node["cluster"].items()) for node in nodes)
]
template_args |= {"nodes": nodes, "clusters": clusters}
RENDERED.mkdir(exist_ok=True)
for template_name in TEMPLATES.list_templates():
template = TEMPLATES.get_template(template_name)
rendered = template.render(template_args)
with open(RENDERED.joinpath(template_name), "w") as f:
f.write(rendered)
if __name__ == "__main__":
main()
+6 -2
View File
@@ -6,7 +6,7 @@ IPXE_VERSION=b41bda4413bf286d7b7a449bc05e1531da1eec2e
IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi) IPXE_BIN=(bin/ipxe.pxe bin-x86_64-efi/ipxe.efi)
IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION} IPXE_DIR=${ROOT}/.ipxe/ipxe-${IPXE_VERSION}
HTTP_URL="http://192.168.1.1:8000" HTTP_URL=$(cat ${ROOT}/config.yaml | yq .server.httpUrl)
function download_ipxe() { function download_ipxe() {
base_dir=$(dirname ${IPXE_DIR}) base_dir=$(dirname ${IPXE_DIR})
@@ -44,6 +44,10 @@ function build_ipxe() {
cd - > /dev/null cd - > /dev/null
} }
function render() {
${ROOT}/tools/render
${ROOT}/rendered/generate_configs.sh
}
function host_tftp() { function host_tftp() {
TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX) TFTP_DIR=$(mktemp --tmpdir -d tftp.XXX)
@@ -88,5 +92,5 @@ function host_http() {
download_ipxe download_ipxe
patch_ipxe patch_ipxe
build_ipxe build_ipxe
crete generate render
host_http host_http
Executable
+153
View File
@@ -0,0 +1,153 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT=$(git rev-parse --show-toplevel)
VM_NAME="talos-vm"
VCPUS="2"
RAM_MB="2048"
DISK_GB="10"
NETWORK=talos
CONNECTION="qemu:///system"
function define_network() {
config_file=$(mktemp)
cat > ${config_file} << EOF
<network>
<name>${NETWORK}</name>
<bridge name="talos0" stp="on" delay="0"/>
<forward mode='nat'>
<nat/>
</forward>
<ip address="192.168.1.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.1.2" end="192.168.1.254"/>
<bootp file='http://192.168.1.1:8000/ipxe.pxe'/>
</dhcp>
</ip>
</network>
EOF
function cleanup() {
rm ${config_file}
}
trap cleanup EXIT
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") == "0" ]]; then
virsh --connect="${CONNECTION}" net-define "${config_file}"
virsh --connect="${CONNECTION}" net-start "${NETWORK}"
virsh --connect="${CONNECTION}" net-autostart "${NETWORK}"
fi
trap - EXIT
cleanup
}
function create() {
define_network
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") == "0" ]]; then
virt-install --connect="${CONNECTION}" --name="${VM_NAME}" --vcpus="${VCPUS}" --memory="${RAM_MB}" \
--os-variant="linux2022" \
--disk="size=${DISK_GB}" \
--pxe \
--sysinfo system.serial=${VM_NAME} \
--network network="${NETWORK}"
else
echo -n "VM already exists, start it with:
${0} start
"
exit -1
fi
}
function start() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" start ${VM_NAME}
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo -n "VM doest not exists yet, create it with:
${0} create
"
exit -1
fi
}
function connect() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virt-viewer --connect="${CONNECTION}" ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function stop() {
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" shutdown ${VM_NAME}
WAIT=240
for i in $(seq 0 1 ${WAIT}); do
echo -en "\rWaiting for VM to shutdown... (${i}/${WAIT})"
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") == "0" ]]; then
echo -e "\nVM successfully shutdown"
exit
fi
sleep 1
done
echo -e "\nDestroying VM"
virsh --connect="${CONNECTION}" destroy ${VM_NAME}
else
echo "VM is not running"
exit -1
fi
}
function delete() {
if [[ $(virsh --connect="${CONNECTION}" list --all | grep -c "${VM_NAME}") > "0" ]]; then
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" destroy "${VM_NAME}"
fi
virsh --connect="${CONNECTION}" undefine "${VM_NAME}" --remove-all-storage
fi
if [[ $(virsh --connect="${CONNECTION}" net-list --all | grep -c "${NETWORK}") > "0" ]]; then
if [[ $(virsh --connect="${CONNECTION}" list | grep -c "${VM_NAME}") > "0" ]]; then
virsh --connect="${CONNECTION}" net-destroy "${NETWORK}"
fi
virsh --connect="${CONNECTION}" net-undefine "${NETWORK}"
fi
}
function help() {
echo -n "Available commands:
start
stop
remove
connect
"
}
COMMAND=${1:-}
case ${COMMAND} in
create)
create Create the vm and perform first install
;;
start)
start Start the vm
;;
stop)
stop Stop the vm
;;
delete)
delete Delete the vm
;;
connect)
connect Connect to an already running vm
;;
*)
help
;;
esac