diff --git a/.devcontainer/ci/Dockerfile b/.devcontainer/ci/Dockerfile new file mode 100644 index 00000000..e6e945b4 --- /dev/null +++ b/.devcontainer/ci/Dockerfile @@ -0,0 +1,2 @@ +# Ref: https://github.com/devcontainers/ci/issues/191 +FROM mcr.microsoft.com/devcontainers/base:alpine diff --git a/.devcontainer/ci/devcontainer.json b/.devcontainer/ci/devcontainer.json new file mode 100644 index 00000000..2064da8c --- /dev/null +++ b/.devcontainer/ci/devcontainer.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://raw.githubusercontent.com/devcontainers/spec/main/schemas/devContainer.schema.json", + "name": "Flux Cluster Template (CI)", + "build": { + "dockerfile": "./Dockerfile", + "context": "." + }, + "features": { + "./features": {} + }, + "customizations": { + "vscode": { + "settings": { + "terminal.integrated.profiles.linux": { + "bash": { + "path": "/usr/bin/fish" + } + }, + "terminal.integrated.defaultProfile.linux": "fish" + }, + "extensions": [ + "redhat.vscode-yaml" + ] + } + } +} diff --git a/.devcontainer/ci/features/devcontainer-feature.json b/.devcontainer/ci/features/devcontainer-feature.json new file mode 100644 index 00000000..3a7322e3 --- /dev/null +++ b/.devcontainer/ci/features/devcontainer-feature.json @@ -0,0 +1,6 @@ +{ + "name": "Cluster Template", + "id": "cluster-template", + "version": "1.0.0", + "description": "Work environment for the Cluster Template project" +} diff --git a/.devcontainer/ci/features/install.sh b/.devcontainer/ci/features/install.sh new file mode 100644 index 00000000..32122c74 --- /dev/null +++ b/.devcontainer/ci/features/install.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +set -e +set -o noglob + +apk add --no-cache \ + age bash bind-tools ca-certificates curl direnv fish fzf \ + gettext git github-cli helm iputils jq k9s python3 py3-pip \ + moreutils openssh-client openssl starship yq + +apk add --no-cache \ + --repository=https://dl-cdn.alpinelinux.org/alpine/edge/community \ + flux kubectl kustomize go-task sops + +apk add --no-cache \ + --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing \ + cloudflared cilium-cli helmfile kubeconform kubectl-krew lsd stern + +for app in \ + "budimanjojo/talhelper!!?as=talhelper&type=script" \ + "kubecolor/kubecolor!!?as=kubecolor&type=script" \ + "siderolabs/talos!!?as=talosctl&type=script" +do + echo "=== Installing ${app} ===" + curl -fsSL "https://i.jpillora.com/${app}" | bash +done + +# Create the fish configuration directory +mkdir -p /home/vscode/.config/fish/{completions,conf.d} + +# Setup autocompletions for fish +gh completion --shell fish > /home/vscode/.config/fish/completions/gh.fish +kubectl completion fish > /home/vscode/.config/fish/completions/kubectl.fish +talhelper completion fish > /home/vscode/.config/fish/completions/talhelper.fish +talosctl completion fish > /home/vscode/.config/fish/completions/talosctl.fish + +# Add hooks into fish +tee /home/vscode/.config/fish/conf.d/hooks.fish > /dev/null < /dev/null < /dev/null < /dev/null <\\S+) depName=(?\\S+)( repository=(?\\S+))?\\n.+: (&\\S+\\s)?(?\\S+)" + ], + "datasourceTemplate": "{{#if datasource}}{{{datasource}}}{{else}}github-releases{{/if}}" + } + ] +} diff --git a/.github/tests/config-talos.yaml b/.github/tests/config-talos.yaml new file mode 100644 index 00000000..eb3372de --- /dev/null +++ b/.github/tests/config-talos.yaml @@ -0,0 +1,44 @@ +--- +skip_tests: true + +boostrap_talos: + schematic_id: "376567988ad370138ad8b2698212367b8edcb69b5fd68c80be1f2ec7d603b4ba" +bootstrap_node_network: 10.10.10.0/24 +bootstrap_node_default_gateway: 10.10.10.1 +bootstrap_node_inventory: + - name: k8s-controller-0 + address: 10.10.10.100 + controller: true + disk: fake + mac_addr: fake + - name: k8s-worker-0 + address: 10.10.10.101 + controller: false + disk: fake + mac_addr: fake +bootstrap_dns_servers: ["1.1.1.1", "1.0.0.1"] +bootstrap_ntp_servers: ["time.cloudflare.com"] +bootstrap_pod_network: 10.69.0.0/16 +bootstrap_service_network: 10.96.0.0/16 +bootstrap_controller_vip: 10.10.10.254 +bootstrap_tls_sans: ["fake"] +bootstrap_age_pubkey: $BOOTSTRAP_AGE_PUBLIC_KEY +bootstrap_bgp: + enabled: false +bootstrap_github_address: https://github.com/onedr0p/cluster-template +bootstrap_github_branch: main +bootstrap_github_webhook_token: fake +bootstrap_cloudflare: + enabled: true + domain: fake + token: take + acme: + email: fake@example.com + production: false + tunnel: + account_id: fake + id: fake + secret: fake + ingress_vip: 10.10.10.252 + ingress_vip: 10.10.10.251 + gateway_vip: 10.10.10.253 diff --git a/.github/workflows/devcontainer.yaml b/.github/workflows/devcontainer.yaml new file mode 100644 index 00000000..f4b04153 --- /dev/null +++ b/.github/workflows/devcontainer.yaml @@ -0,0 +1,54 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "devcontainer" + +on: + workflow_dispatch: + push: + branches: ["main"] + paths: [".devcontainer/ci/**"] + pull_request: + branches: ["main"] + paths: [".devcontainer/ci/**"] + schedule: + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} + cancel-in-progress: true + +jobs: + devcontainer: + if: ${{ github.repository == 'onedr0p/cluster-template' }} + name: publish + runs-on: ubuntu-24.04 # TODO: Change to ubuntu-latest when available + permissions: + contents: read + packages: write + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - if: ${{ github.event_name != 'pull_request' }} + name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: devcontainers/ci@v0.3 + with: + imageName: ghcr.io/${{ github.repository }}/devcontainer + cacheFrom: ghcr.io/${{ github.repository }}/devcontainer + imageTag: latest + platform: linux/amd64,linux/arm64 + configFile: .devcontainer/ci/devcontainer.json + push: ${{ github.event_name == 'pull_request' && 'never' || 'always' }} diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml new file mode 100644 index 00000000..9566651a --- /dev/null +++ b/.github/workflows/e2e.yaml @@ -0,0 +1,127 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "e2e" + +on: + workflow_dispatch: + pull_request: + branches: ["main"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} + cancel-in-progress: true + +jobs: + archlinux: + name: workstation (archlinux) + runs-on: ubuntu-latest + container: + image: greyltc/archlinux-aur:yay + options: --user root + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Test Arch dependencies + shell: bash + run: >- + sudo -E -u ab -D~ bash -c ' + cd $GITHUB_WORKSPACE; + yay -Syu --needed --noconfirm --noprogressbar go-task; + go-task workstation:arch + ' + + generic-linux: + name: workstation (generic-linux) + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Homebrew + id: setup-homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Setup Workflow Tools + shell: bash + run: brew install go-task + + - name: Run Workstation Generic linux tasks + shell: bash + run: task workstation:generic-linux + + configure: + if: ${{ github.repository == 'onedr0p/cluster-template' }} + name: configure + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + config-files: + - talos + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Homebrew + id: setup-homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Setup Python + uses: actions/setup-python@v5 + id: setup-python + with: + python-version: "3.11" + + - name: Cache homebrew packages + if: ${{ github.event_name == 'pull_request' }} + uses: actions/cache@v4 + id: cache-homebrew-packages + with: + key: homebrew-${{ runner.os }}-${{ steps.setup-homebrew.outputs.gems-hash }}-${{ hashFiles('.taskfiles/workstation/Brewfile') }} + path: /home/linuxbrew/.linuxbrew + + - name: Cache venv + if: ${{ github.event_name == 'pull_request' }} + uses: actions/cache@v4 + with: + key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('requirements.txt', 'requirements.yaml') }} + path: .venv + + - name: Setup Workflow Tools + if: ${{ github.event_name == 'pull_request' && steps.cache-homebrew-packages.outputs.cache-hit != 'true' }} + shell: bash + run: brew install go-task + + - name: Run Workstation Brew tasks + if: ${{ github.event_name == 'pull_request' && steps.cache-homebrew-packages.outputs.cache-hit != 'true' }} + shell: bash + run: task workstation:brew + + - name: Run Workstation venv tasks + shell: bash + run: task workstation:venv + + - name: Run Workstation direnv tasks + shell: bash + run: task workstation:direnv + + - name: Run Sops Age key task + shell: bash + run: task bootstrap:age-keygen + + - name: Run init tasks + shell: bash + run: | + task init + cp ./.github/tests/config-${{ matrix.config-files }}.yaml ./config.yaml + export BOOTSTRAP_AGE_PUBLIC_KEY=$(sed -n 's/# public key: //gp' age.key) + envsubst < ./config.yaml | sponge ./config.yaml + + - name: Run configure task + shell: bash + run: task configure --yes + + - name: Run clean + shell: bash + run: task bootstrap:clean diff --git a/.github/workflows/flux-diff.yaml b/.github/workflows/flux-diff.yaml new file mode 100644 index 00000000..162008a7 --- /dev/null +++ b/.github/workflows/flux-diff.yaml @@ -0,0 +1,68 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Flux Diff" + +on: + pull_request: + branches: ["main"] + paths: ["kubernetes/**"] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.number || github.ref }} + cancel-in-progress: true + +jobs: + flux-diff: + name: Flux Diff + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + strategy: + matrix: + paths: ["kubernetes"] + resources: ["helmrelease", "kustomization"] + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + path: pull + + - name: Checkout Default Branch + uses: actions/checkout@v4 + with: + ref: "${{ github.event.repository.default_branch }}" + path: default + + - name: Diff Resources + uses: docker://ghcr.io/allenporter/flux-local:v6.0.1 + with: + args: >- + diff ${{ matrix.resources }} + --unified 6 + --path /github/workspace/pull/${{ matrix.paths }}/flux + --path-orig /github/workspace/default/${{ matrix.paths }}/flux + --strip-attrs "helm.sh/chart,checksum/config,app.kubernetes.io/version,chart" + --limit-bytes 10000 + --all-namespaces + --sources "home-kubernetes" + --output-file diff.patch + + - name: Generate Diff + id: diff + run: | + cat diff.patch + echo "diff<> $GITHUB_OUTPUT + cat diff.patch >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - if: ${{ steps.diff.outputs.diff != '' }} + name: Add comment + uses: mshick/add-pr-comment@v2 + with: + message-id: "${{ github.event.pull_request.number }}/${{ matrix.paths }}/${{ matrix.resources }}" + message-failure: Diff was not successful + message: | + ```diff + ${{ steps.diff.outputs.diff }} + ``` diff --git a/.github/workflows/kubeconform.yaml b/.github/workflows/kubeconform.yaml new file mode 100644 index 00000000..58a63cc1 --- /dev/null +++ b/.github/workflows/kubeconform.yaml @@ -0,0 +1,29 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Kubeconform" + +on: + pull_request: + branches: ["main"] + paths: ["kubernetes/**"] + +env: + KUBERNETES_DIR: ./kubernetes + +jobs: + kubeconform: + name: Kubeconform + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Homebrew + uses: Homebrew/actions/setup-homebrew@master + + - name: Setup Workflow Tools + run: brew install fluxcd/tap/flux kubeconform kustomize + + - name: Run kubeconform + shell: bash + run: bash ./scripts/kubeconform.sh ${{ env.KUBERNETES_DIR }} diff --git a/.github/workflows/label-sync.yaml b/.github/workflows/label-sync.yaml new file mode 100644 index 00000000..90804e0a --- /dev/null +++ b/.github/workflows/label-sync.yaml @@ -0,0 +1,23 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Label Sync" + +on: + workflow_dispatch: + push: + branches: ["main"] + paths: [".github/labels.yaml"] + +jobs: + label-sync: + name: Label Sync + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Sync Labels + uses: EndBug/label-sync@v2 + with: + config-file: .github/labels.yaml + delete-other-labels: true diff --git a/.github/workflows/labeler.yaml b/.github/workflows/labeler.yaml new file mode 100644 index 00000000..d658c1d9 --- /dev/null +++ b/.github/workflows/labeler.yaml @@ -0,0 +1,21 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Labeler" + +on: + workflow_dispatch: + pull_request_target: + branches: ["main"] + +jobs: + labeler: + name: Labeler + runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + steps: + - name: Labeler + uses: actions/labeler@v5 + with: + configuration-path: .github/labeler.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..ff8eabe4 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,44 @@ +--- +# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json +name: "Release" + +on: + workflow_dispatch: + schedule: + - cron: "0 0 1 * *" + +jobs: + release: + if: ${{ github.repository == 'onedr0p/cluster-template' }} + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Create Release + shell: bash + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + # Retrieve previous release tag + previous_tag="$(gh release list --limit 1 | awk '{ print $1 }')" + previous_major="${previous_tag%%\.*}" + previous_minor="${previous_tag#*.}" + previous_minor="${previous_minor%.*}" + previous_patch="${previous_tag##*.}" + # Determine next release tag + next_major_minor="$(date +'%Y').$(date +'%-m')" + if [[ "${previous_major}.${previous_minor}" == "${next_major_minor}" ]]; then + echo "Month release already exists for year, incrementing patch number by 1" + next_patch="$((previous_patch + 1))" + else + echo "Month release does not exist for year, setting patch number to 0" + next_patch="0" + fi + # Create release + release_tag="${next_major_minor}.${next_patch}" + gh release create "${release_tag}" \ + --repo="${GITHUB_REPOSITORY}" \ + --title="${release_tag}" \ + --generate-notes diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4f71d416 --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +# Trash +.DS_Store +Thumbs.db +# k8s +kubeconfig +talosconfig +.decrypted~*.yaml +.config.env +*.agekey +*.pub +*.key +# Private +.private +.bin +# Ansible +.venv* +# Taskfile +.task +# Brew +Brewfile.lock.json +# intellij +.idea +# wiki +wiki +# Bootstrap +/config.yaml diff --git a/.taskfiles/bootstrap/Taskfile.yaml b/.taskfiles/bootstrap/Taskfile.yaml new file mode 100644 index 00000000..f176a43c --- /dev/null +++ b/.taskfiles/bootstrap/Taskfile.yaml @@ -0,0 +1,131 @@ +--- +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: '3' + +vars: + GITHUB_DEPLOY_KEY_FILE: '{{.KUBERNETES_DIR}}/bootstrap/flux/github-deploy-key.sops.yaml' + TALHELPER_CLUSTER_DIR: '{{.KUBERNETES_DIR}}/bootstrap/talos/clusterconfig' + TALHELPER_SECRET_FILE: '{{.KUBERNETES_DIR}}/bootstrap/talos/talsecret.sops.yaml' + TALHELPER_CONFIG_FILE: '{{.KUBERNETES_DIR}}/bootstrap/talos/talconfig.yaml' + +env: + TALOSCONFIG: '{{.TALHELPER_CLUSTER_DIR}}/talosconfig' + +tasks: + + talos: + desc: Bootstrap the Talos cluster + dir: '{{.KUBERNETES_DIR}}/bootstrap/talos' + cmds: + - '{{if eq .TALHELPER_SECRET_EXISTS "false"}}talhelper gensecret > {{.TALHELPER_SECRET_FILE}}{{end}}' + - '{{if eq .TALHELPER_SECRET_EXISTS "false"}}sops --encrypt --in-place {{.TALHELPER_SECRET_FILE}}{{end}}' + - talhelper genconfig --config-file {{.TALHELPER_CONFIG_FILE}} --secret-file {{.TALHELPER_SECRET_FILE}} --out-dir {{.TALHELPER_CLUSTER_DIR}} + - talhelper gencommand apply --config-file {{.TALHELPER_CONFIG_FILE}} --out-dir {{.TALHELPER_CLUSTER_DIR}} --extra-flags="--insecure" | bash + - until talhelper gencommand bootstrap --config-file {{.TALHELPER_CONFIG_FILE}} --out-dir {{.TALHELPER_CLUSTER_DIR}} | bash; do sleep 10; done + - until talhelper gencommand kubeconfig --config-file {{.TALHELPER_CONFIG_FILE}} --out-dir {{.TALHELPER_CLUSTER_DIR}} --extra-flags="{{.ROOT_DIR}} --force" | bash; do sleep 10; done + - task: apps + - talosctl health --server=false + vars: + TALHELPER_SECRET_EXISTS: + sh: test -f {{.TALHELPER_SECRET_FILE}} && echo true || echo false + preconditions: + - msg: Missing talhelper config file + sh: test -f {{.TALHELPER_CONFIG_FILE}} + - msg: Missing Sops config file + sh: test -f {{.SOPS_CONFIG_FILE}} + - msg: Missing Sops Age key file + sh: test -f {{.SOPS_AGE_KEY_FILE}} + + flux: + desc: Bootstrap Flux into the Talos cluster + cmds: + - '{{if eq .GITHUB_DEPLOY_KEY_EXISTS "true"}}kubectl create namespace flux-system --dry-run=client -o yaml | kubectl apply --filename -{{end}}' + - '{{if eq .GITHUB_DEPLOY_KEY_EXISTS "true"}}sops exec-file {{.GITHUB_DEPLOY_KEY_FILE}} "kubectl apply --server-side --filename {}"{{end}}' + - kubectl apply --server-side --kustomize {{.KUBERNETES_DIR}}/bootstrap/flux + - '{{if eq .SOPS_SECRET_EXISTS "false"}}cat {{.SOPS_AGE_KEY_FILE}} | kubectl --namespace flux-system create secret generic sops-age --from-file=age.agekey=/dev/stdin{{end}}' + - sops exec-file {{.KUBERNETES_DIR}}/flux/vars/cluster-secrets.sops.yaml "kubectl apply --server-side --filename {}" + - kubectl apply --server-side --filename {{.KUBERNETES_DIR}}/flux/vars/cluster-settings.yaml + - kubectl apply --server-side --kustomize {{.KUBERNETES_DIR}}/flux/config + vars: + GITHUB_DEPLOY_KEY_EXISTS: + sh: test -f {{.GITHUB_DEPLOY_KEY_FILE}} && echo true || echo false + SOPS_SECRET_EXISTS: + sh: kubectl --namespace flux-system get secret sops-age &>/dev/null && echo true || echo false + preconditions: + - msg: Missing kubeconfig + sh: test -f {{.KUBECONFIG}} + - msg: Missing Sops Age key file + sh: test -f {{.SOPS_AGE_KEY_FILE}} + + apps: + internal: true + dir: '{{.KUBERNETES_DIR}}/bootstrap/talos' + cmds: + - until kubectl wait --for=condition=Ready=False nodes --all --timeout=600s; do sleep 10; done + - helmfile --file {{.KUBERNETES_DIR}}/bootstrap/helmfile.yaml apply --skip-diff-on-install --suppress-diff + - until kubectl wait --for=condition=Ready nodes --all --timeout=600s; do sleep 10; done + preconditions: + - msg: Missing kubeconfig + sh: test -f {{.KUBECONFIG}} + - msg: Missing helmfile + sh: test -f {{.KUBERNETES_DIR}}/bootstrap/helmfile.yaml + + age-keygen: + desc: Bootstrap the Sops Age key + cmd: age-keygen --output {{.SOPS_AGE_KEY_FILE}} + status: + - test -f {{.SOPS_AGE_KEY_FILE}} + + template: + internal: true + cmd: '{{.VIRTUAL_ENV}}/bin/makejinja' + preconditions: + - msg: Missing virtual environment + sh: test -d {{.VIRTUAL_ENV}} + - msg: Missing Makejinja config file + sh: test -f {{.MAKEJINJA_CONFIG_FILE}} + - msg: Missing Makejinja plugin file + sh: test -f {{.BOOTSTRAP_DIR}}/scripts/plugin.py + - msg: Missing bootstrap config file + sh: test -f {{.BOOTSTRAP_CONFIG_FILE}} + + secrets: + internal: true + cmds: + - for: { var: SECRET_FILES } + cmd: | + if sops filestatus "{{.ITEM}}" | jq --exit-status ".encrypted == false" &>/dev/null; then + sops --encrypt --in-place "{{.ITEM}}" + fi + vars: + SECRET_FILES: + sh: find "{{.KUBERNETES_DIR}}" -type f -name "*.sops.*" + preconditions: + - msg: Missing Sops config file + sh: test -f {{.SOPS_CONFIG_FILE}} + - msg: Missing Sops Age key file + sh: test -f {{.SOPS_AGE_KEY_FILE}} + + clean: + desc: Clean files and directories no longer needed after cluster bootstrap + cmds: + # Create backup directory + - mkdir -p {{.ROOT_DIR}}/.private + # Clean up CI + - rm -rf {{.ROOT_DIR}}/.github/tests + - rm -rf {{.ROOT_DIR}}/.github/workflows/e2e.yaml + # Clean up devcontainer + - rm -rf {{.ROOT_DIR}}/.devcontainer/ci + - rm -rf {{.ROOT_DIR}}/.github/workflows/devcontainer.yaml + # Move bootstrap directory to gitignored directory + - mv {{.BOOTSTRAP_DIR}} {{.ROOT_DIR}}/.private/bootstrap-{{.TS}} + - mv {{.MAKEJINJA_CONFIG_FILE}} {{.ROOT_DIR}}/.private/makejinja-{{.TS}}.toml + # Update renovate.json5 + - sed -i {{if eq OS "darwin"}}''{{end}} 's/(..\.j2)\?//g' {{.ROOT_DIR}}/.github/renovate.json5 + vars: + TS: '{{now | unixEpoch}}' + preconditions: + - msg: Missing bootstrap directory + sh: test -d {{.BOOTSTRAP_DIR}} + - msg: Missing Renovate config file + sh: test -f {{.ROOT_DIR}}/.github/renovate.json5 diff --git a/.taskfiles/kubernetes/Taskfile.yaml b/.taskfiles/kubernetes/Taskfile.yaml new file mode 100644 index 00000000..09cf5475 --- /dev/null +++ b/.taskfiles/kubernetes/Taskfile.yaml @@ -0,0 +1,63 @@ +--- +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: '3' + +vars: + KUBECONFORM_SCRIPT: "{{.SCRIPTS_DIR}}/kubeconform.sh" + +tasks: + + apply-ks: + desc: Apply a Flux Kustomization resource for a cluster + summary: |- + PATH: Path to the Flux Kustomization resource from the apps base dir (required, e.g. network/echo-server) + NS: Namespace the Flux Kustomization exists in (default: flux-system) + cmd: > + flux build --namespace {{.NS}} ks {{base .PATH}} + --kustomization-file {{.KUBERNETES_DIR}}/apps/{{.PATH}}/ks.yaml + --path {{.KUBERNETES_DIR}}/apps/{{.PATH}} + {{- if contains "not found" .KS }}--dry-run \{{ end }} + | yq 'with(select(.apiVersion == "kustomize.toolkit.fluxcd.io/v1" and .kind == "Kustomization"); .metadata.namespace = "{{.NS}}")' - + | kubectl apply --server-side --field-manager=kustomize-controller -f - + requires: + vars: [PATH] + vars: + NS: '{{.NS | default "flux-system"}}' + KS: + sh: flux --namespace {{.NS}} get kustomizations {{base .PATH}} 2>&1 + preconditions: + - test -f {{.KUBERNETES_DIR}}/apps/{{.PATH}}/ks.yaml + + reconcile: + desc: Force update Flux to pull in changes from your Git repository + cmd: flux reconcile --namespace flux-system kustomization cluster --with-source + preconditions: + - msg: Missing kubeconfig + sh: test -f {{.KUBECONFIG}} + + kubeconform: + desc: Validate Kubernetes manifests with kubeconform + cmd: bash {{.KUBECONFORM_SCRIPT}} {{.KUBERNETES_DIR}} + preconditions: + - msg: Missing kubeconform script + sh: test -f {{.KUBECONFORM_SCRIPT}} + + resources: + desc: Gather common resources in your cluster, useful when asking for support + cmds: + - for: { var: RESOURCE } + cmd: kubectl get {{.ITEM}} {{.CLI_ARGS | default "-A"}} + vars: + RESOURCE: >- + nodes + gitrepositories + kustomizations + helmrepositories + helmreleases + certificates + certificaterequests + ingresses + pods + preconditions: + - msg: Missing kubeconfig + sh: test -f {{.KUBECONFIG}} diff --git a/.taskfiles/talos/Taskfile.yaml b/.taskfiles/talos/Taskfile.yaml new file mode 100644 index 00000000..09b9929a --- /dev/null +++ b/.taskfiles/talos/Taskfile.yaml @@ -0,0 +1,122 @@ +--- +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: '3' + +vars: + TALHELPER_CLUSTER_DIR: '{{.KUBERNETES_DIR}}/bootstrap/talos/clusterconfig' + TALHELPER_CONFIG_FILE: '{{.KUBERNETES_DIR}}/bootstrap/talos/talconfig.yaml' + TALHELPER_SECRET_FILE: '{{.KUBERNETES_DIR}}/bootstrap/talos/talsecret.sops.yaml' + TALOSCONFIG: '{{.TALHELPER_CLUSTER_DIR}}/talosconfig' + +env: + TALOSCONFIG: '{{.TALOSCONFIG}}' + +tasks: + + generate-config: + desc: Generate Talos configuration + cmd: talhelper genconfig --config-file {{.TALHELPER_CONFIG_FILE}} --secret-file {{.TALHELPER_SECRET_FILE}} --out-dir {{.TALHELPER_CLUSTER_DIR}} + preconditions: + - msg: Missing talhelper config file + sh: test -f {{.TALHELPER_CONFIG_FILE}} + - msg: Missing Sops config file + sh: test -f {{.SOPS_CONFIG_FILE}} + - msg: Missing Sops Age key file + sh: test -f {{.SOPS_AGE_KEY_FILE}} + + apply-config: + desc: Apply Talos configuration to a node + cmds: + - talosctl --nodes {{.HOSTNAME}} apply-config --mode=staged --file {{.TALHELPER_CLUSTER_DIR}}/{{.CLUSTER_NAME}}-{{.HOSTNAME}}.yaml + - talosctl --nodes {{.HOSTNAME}} reboot + - talosctl --nodes {{.HOSTNAME}} health --wait-timeout=10m --server=false + vars: + CLUSTER_NAME: + sh: yq '.clusterName' {{.TALHELPER_CONFIG_FILE}} + requires: + vars: [HOSTNAME] + preconditions: + - msg: Missing talosconfig + sh: test -f {{.TALOSCONFIG}} + - msg: Unable to retrieve Talos config + sh: talosctl config info &>/dev/null + - msg: Node not found + sh: talosctl --nodes {{.HOSTNAME}} get machineconfig &>/dev/null + - msg: Talos config for node not found + sh: test -f {{.TALHELPER_CLUSTER_DIR}}/{{.CLUSTER_NAME}}-{{.HOSTNAME}}.yaml + + upgrade-node: + desc: Upgrade Talos on a single node + cmds: + - task: '{{if ne .ROLLOUT true}}down{{else}}noop{{end}}' + - talosctl --nodes {{.HOSTNAME}} upgrade --image="factory.talos.dev/installer/{{.TALOS_SCHEMATIC_ID}}:{{.TALOS_VERSION}}" --timeout=10m + - talosctl --nodes {{.HOSTNAME}} health --wait-timeout=10m --server=false + - task: '{{if ne .ROLLOUT true}}up{{else}}noop{{end}}' + vars: + TALOS_SCHEMATIC_ID: + sh: kubectl get node {{.HOSTNAME}} --output=jsonpath='{.metadata.annotations.extensions\.talos\.dev/schematic}' + TALOS_VERSION: + sh: yq '.talosVersion' {{.TALHELPER_CONFIG_FILE}} + requires: + vars: [HOSTNAME] + preconditions: + - msg: Missing talosconfig + sh: test -f {{.TALOSCONFIG}} + - msg: Unable to retrieve Talos config + sh: talosctl config info &>/dev/null + - msg: Node not found + sh: talosctl --nodes {{.HOSTNAME}} get machineconfig &>/dev/null + - msg: Upstream Talos version not found + sh: curl -fsSL -o /dev/null --fail https://github.com/siderolabs/talos/releases/tag/{{.TALOS_VERSION}} + + upgrade-cluster: + desc: Upgrade Talos on the whole cluster + cmds: + - task: down + - for: { var: HOSTNAMES } + task: upgrade-node + vars: + HOSTNAME: '{{.ITEM}}' + ROLLOUT: true + - task: up + vars: + HOSTNAMES: + sh: kubectl get nodes --output=jsonpath='{.items[*].metadata.name}' + + upgrade-k8s: + desc: Upgrade Kubernetes + cmd: talosctl --nodes {{.KUBERNETES_CONTROLLER}} upgrade-k8s --to {{.KUBERNETES_VERSION}} + vars: + KUBERNETES_CONTROLLER: + sh: talosctl config info --output json | jq --raw-output '.endpoints[]' | shuf -n 1 + KUBERNETES_VERSION: + sh: yq '.kubernetesVersion' {{.TALHELPER_CONFIG_FILE}} + preconditions: + - msg: Missing talosconfig + sh: test -f {{.TALOSCONFIG}} + - msg: Unable to retrieve Talos config + sh: talosctl config info &>/dev/null + - msg: Node not found + sh: talosctl --nodes {{.KUBERNETES_CONTROLLER}} get machineconfig &>/dev/null + - msg: Upstream Kubernetes version not found + sh: curl -fsSL -o /dev/null --fail https://github.com/siderolabs/kubelet/releases/tag/{{.KUBERNETES_VERSION}} + + reset: + desc: Resets nodes back to maintenance mode + dir: "{{.KUBERNETES_DIR}}/bootstrap/talos" + prompt: This will destroy your cluster and reset the nodes back to maintenance mode... continue? + cmd: talhelper gencommand reset --config-file {{.TALHELPER_CONFIG_FILE}} --out-dir {{.TALHELPER_CLUSTER_DIR}} --extra-flags="--reboot {{- if eq .CLI_FORCE false }} --system-labels-to-wipe STATE --system-labels-to-wipe EPHEMERAL{{ end }} --graceful=false --wait=false" | bash + + down: + internal: true + cmd: flux --namespace flux-system suspend kustomization --all + + up: + internal: true + cmd: flux --namespace flux-system resume kustomization --all + + # Ref: https://github.com/go-task/task/issues/608 + noop: + internal: true + silent: true + cmd: noop() { :; } diff --git a/.taskfiles/workstation/Archfile b/.taskfiles/workstation/Archfile new file mode 100644 index 00000000..2fc5df92 --- /dev/null +++ b/.taskfiles/workstation/Archfile @@ -0,0 +1,18 @@ +age +cloudflared-bin +direnv +flux-bin +go-task +go-yq +helm +helmfile +jq +kubeconform +kubectl-bin +kustomize +minijinja-cli-bin +moreutils +sops +stern-bin +talhelper-bin +talosctl diff --git a/.taskfiles/workstation/Brewfile b/.taskfiles/workstation/Brewfile new file mode 100644 index 00000000..65f69bf5 --- /dev/null +++ b/.taskfiles/workstation/Brewfile @@ -0,0 +1,21 @@ +tap "fluxcd/tap" +tap "go-task/tap" +tap "siderolabs/tap" +brew "age" +brew "cloudflared" +brew "direnv" +brew "fluxcd/tap/flux" +brew "go-task/tap/go-task" +brew "helm" +brew "helmfile" +brew "jq" +brew "kubeconform" +brew "kubernetes-cli" +brew "kustomize" +brew "minijinja-cli" +brew "moreutils" +brew "siderolabs/tap/talosctl" +brew "sops" +brew "stern" +brew "talhelper" +brew "yq" diff --git a/.taskfiles/workstation/Taskfile.yaml b/.taskfiles/workstation/Taskfile.yaml new file mode 100644 index 00000000..b574e61e --- /dev/null +++ b/.taskfiles/workstation/Taskfile.yaml @@ -0,0 +1,91 @@ +--- +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: '3' + +tasks: + + arch: + desc: Set up Arch Linux tools + cmd: '{{.PKGMGR}} -Syu --needed --noconfirm --noprogressbar $(cat {{.ROOT_DIR}}/.taskfiles/workstation/Archfile | xargs)' + vars: + PKGMGR: + sh: command -v paru || command -v yay + preconditions: + - msg: Missing paru or yay + sh: command -v paru &>/dev/null || command -v yay &>/dev/null + - msg: Missing Archfile + sh: test -f {{.ROOT_DIR}}/.taskfiles/workstation/Archfile + + brew: + desc: Set up Homebrew tools + cmds: + - brew bundle --file {{.ROOT_DIR}}/.taskfiles/workstation/Brewfile + sources: + - '{{.ROOT_DIR}}/.taskfiles/workstation/Brewfile' + generates: + - '{{.ROOT_DIR}}/.taskfiles/workstation/Brewfile.lock.json' + preconditions: + - msg: Missing brew + sh: command -v brew &>/dev/null + - msg: Missing Brewfile + sh: test -f {{.ROOT_DIR}}/.taskfiles/workstation/Brewfile + + direnv: + desc: Run direnv hooks + cmd: direnv allow . + status: + - '[[ $(direnv status --json | jq ".state.foundRC.allowed") == 0 ]]' + - '[[ $(direnv status --json | jq ".state.loadedRC.allowed") == 0 ]]' + preconditions: + - msg: Missing direnv + sh: command -v direnv &>/dev/null + + generic-linux: + desc: Setup CLI tools into the projects .bin directory + dir: '{{.ROOT_DIR}}/.bin' + platforms: ['linux/amd64', 'linux/arm64'] + cmds: + - for: + - budimanjojo/talhelper?as=talhelper + - cloudflare/cloudflared?as=cloudflared + - FiloSottile/age?as=age + - fluxcd/flux2?as=flux + - helmfile/helmfile?as=helmfile + - jqlang/jq?as=jq + - kubernetes-sigs/kustomize?as=kustomize + - mikefarah/yq?as=yq + - siderolabs/talos?as=talosctl + - yannh/kubeconform?as=kubeconform + cmd: curl -fsSL "https://i.jpillora.com/{{.ITEM}}&type=script" | bash + - cmd: | + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"; + curl -sSfL -o sops https://github.com/getsops/sops/releases/latest/download/sops-v3.9.1.linux.amd64 + platforms: ['linux/amd64'] + - cmd: | + curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/arm64/kubectl"; + curl -sSfL -o sops https://github.com/getsops/sops/releases/latest/download/sops-v3.9.1.linux.arm64 + platforms: ['linux/arm64'] + - cmd: chmod +x kubectl sops + - cmd: curl -sSfL https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash + - cmd: curl -sSfL https://github.com/mitsuhiko/minijinja/releases/latest/download/minijinja-cli-installer.sh | bash + env: + MINIJINJA_CLI_INSTALL_DIR: '.' + MINIJINJA_CLI_UNMANAGED_INSTALL: 'true' + HELM_INSTALL_DIR: '.' + USE_SUDO: 'false' + + venv: + desc: Set up virtual environment + cmds: + - python3 -m venv {{.VIRTUAL_ENV}} + - '{{.VIRTUAL_ENV}}/bin/python3 -m pip install --upgrade pip setuptools wheel' + - '{{.VIRTUAL_ENV}}/bin/python3 -m pip install --upgrade --requirement "{{.ROOT_DIR}}/requirements.txt"' + sources: + - '{{.ROOT_DIR}}/requirements.txt' + generates: + - '{{.VIRTUAL_ENV}}/pyvenv.cfg' + preconditions: + - msg: Missing python3 + sh: command -v python3 &>/dev/null + - msg: Missing Pip requirements file + sh: test -f {{.ROOT_DIR}}/requirements.txt diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 00000000..f4312e64 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,14 @@ +{ + "recommendations": [ + "albert.TabOut", + "britesnow.vscode-toggle-quotes", + "fcrespo82.markdown-table-formatter", + "mikestead.dotenv", + "mitchdenny.ecdc", + "signageos.signageos-vscode-sops", + "will-stone.in-any-case", + "EditorConfig.editorconfig", + "PKief.material-icon-theme", + "Gruntfuggly.todo-tree" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..579ebc39 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "files.associations": { + "*.json5": "jsonc", + "./kubernetes/**/*.sops.toml": "plaintext" + }, + "sops.defaults.ageKeyFile": "age.key", + "yaml.schemas": { + "Kubernetes": "./kubernetes/*.yaml" + }, + "vs-kubernetes": { + "vs-kubernetes.kubeconfig": "./kubeconfig", + "vs-kubernetes.knownKubeconfigs": [ + "./kubeconfig" + ] + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..ab784ede --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 onedr0p + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..0fb2bfa5 --- /dev/null +++ b/README.md @@ -0,0 +1,422 @@ +# ⛵ Cluster Template + +Welcome to my opinionated and extensible template for deploying a single Kubernetes cluster. The goal of this project is to make it easier for people interested in using Kubernetes to deploy a cluster at home on bare-metal or VMs. This template closely mirrors my personal [home-ops](https://github.com/onedr0p/home-ops) repository. + +At a high level this project makes use of [makejinja](https://github.com/mirkolenz/makejinja) to read in a [configuration file](./config.sample.yaml) which renders out templates that will allow you to install and manage your Kubernetes cluster with. + +## ✨ Features + +The features included will depend on the type of configuration you want to use. There are currently **2 different types** of **configurations** available with this template. + +1. **"Flux cluster"** - a Kubernetes cluster deployed on-top of [Talos Linux](https://github.com/siderolabs/talos) with an opinionated implementation of [Flux](https://github.com/fluxcd/flux2) using [GitHub](https://github.com/) as the Git provider and [sops](https://github.com/getsops/sops) to manage secrets. + + - **Required:** Some knowledge of [Containers](https://opencontainers.org/), [YAML](https://yaml.org/), and [Git](https://git-scm.com/). + - **Components:** [flux](https://github.com/fluxcd/flux2), [cilium](https://github.com/cilium/cilium), [cert-manager](https://github.com/cert-manager/cert-manager), [spegel](https://github.com/spegel-org/spegel), [reloader](https://github.com/stakater/Reloader), and [openebs](https://github.com/openebs/openebs). + +2. **"Flux cluster with Cloudflare"** - An addition to "**Flux cluster**" that provides DNS and SSL with [Cloudflare](https://www.cloudflare.com/). [Cloudflare Tunnel](https://www.cloudflare.com/products/tunnel/) is also included to provide external access to certain applications deployed in your cluster. + + - **Required:** A Cloudflare account with a domain managed in your Cloudflare account. + - **Components:** [ingress-nginx](https://github.com/kubernetes/ingress-nginx/), [external-dns](https://github.com/kubernetes-sigs/external-dns) and [cloudflared](https://github.com/cloudflare/cloudflared). + +**Other features include:** + +- A [Renovate](https://www.mend.io/renovate)-ready repository with pull request diffs provided by [flux-local](https://github.com/allenporter/flux-local) +- Integrated [GitHub Actions](https://github.com/features/actions) with helpful workflows. + +## 💻 Machine Preparation + +### System requirements + +> [!NOTE] +> 1. The included behaviour of Talos is that all nodes are able to run workloads, **including** the controller nodes. **Worker nodes** are therefore **optional**. +> 2. Do you have 3 or more nodes? It is highly recommended to make 3 of them controller nodes for a highly available control plane. +> 3. Running the cluster on Proxmox VE? My thoughts and recommendations about that are documented [here](https://onedr0p.github.io/home-ops/notes/proxmox-considerations.html). + +| Role | Cores | Memory | System Disk | +|---------|----------|---------------|---------------------------| +| Control | 4 _(6*)_ | 8GB _(24GB*)_ | 120GB _(500GB*)_ SSD/NVMe | +| Worker | 4 _(6*)_ | 8GB _(24GB*)_ | 120GB _(500GB*)_ SSD/NVMe | +| _\* recommended_ | + +1. Head over to and follow the instructions which will eventually lead you to download a Talos Linux iso file (or for SBCs the `.raw.xz`). Make sure to note the schematic ID you will need this later on. + +2. Flash the iso or raw file to a USB drive and boot to Talos on your nodes with it. + +3. Continue on to 🚀 [**Getting Started**](#-getting-started) + +## 🚀 Getting Started + +Once you have installed Talos on your nodes, there are six stages to getting a Flux-managed cluster up and running. + +> [!NOTE] +> For all stages below the commands **MUST** be ran on your personal workstation within your repository directory + +### 🎉 Stage 1: Create a Git repository + +1. Create a new **public** repository by clicking the big green "Use this template" button at the top of this page. + +2. Clone **your new repo** to you local workstation and `cd` into it. + +3. Continue on to 🌱 [**Stage 2**](#-stage-2-setup-your-local-workstation-environment) + +### 🌱 Stage 2: Setup your local workstation + +You have two different options for setting up your local workstation. + +- First option is using a `devcontainer` which requires you to have Docker and VSCode installed. This method is the fastest to get going because all the required CLI tools are provided for you in my [devcontainer](https://github.com/onedr0p/cluster-template/pkgs/container/cluster-template%2Fdevcontainer) image. +- The second option is setting up the CLI tools directly on your workstation. + +#### Devcontainer method + +1. Start Docker and open your repository in VSCode. There will be a pop-up asking you to use the `devcontainer`, click the button to start using it. + +2. Continue on to 🔧 [**Stage 3**](#-stage-3-bootstrap-configuration) + +#### Non-devcontainer method + +1. Install the most recent version of [task](https://taskfile.dev/) and [direnv](https://direnv.net/) + + ```sh + # Homebrew + brew install direnv go-task + # or, Arch + pacman -S --noconfirm direnv go-task \ + && ln -sf /usr/bin/go-task /usr/local/bin/task + ``` + +2. [Hook `direnv` into your preferred shell](https://direnv.net/docs/hook.html), then run: + + ```sh + task workstation:direnv + ``` + + 📍 _**Verify** that `direnv` is setup properly by opening a new terminal and `cd`ing into your repository. You should see something like:_ + + ```sh + cd /path/to/repo + direnv: loading ... .envrc + direnv: export +VIRTUAL_ENV ... ~PATH + ``` + +3. Install the additional **required** CLI tools + + 📍 _**Not using Homebrew or ArchLinux?** Try using the generic Linux task below, if that fails check out the [Brewfile](.taskfiles/workstation/Brewfile)/[Archfile](.taskfiles/workstation/Archfile) for what CLI tools needed and install them._ + + ```sh + # Homebrew + task workstation:brew + # or, Arch with yay/paru + task workstation:arch + # or, Generic Linux (YMMV, this pulls binaires in to ./bin) + task workstation:generic-linux + ``` + +4. Setup a Python virual environment by running the following task command. + + 📍 _This commands requires Python 3.11+ to be installed._ + + ```sh + task workstation:venv + ``` + +5. Continue on to 🔧 [**Stage 3**](#-stage-3-bootstrap-configuration) + +### 🔧 Stage 3: Bootstrap configuration + +> [!NOTE] +> The [config.sample.yaml](./config.sample.yaml) file contains config that is **vital** to the bootstrap process. + +1. Generate the `config.yaml` from the [config.sample.yaml](./config.sample.yaml) configuration file. + + ```sh + task init + ``` + +2. Fill out the `config.yaml` configuration file using the comments in that file as a guide. + +3. Run the following command which will generate all the files needed to continue. + + ```sh + task configure + ``` + +4. Push you changes to git + + 📍 _**Verify** all the `./kubernetes/**/*.sops.*` files are **encrypted** with SOPS_ + + ```sh + git add -A + git commit -m "Initial commit :rocket:" + git push + ``` + +### ⛵ Stage 4: Install Kubernetes + +1. Deploy your cluster and bootstrap it. This generates secrets, generates the config files for your nodes and applies them. It bootstraps the cluster afterwards, fetches the kubeconfig file and installs Cilium and kubelet-csr-approver. It finishes with some health checks. + + ```sh + task bootstrap:talos + ``` + +2. ⚠️ It might take a while for the cluster to be setup (10+ minutes is normal), during which time you will see a variety of error messages like: "couldn't get current server API group list," "error: no matching resources found", etc. This is a normal. If this step gets interrupted, e.g. by pressing Ctrl + C, you likely will need to [nuke the cluster](#-Nuke) before trying again. + +#### Cluster validation + +1. The `kubeconfig` for interacting with your cluster should have been created in the root of your repository. + +2. Verify the nodes are online + + 📍 _If this command **fails** you likely haven't configured `direnv` as [mentioned previously](#non-devcontainer-method) in the guide._ + + ```sh + kubectl get nodes -o wide + # NAME STATUS ROLES AGE VERSION + # k8s-0 Ready control-plane,etcd,master 1h v1.30.1 + # k8s-1 Ready worker 1h v1.30.1 + ``` + +3. Continue on to 🔹 [**Stage 6**](#-stage-6-install-flux-in-your-cluster) + +### 🔹 Stage 6: Install Flux in your cluster + +1. Verify Flux can be installed + + ```sh + flux check --pre + # ► checking prerequisites + # ✔ kubectl 1.30.1 >=1.18.0-0 + # ✔ Kubernetes 1.30.1 >=1.16.0-0 + # ✔ prerequisites checks passed + ``` + +2. Install Flux and sync the cluster to the Git repository + + ```sh + task bootstrap:flux + # namespace/flux-system configured + # customresourcedefinition.apiextensions.k8s.io/alerts.notification.toolkit.fluxcd.io created + # ... + ``` + +3. Verify Flux components are running in the cluster + + ```sh + kubectl -n flux-system get pods -o wide + # NAME READY STATUS RESTARTS AGE + # helm-controller-5bbd94c75-89sb4 1/1 Running 0 1h + # kustomize-controller-7b67b6b77d-nqc67 1/1 Running 0 1h + # notification-controller-7c46575844-k4bvr 1/1 Running 0 1h + # source-controller-7d6875bcb4-zqw9f 1/1 Running 0 1h + ``` + +### 🎤 Verification Steps + +_Mic check, 1, 2_ - In a few moments applications should be lighting up like Christmas in July 🎄 + +1. Output all the common resources in your cluster. + + 📍 _Feel free to use the provided [kubernetes tasks](.taskfiles/Kubernetes/Taskfile.yaml) for validation of cluster resources or continue to get familiar with the `kubectl` and `flux` CLI tools._ + + ```sh + task kubernetes:resources + ``` + +2. ⚠️ It might take `cert-manager` awhile to generate certificates, this is normal so be patient. + +3. 🏆 **Congratulations** if all goes smooth you will have a Kubernetes cluster managed by Flux and your Git repository is driving the state of your cluster. + +4. 🧠 Now it's time to pause and go get some motel motor oil ☕ and admire you made it this far! + +## 📣 Flux w/ Cloudflare post installation + +#### 🌐 Public DNS + +The `external-dns` application created in the `networking` namespace will handle creating public DNS records. By default, `echo-server` and the `flux-webhook` are the only subdomains reachable from the public internet. In order to make additional applications public you must set set the correct ingress class name and ingress annotations like in the HelmRelease for `echo-server`. + +#### 🏠 Home DNS + +`k8s_gateway` will provide DNS resolution to external Kubernetes resources (i.e. points of entry to the cluster) from any device that uses your home DNS server. For this to work, your home DNS server must be configured to forward DNS queries for `${bootstrap_cloudflare.domain}` to `${bootstrap_cloudflare.gateway_vip}` instead of the upstream DNS server(s) it normally uses. This is a form of **split DNS** (aka split-horizon DNS / conditional forwarding). + +> [!TIP] +> Below is how to configure a Pi-hole for split DNS. Other platforms should be similar. +> 1. Apply this file on the Pihole server while substituting the variables +> ```sh +> # /etc/dnsmasq.d/99-k8s-gateway-forward.conf +> server=/${bootstrap_cloudflare.domain}/${bootstrap_cloudflare.gateway_vip} +> ``` +> 2. Restart dnsmasq on the server. +> 3. Query an internal-only subdomain from your workstation (any `internal` class ingresses): `dig @${home-dns-server-ip} echo-server-internal.${bootstrap_cloudflare.domain}`. It should resolve to `${bootstrap_cloudflare.ingress_vip}`. + +If you're having trouble with DNS be sure to check out these two GitHub discussions: [Internal DNS](https://github.com/onedr0p/cluster-template/discussions/719) and [Pod DNS resolution broken](https://github.com/onedr0p/cluster-template/discussions/635). + +... Nothing working? That is expected, this is DNS after all! + +#### 📜 Certificates + +By default this template will deploy a wildcard certificate using the Let's Encrypt **staging environment**, which prevents you from getting rate-limited by the Let's Encrypt production servers if your cluster doesn't deploy properly (for example due to a misconfiguration). Once you are sure you will keep the cluster up for more than a few hours be sure to switch to the production servers as outlined in `config.yaml`. + +📍 _You will need a production certificate to reach internet-exposed applications through `cloudflared`._ + +#### 🪝 Github Webhook + +By default Flux will periodically check your git repository for changes. In order to have Flux reconcile on `git push` you must configure Github to send `push` events to Flux. + +> [!NOTE] +> This will only work after you have switched over certificates to the Let's Encrypt Production servers. + +1. Obtain the webhook path + + 📍 _Hook id and path should look like `/hook/12ebd1e363c641dc3c2e430ecf3cee2b3c7a5ac9e1234506f6f5f3ce1230e123`_ + + ```sh + kubectl -n flux-system get receiver github-receiver -o jsonpath='{.status.webhookPath}' + ``` + +2. Piece together the full URL with the webhook path appended + + ```text + https://flux-webhook.${bootstrap_cloudflare.domain}/hook/12ebd1e363c641dc3c2e430ecf3cee2b3c7a5ac9e1234506f6f5f3ce1230e123 + ``` + +3. Navigate to the settings of your repository on Github, under "Settings/Webhooks" press the "Add webhook" button. Fill in the webhook URL and your `bootstrap_github_webhook_token` secret in `config.yaml`, Content type: `application/json`, Events: Choose Just the push event, and save. + +## 💥 Reset + +There might be a situation where you want to destroy your Kubernetes cluster. The following command will reset your nodes back to maintenance mode, append `--force` to completely format your the Talos installation. Either way the nodes should reboot after the command has run. + +```sh +task talos:reset # --force +``` + +## 🛠️ Talos and Kubernetes Maintenance + +#### ⚙️ Updating Talos node configuration + +📍 _Ensure you have updated `talconfig.yaml` and any patches with your updated configuration._ + +```sh +# (Re)generate the Talos config +task talos:generate-config +# Apply the config to the node +task talos:apply-config HOSTNAME=? MODE=? +# e.g. task talos:apply-config HOSTNAME=k8s-0 MODE=reboot +``` + +#### ⬆️ Updating Talos and Kubernetes versions + +📍 _Ensure the `talosVersion` and `kubernetesVersion` in `talhelper.yaml` are up-to-date with the version you wish to upgrade to._ + +```sh +# Upgrade the whole cluster to a newer Talos version +task talos:upgrade-cluster +# e.g. task talos:upgrade-cluster +``` + +```sh +# Upgrade node to a newer Talos version +task talos:upgrade-node HOSTNAME=? +# e.g. task talos:upgrade HOSTNAME=k8s-0 +``` + +```sh +# Upgrade cluster to a newer Kubernetes version +task talos:upgrade-k8s +# e.g. task talos:upgrade-k8s +``` + +## 🤖 Renovate + +[Renovate](https://www.mend.io/renovate) is a tool that automates dependency management. It is designed to scan your repository around the clock and open PRs for out-of-date dependencies it finds. Common dependencies it can discover are Helm charts, container images, GitHub Actions, Ansible roles... even Flux itself! Merging a PR will cause Flux to apply the update to your cluster. + +To enable Renovate, click the 'Configure' button over at their [Github app page](https://github.com/apps/renovate) and select your repository. Renovate creates a "Dependency Dashboard" as an issue in your repository, giving an overview of the status of all updates. The dashboard has interactive checkboxes that let you do things like advance scheduling or reattempt update PRs you closed without merging. + +The base Renovate configuration in your repository can be viewed at [.github/renovate.json5](./.github/renovate.json5). By default it is scheduled to be active with PRs every weekend, but you can [change the schedule to anything you want](https://docs.renovatebot.com/presets-schedule), or remove it if you want Renovate to open PRs right away. + +## 🐛 Debugging + +Below is a general guide on trying to debug an issue with an resource or application. For example, if a workload/resource is not showing up or a pod has started but in a `CrashLoopBackOff` or `Pending` state. + +1. Start by checking all Flux Kustomizations & Git Repository & OCI Repository and verify they are healthy. + + ```sh + flux get sources oci -A + flux get sources git -A + flux get ks -A + ``` + +2. Then check all the Flux Helm Releases and verify they are healthy. + + ```sh + flux get hr -A + ``` + +3. Then check the if the pod is present. + + ```sh + kubectl -n get pods -o wide + ``` + +4. Then check the logs of the pod if its there. + + ```sh + kubectl -n logs -f + # or + stern -n + ``` + +5. If a resource exists try to describe it to see what problems it might have. + + ```sh + kubectl -n describe + ``` + +6. Check the namespace events + + ```sh + kubectl -n get events --sort-by='.metadata.creationTimestamp' + ``` + +Resolving problems that you have could take some tweaking of your YAML manifests in order to get things working, other times it could be a external factor like permissions on NFS. If you are unable to figure out your problem see the help section below. + +## 👉 Help + +- Make a post in this repository's Github [Discussions](https://github.com/onedr0p/cluster-template/discussions). +- Start a thread in the `#support` or `#cluster-template` channels in the [Home Operations](https://discord.gg/home-operations) Discord server. + +## ❔ What's next + +The cluster is your oyster (or something like that). Below are some optional considerations you might want to review. + +### Ship it + +To browse or get ideas on applications people are running, community member [@whazor](https://github.com/whazor) created [Kubesearch](https://kubesearch.dev) as a creative way to search Flux HelmReleases across Github and Gitlab. + +### DNS + +Instead of using [k8s_gateway](https://github.com/ori-edge/k8s_gateway) to provide DNS for your applications you might want to check out [external-dns](https://github.com/kubernetes-sigs/external-dns), it has wide support for many different providers such as [Pi-hole](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/pihole.md), [UniFi](https://github.com/kashalls/external-dns-unifi-webhook), [Adguard Home](https://github.com/muhlba91/external-dns-provider-adguard), [Bind](https://github.com/kubernetes-sigs/external-dns/blob/master/docs/tutorials/rfc2136.md) and more. + +### Storage + +The included CSI (openebs in local-hostpath mode) is a great start for storage but soon you might find you need more features like replicated block storage, or to connect to a NFS/SMB/iSCSI server. If you need any of those features be sure to check out the projects like [rook-ceph](https://github.com/rook/rook), [longhorn](https://github.com/longhorn/longhorn), [openebs](https://github.com/openebs/openebs), [democratic-csi](https://github.com/democratic-csi/democratic-csi), [csi-driver-nfs](https://github.com/kubernetes-csi/csi-driver-nfs), +and [synology-csi](https://github.com/SynologyOpenSource/synology-csi). + +## 🙌 Related Projects + +If this repo is too hot to handle or too cold to hold check out these following projects. + +- [khuedoan/homelab](https://github.com/khuedoan/homelab) - _Modern self-hosting framework, fully automated from empty disk to operating services with a single command._ +- [danmanners/aws-argo-cluster-template](https://github.com/danmanners/aws-argo-cluster-template) - _A community opinionated template for deploying Kubernetes clusters on-prem and in AWS using Pulumi, SOPS, Sealed Secrets, GitHub Actions, Renovate, Cilium and more!_ +- [ricsanfre/pi-cluster](https://github.com/ricsanfre/pi-cluster) - _Pi Kubernetes Cluster. Homelab kubernetes cluster automated with Ansible and ArgoCD_ +- [techno-tim/k3s-ansible](https://github.com/techno-tim/k3s-ansible) - _The easiest way to bootstrap a self-hosted High Availability Kubernetes cluster. A fully automated HA k3s etcd install with kube-vip, MetalLB, and more_ + +## ⭐ Stargazers + +
+ +[![Star History Chart](https://api.star-history.com/svg?repos=onedr0p/cluster-template&type=Date)](https://star-history.com/#onedr0p/cluster-template&Date) + +
+ +## 🤝 Thanks + +Big shout out to all the contributors, sponsors and everyone else who has helped on this project. diff --git a/Taskfile.yaml b/Taskfile.yaml new file mode 100644 index 00000000..10090c65 --- /dev/null +++ b/Taskfile.yaml @@ -0,0 +1,52 @@ +--- +# yaml-language-server: $schema=https://taskfile.dev/schema.json +version: '3' + +set: [pipefail] +shopt: [globstar] + +vars: + BOOTSTRAP_DIR: '{{.ROOT_DIR}}/bootstrap' + KUBERNETES_DIR: '{{.ROOT_DIR}}/kubernetes' + SCRIPTS_DIR: '{{.ROOT_DIR}}/scripts' + BOOTSTRAP_CONFIG_FILE: '{{.ROOT_DIR}}/config.yaml' + MAKEJINJA_CONFIG_FILE: '{{.ROOT_DIR}}/makejinja.toml' + SOPS_CONFIG_FILE: '{{.ROOT_DIR}}/.sops.yaml' + +env: + KUBECONFIG: '{{.ROOT_DIR}}/kubeconfig' + PYTHONDONTWRITEBYTECODE: '1' + SOPS_AGE_KEY_FILE: '{{.ROOT_DIR}}/age.key' + VIRTUAL_ENV: '{{.ROOT_DIR}}/.venv' + +includes: + bootstrap: .taskfiles/bootstrap + kubernetes: .taskfiles/kubernetes + talos: .taskfiles/talos + workstation: .taskfiles/workstation + user: + taskfile: .taskfiles/User + optional: true + +tasks: + + default: task --list + + init: + desc: Initialize configuration files + cmd: cp {{.BOOTSTRAP_CONFIG_FILE | replace ".yaml" ".sample.yaml"}} {{.BOOTSTRAP_CONFIG_FILE}} + status: + - test -f {{.BOOTSTRAP_CONFIG_FILE}} + + configure: + desc: Render and validate configuration files + prompt: Any conflicting config in the kubernetes directory will be overwritten... continue? + deps: + - init + - bootstrap:age-keygen + - workstation:direnv + - workstation:venv + cmds: + - task: bootstrap:template + - task: bootstrap:secrets + - task: kubernetes:kubeconform diff --git a/bootstrap/overrides/readme.partial.yaml.j2 b/bootstrap/overrides/readme.partial.yaml.j2 new file mode 100644 index 00000000..b73f7538 --- /dev/null +++ b/bootstrap/overrides/readme.partial.yaml.j2 @@ -0,0 +1,5 @@ +#| Place user jinja template overrides in this file's directory |# +#| Docs: https://mirkolenz.github.io/makejinja/makejinja.html |# +#| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/makejinja.toml |# +#| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/input1/not-empty.yaml.jinja |# +#| Example: https://github.com/mirkolenz/makejinja/blob/main/tests/data/input2/not-empty.yaml.jinja |# diff --git a/bootstrap/scripts/plugin.py b/bootstrap/scripts/plugin.py new file mode 100644 index 00000000..e8f1a288 --- /dev/null +++ b/bootstrap/scripts/plugin.py @@ -0,0 +1,81 @@ +import importlib.util +import sys +from collections.abc import Callable +from pathlib import Path +from typing import Any + +from typing import Any +from netaddr import IPNetwork + +import makejinja +import validation + + +# Return the filename of a path without the j2 extension +def basename(value: str) -> str: + return Path(value).stem + + +# Return a list of files in the talos patches directory +def talos_patches(value: str) -> list[str]: + path = Path(f'bootstrap/templates/kubernetes/bootstrap/talos/patches/{value}') + if not path.is_dir(): + return [] + return [str(f) for f in sorted(path.glob('*.yaml.j2')) if f.is_file()] + + +# Return the nth host in a CIDR range +def nthhost(value: str, query: int) -> str: + value = IPNetwork(value) + try: + nth = int(query) + if value.size > nth: + return str(value[nth]) + except ValueError: + return False + return value + + +def import_filter(file: Path) -> Callable[[dict[str, Any]], bool]: + module_path = file.relative_to(Path.cwd()).with_suffix("") + module_name = str(module_path).replace("/", ".") + spec = importlib.util.spec_from_file_location(module_name, file) + assert spec is not None + module = importlib.util.module_from_spec(spec) + sys.modules[module_name] = module + assert spec.loader is not None + spec.loader.exec_module(module) + return module.main + + +class Plugin(makejinja.plugin.Plugin): + def __init__(self, data: dict[str, Any], config: makejinja.config.Config): + self._data = data + self._config = config + + self._excluded_dirs: set[Path] = set() + for input_path in config.inputs: + for filter_file in input_path.rglob(".mjfilter.py"): + filter_func = import_filter(filter_file) + if filter_func(data) is False: + self._excluded_dirs.add(filter_file.parent) + + validation.validate(data) + + + def filters(self) -> makejinja.plugin.Filters: + return [basename, nthhost] + + + def functions(self) -> makejinja.plugin.Functions: + return [talos_patches] + + + def path_filters(self): + return [self._mjfilter_func] + + + def _mjfilter_func(self, path: Path) -> bool: + return not any( + path.is_relative_to(excluded_dir) for excluded_dir in self._excluded_dirs + ) diff --git a/bootstrap/scripts/validation.py b/bootstrap/scripts/validation.py new file mode 100644 index 00000000..456f9c13 --- /dev/null +++ b/bootstrap/scripts/validation.py @@ -0,0 +1,113 @@ +from functools import wraps +from shutil import which +from typing import Callable, cast +from zoneinfo import available_timezones +import netaddr +import re +import socket +import sys + +GLOBAL_CLI_TOOLS = ["age", "flux", "helmfile", "sops", "jq", "kubeconform", "kustomize", "talosctl", "talhelper"] +CLOUDFLARE_TOOLS = ["cloudflared"] + + +def required(*keys: str): + def wrapper_outter(func: Callable): + @wraps(func) + def wrapper(data: dict, *_, **kwargs) -> None: + for key in keys: + if data.get(key) is None: + raise ValueError(f"Missing required key {key}") + return func(*[data[key] for key in keys], **kwargs) + + return wrapper + + return wrapper_outter + + +def validate_python_version() -> None: + required_version = (3, 11, 0) + if sys.version_info < required_version: + raise ValueError(f"Python {sys.version_info} is below 3.11. Please upgrade.") + + +def validate_ip(ip: str) -> str: + try: + netaddr.IPAddress(ip) + except netaddr.core.AddrFormatError as e: + raise ValueError(f"Invalid IP address {ip}") from e + return ip + + +def validate_network(cidr: str, family: int) -> str: + try: + network = netaddr.IPNetwork(cidr) + if network.version != family: + raise ValueError(f"Invalid CIDR family {network.version}") + except netaddr.core.AddrFormatError as e: + raise ValueError(f"Invalid CIDR {cidr}") from e + return cidr + + +def validate_node(node: dict, node_cidr: str) -> None: + if not node.get("name"): + raise ValueError(f"A node is missing a name") + if not re.match(r"^[a-z0-9-]+$", node.get('name')): + raise ValueError(f"Node {node.get('name')} has an invalid name") + if not node.get("disk"): + raise ValueError(f"Node {node.get('name')} is missing disk") + if not node.get("mac_addr"): + raise ValueError(f"Node {node.get('name')} is missing mac_addr") + if not re.match(r"(?:[0-9a-fA-F]:?){12}", node.get("mac_addr")): + raise ValueError(f"Node {node.get('name')} has an invalid mac_addr, is this a MAC address?") + if node.get("address"): + ip = validate_ip(node.get("address")) + if netaddr.IPAddress(ip, 4) not in netaddr.IPNetwork(node_cidr): + raise ValueError(f"Node {node.get('name')} is not in the node CIDR {node_cidr}") + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: + sock.settimeout(5) + result = sock.connect_ex((ip, 50000)) + if result != 0: + raise ValueError(f"Node {node.get('name')} port 50000 is not open") + + +@required("bootstrap_cloudflare") +def validate_cli_tools(cloudflare: dict, **_) -> None: + for tool in GLOBAL_CLI_TOOLS: + if not which(tool): + raise ValueError(f"Missing required CLI tool {tool}") + for tool in CLOUDFLARE_TOOLS if cloudflare.get("enabled", False) else []: + if not which(tool): + raise ValueError(f"Missing required CLI tool {tool}") + + +@required("bootstrap_age_pubkey") +def validate_age(key: str, **_) -> None: + if not re.match(r"^age1[a-z0-9]{0,58}$", key): + raise ValueError(f"Invalid Age public key {key}") + + +@required("bootstrap_node_network", "bootstrap_node_inventory") +def validate_nodes(node_cidr: str, nodes: dict[list], **_) -> None: + node_cidr = validate_network(node_cidr, 4) + + controllers = [node for node in nodes if node.get('controller') == True] + if len(controllers) < 1: + raise ValueError(f"Must have at least one controller node") + if len(controllers) % 2 == 0: + raise ValueError(f"Must have an odd number of controller nodes") + for node in controllers: + validate_node(node, node_cidr) + + workers = [node for node in nodes if node.get('controller') == False] + for node in workers: + validate_node(node, node_cidr) + + +def validate(data: dict) -> None: + validate_python_version() + validate_cli_tools(data) + validate_age(data) + + if not data.get("skip_tests", False): + validate_nodes(data) diff --git a/bootstrap/templates/.sops.yaml.j2 b/bootstrap/templates/.sops.yaml.j2 new file mode 100644 index 00000000..aa3b06fa --- /dev/null +++ b/bootstrap/templates/.sops.yaml.j2 @@ -0,0 +1,12 @@ +--- +creation_rules: + - # IMPORTANT: This rule MUST be above the others + path_regex: talos/.*\.sops\.ya?ml + key_groups: + - age: + - "#{ bootstrap_age_pubkey }#" + - path_regex: kubernetes/.*\.sops\.ya?ml + encrypted_regex: "^(data|stringData)$" + key_groups: + - age: + - "#{ bootstrap_age_pubkey }#" diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..9d479bdf --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/app/helmrelease.yaml.j2 @@ -0,0 +1,31 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: cert-manager +spec: + interval: 30m + chart: + spec: + chart: cert-manager + version: v1.16.1 + sourceRef: + kind: HelmRepository + name: jetstack + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + crds: + enabled: true + dns01RecursiveNameservers: https://1.1.1.1:443/dns-query,https://1.0.0.1:443/dns-query + dns01RecursiveNameserversOnly: true + prometheus: + enabled: true + servicemonitor: + enabled: true diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/.mjfilter.py b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/.mjfilter.py new file mode 100644 index 00000000..d9ae82b4 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/.mjfilter.py @@ -0,0 +1 @@ +main = lambda data: data.get("bootstrap_cloudflare", {}).get("enabled", False) == True diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/issuers.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/issuers.yaml.j2 new file mode 100644 index 00000000..1cf7148a --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/issuers.yaml.j2 @@ -0,0 +1,39 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-production +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: "${SECRET_ACME_EMAIL}" + privateKeySecretRef: + name: letsencrypt-production + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cert-manager-secret + key: api-token + selector: + dnsZones: + - "${SECRET_DOMAIN}" +--- +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: letsencrypt-staging +spec: + acme: + server: https://acme-staging-v02.api.letsencrypt.org/directory + email: "${SECRET_ACME_EMAIL}" + privateKeySecretRef: + name: letsencrypt-staging + solvers: + - dns01: + cloudflare: + apiTokenSecretRef: + name: cert-manager-secret + key: api-token + selector: + dnsZones: + - "${SECRET_DOMAIN}" diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/kustomization.yaml.j2 new file mode 100644 index 00000000..17754be6 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./secret.sops.yaml + - ./issuers.yaml diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml.j2 new file mode 100644 index 00000000..f5bf887f --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/issuers/secret.sops.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: cert-manager-secret +stringData: + api-token: "#{ bootstrap_cloudflare.token }#" diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 new file mode 100644 index 00000000..a26f1852 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/cert-manager/ks.yaml.j2 @@ -0,0 +1,42 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app cert-manager + namespace: flux-system +spec: + targetNamespace: cert-manager + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/cert-manager/cert-manager/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: true + interval: 30m + timeout: 5m +#% if bootstrap_cloudflare.enabled %# +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app cert-manager-issuers + namespace: flux-system +spec: + targetNamespace: cert-manager + commonMetadata: + labels: + app.kubernetes.io/name: *app + dependsOn: + - name: cert-manager + path: ./kubernetes/apps/cert-manager/cert-manager/issuers + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: true + interval: 30m + timeout: 5m +#% endif %# diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/kustomization.yaml.j2 new file mode 100644 index 00000000..a0a3e5ed --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./namespace.yaml + - ./cert-manager/ks.yaml diff --git a/bootstrap/templates/kubernetes/apps/cert-manager/namespace.yaml.j2 b/bootstrap/templates/kubernetes/apps/cert-manager/namespace.yaml.j2 new file mode 100644 index 00000000..ed788350 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/cert-manager/namespace.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: cert-manager + labels: + kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/bootstrap/templates/kubernetes/apps/flux-system/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/kustomization.yaml.j2 new file mode 100644 index 00000000..10587f8c --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./namespace.yaml + - ./webhooks/ks.yaml diff --git a/bootstrap/templates/kubernetes/apps/flux-system/namespace.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/namespace.yaml.j2 new file mode 100644 index 00000000..b48db452 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/namespace.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: flux-system + labels: + kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/ingress.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/ingress.yaml.j2 new file mode 100644 index 00000000..17171674 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/ingress.yaml.j2 @@ -0,0 +1,22 @@ +#% if bootstrap_cloudflare.enabled %# +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: flux-webhook + annotations: + external-dns.alpha.kubernetes.io/target: "external.${SECRET_DOMAIN}" +spec: + ingressClassName: external + rules: + - host: "flux-webhook.${SECRET_DOMAIN}" + http: + paths: + - path: /hook/ + pathType: Prefix + backend: + service: + name: webhook-receiver + port: + number: 80 +#% endif %# diff --git a/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/kustomization.yaml.j2 new file mode 100644 index 00000000..75fc5841 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/kustomization.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./secret.sops.yaml + #% if bootstrap_cloudflare.enabled %# + - ./ingress.yaml + #% endif %# + - ./receiver.yaml diff --git a/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/receiver.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/receiver.yaml.j2 new file mode 100644 index 00000000..cca5931b --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/receiver.yaml.j2 @@ -0,0 +1,25 @@ +--- +apiVersion: notification.toolkit.fluxcd.io/v1 +kind: Receiver +metadata: + name: github-receiver +spec: + type: github + events: + - ping + - push + secretRef: + name: github-webhook-token-secret + resources: + - apiVersion: source.toolkit.fluxcd.io/v1 + kind: GitRepository + name: home-kubernetes + namespace: flux-system + - apiVersion: kustomize.toolkit.fluxcd.io/v1 + kind: Kustomization + name: cluster + namespace: flux-system + - apiVersion: kustomize.toolkit.fluxcd.io/v1 + kind: Kustomization + name: cluster-apps + namespace: flux-system diff --git a/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/secret.sops.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/secret.sops.yaml.j2 new file mode 100644 index 00000000..34ac7daf --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/github/secret.sops.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: github-webhook-token-secret +stringData: + token: "#{ bootstrap_github_webhook_token }#" diff --git a/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/kustomization.yaml.j2 new file mode 100644 index 00000000..ccd8b3eb --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./github diff --git a/bootstrap/templates/kubernetes/apps/flux-system/webhooks/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/ks.yaml.j2 new file mode 100644 index 00000000..25e4b9d5 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/flux-system/webhooks/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app flux-webhooks + namespace: flux-system +spec: + targetNamespace: flux-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/flux-system/webhooks/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: true + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/helm-values.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/helm-values.yaml.j2 new file mode 100644 index 00000000..3de4cea3 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/helm-values.yaml.j2 @@ -0,0 +1,70 @@ +--- +autoDirectNodeRoutes: true +#% if bootstrap_bgp.enabled %# +bgpControlPlane: + enabled: true +#% endif %# +bpf: + masquerade: false # Required for Talos `.machine.features.hostDNS.forwardKubeDNSToHost` +cgroup: + automount: + enabled: false + hostRoot: /sys/fs/cgroup +cluster: + id: 1 + name: "#{ bootstrap_cluster_name | default('home-kubernetes', true) }#" +cni: + exclusive: false +# NOTE: devices might need to be set if you have more than one active NIC on your hosts +# devices: eno+ eth+ +endpointRoutes: + enabled: true +envoy: + enabled: false +hubble: + enabled: false +ipam: + mode: kubernetes +ipv4NativeRoutingCIDR: "#{ bootstrap_pod_network.split(',')[0] }#" +#% if bootstrap_feature_gates.dual_stack_ipv4_first %# +ipv6NativeRoutingCIDR: "#{ bootstrap_pod_network.split(',')[1] }#" +ipv6: + enabled: true +#% endif %# +k8sServiceHost: 127.0.0.1 +k8sServicePort: 7445 +kubeProxyReplacement: true +kubeProxyReplacementHealthzBindAddr: 0.0.0.0:10256 +l2announcements: + #% if ((bootstrap_bgp.enabled) or (bootstrap_feature_gates.dual_stack_ipv4_first)) %# + enabled: false # https://github.com/cilium/cilium/issues/28985 + #% else %# + enabled: true + #% endif %# +loadBalancer: + algorithm: maglev + mode: "#{ bootstrap_loadbalancer_mode | default('dsr', true) }#" +localRedirectPolicy: true +operator: + replicas: 1 + rollOutPods: true +rollOutCiliumPods: true +routingMode: native +securityContext: + capabilities: + ciliumAgent: + - CHOWN + - KILL + - NET_ADMIN + - NET_RAW + - IPC_LOCK + - SYS_ADMIN + - SYS_RESOURCE + - DAC_OVERRIDE + - FOWNER + - SETGID + - SETUID + cleanCiliumState: + - NET_ADMIN + - SYS_ADMIN + - SYS_RESOURCE diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..e9cbc674 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/helmrelease.yaml.j2 @@ -0,0 +1,76 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: cilium +spec: + interval: 30m + chart: + spec: + chart: cilium + version: 1.16.3 + sourceRef: + kind: HelmRepository + name: cilium + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + valuesFrom: + - kind: ConfigMap + name: cilium-helm-values + values: + #% if bootstrap_cloudflare.enabled %# + hubble: + enabled: true + metrics: + enabled: + - dns:query + - drop + - tcp + - flow + - port-distribution + - icmp + - http + serviceMonitor: + enabled: true + dashboards: + enabled: true + annotations: + grafana_folder: Cilium + relay: + enabled: true + rollOutPods: true + prometheus: + serviceMonitor: + enabled: true + ui: + enabled: true + rollOutPods: true + ingress: + enabled: true + className: internal + hosts: ["hubble.${SECRET_DOMAIN}"] + #% endif %# + operator: + prometheus: + enabled: true + serviceMonitor: + enabled: true + dashboards: + enabled: true + annotations: + grafana_folder: Cilium + prometheus: + enabled: true + serviceMonitor: + enabled: true + trustCRDsExist: true + dashboards: + enabled: true + annotations: + grafana_folder: Cilium diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 new file mode 100644 index 00000000..b4f3860b --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/kustomization.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml +configMapGenerator: + - name: cilium-helm-values + files: + - values.yaml=./helm-values.yaml +configurations: + - kustomizeconfig.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/kustomizeconfig.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/kustomizeconfig.yaml.j2 new file mode 100644 index 00000000..58f92ba1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/app/kustomizeconfig.yaml.j2 @@ -0,0 +1,7 @@ +--- +nameReference: + - kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/cilium-l2.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/cilium-l2.yaml.j2 new file mode 100644 index 00000000..db6cd461 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/cilium-l2.yaml.j2 @@ -0,0 +1,26 @@ +#% if ((not bootstrap_bgp.enabled) and (not bootstrap_feature_gates.dual_stack_ipv4_first)) %# +--- +# https://docs.cilium.io/en/latest/network/l2-announcements +apiVersion: cilium.io/v2alpha1 +kind: CiliumL2AnnouncementPolicy +metadata: + name: l2-policy +spec: + loadBalancerIPs: true + # NOTE: interfaces might need to be set if you have more than one active NIC on your hosts + # interfaces: + # - ^eno[0-9]+ + # - ^eth[0-9]+ + nodeSelector: + matchLabels: + kubernetes.io/os: linux +--- +apiVersion: cilium.io/v2alpha1 +kind: CiliumLoadBalancerIPPool +metadata: + name: l2-pool +spec: + allowFirstLastIPs: "Yes" + blocks: + - cidr: "#{ bootstrap_node_network }#" +#% endif %# diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/cilium-l3.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/cilium-l3.yaml.j2 new file mode 100644 index 00000000..e2a97148 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/cilium-l3.yaml.j2 @@ -0,0 +1,41 @@ +#% if bootstrap_bgp.enabled %# +--- +# https://docs.cilium.io/en/latest/network/bgp-control-plane/ +apiVersion: cilium.io/v2alpha1 +kind: CiliumBGPPeeringPolicy +metadata: + name: l3-policy +spec: + nodeSelector: + matchLabels: + kubernetes.io/os: linux + virtualRouters: + - localASN: #{ bootstrap_bgp.local_asn }# + neighbors: + #% if bootstrap_bgp.peers %# + #% for item in bootstrap_bgp.peers %# + - peerAddress: "#{ item }#/32" + peerASN: #{ bootstrap_bgp.peer_asn }# + peerPort: #{ bootstrap_bgp.peer_port | default(179, true) }# + #% endfor %# + #% else %# + #% if bootstrap_node_default_gateway %# + - peerAddress: "#{ bootstrap_node_default_gateway }#/32" + #% else %# + - peerAddress: "#{ bootstrap_node_network | nthhost(1) }#/32" + #% endif %# + peerASN: #{ bootstrap_bgp.peer_asn }# + #% endif %# + serviceSelector: + matchExpressions: + - {key: somekey, operator: NotIn, values: ['never-used-value']} +--- +apiVersion: cilium.io/v2alpha1 +kind: CiliumLoadBalancerIPPool +metadata: + name: l3-pool +spec: + allowFirstLastIPs: "Yes" + blocks: + - cidr: "#{ bootstrap_bgp.advertised_network }#" +#% endif %# diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/kustomization.yaml.j2 new file mode 100644 index 00000000..4fc169b4 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/config/kustomization.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + #% if bootstrap_bgp.enabled %# + - ./cilium-l3.yaml + #% elif not bootstrap_feature_gates.dual_stack_ipv4_first %# + - ./cilium-l2.yaml + #% else %# + [] + #% endif %# diff --git a/bootstrap/templates/kubernetes/apps/kube-system/cilium/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/cilium/ks.yaml.j2 new file mode 100644 index 00000000..2b0c235c --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/cilium/ks.yaml.j2 @@ -0,0 +1,40 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app cilium + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/kube-system/cilium/app + prune: false # never should be deleted + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: true + interval: 30m + timeout: 5m +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app cilium-config + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + dependsOn: + - name: cilium + path: ./kubernetes/apps/kube-system/cilium/config + prune: false # never should be deleted + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/helm-values.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/helm-values.yaml.j2 new file mode 100644 index 00000000..2c358ee3 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/helm-values.yaml.j2 @@ -0,0 +1,50 @@ +--- +fullnameOverride: coredns +k8sAppLabelOverride: kube-dns +serviceAccount: + create: true +service: + name: kube-dns + clusterIP: "#{ bootstrap_service_network | nthhost(10) }#" +servers: + - zones: + - zone: . + scheme: dns:// + use_tcp: true + port: 53 + plugins: + - name: errors + - name: health + configBlock: |- + lameduck 5s + - name: ready + - name: log + configBlock: |- + class error + - name: prometheus + parameters: 0.0.0.0:9153 + - name: kubernetes + parameters: cluster.local in-addr.arpa ip6.arpa + configBlock: |- + pods insecure + fallthrough in-addr.arpa ip6.arpa + - name: forward + parameters: . /etc/resolv.conf + - name: cache + parameters: 30 + - name: loop + - name: reload + - name: loadbalance +affinity: + nodeAffinity: + requiredDuringSchedulingIgnoredDuringExecution: + nodeSelectorTerms: + - matchExpressions: + - key: node-role.kubernetes.io/control-plane + operator: Exists +tolerations: + - key: CriticalAddonsOnly + operator: Exists + - key: node-role.kubernetes.io/control-plane + operator: Exists + effect: NoSchedule diff --git a/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..72c947c2 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/helmrelease.yaml.j2 @@ -0,0 +1,26 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: coredns +spec: + interval: 30m + chart: + spec: + chart: coredns + version: 1.36.1 + sourceRef: + kind: HelmRepository + name: coredns + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + strategy: rollback + retries: 3 + valuesFrom: + - kind: ConfigMap + name: coredns-helm-values diff --git a/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 new file mode 100644 index 00000000..691355b5 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/kustomization.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml +configMapGenerator: + - name: coredns-helm-values + files: + - values.yaml=./helm-values.yaml +configurations: + - kustomizeconfig.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/kustomizeconfig.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/kustomizeconfig.yaml.j2 new file mode 100644 index 00000000..58f92ba1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/coredns/app/kustomizeconfig.yaml.j2 @@ -0,0 +1,7 @@ +--- +nameReference: + - kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease diff --git a/bootstrap/templates/kubernetes/apps/kube-system/coredns/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/coredns/ks.yaml.j2 new file mode 100644 index 00000000..afa7ae2f --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/coredns/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app coredns + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/kube-system/coredns/app + prune: false # never should be deleted + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/helm-values.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/helm-values.yaml.j2 new file mode 100644 index 00000000..09d17584 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/helm-values.yaml.j2 @@ -0,0 +1,3 @@ +--- +providerRegex: ^(#{ (bootstrap_node_inventory | map(attribute='name') | join('|')) }#)$ +bypassDnsResolution: true diff --git a/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..f3e6a7d1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/helmrelease.yaml.j2 @@ -0,0 +1,30 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: kubelet-csr-approver +spec: + interval: 30m + chart: + spec: + chart: kubelet-csr-approver + version: 1.2.3 + sourceRef: + kind: HelmRepository + name: postfinance + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + valuesFrom: + - kind: ConfigMap + name: kubelet-csr-approver-helm-values + values: + metrics: + enable: true + serviceMonitor: + enabled: true diff --git a/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/kustomization.yaml.j2 new file mode 100644 index 00000000..30dddafc --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/kustomization.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml +configMapGenerator: + - name: kubelet-csr-approver-helm-values + files: + - values.yaml=./helm-values.yaml +configurations: + - kustomizeconfig.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/kustomizeconfig.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/kustomizeconfig.yaml.j2 new file mode 100644 index 00000000..58f92ba1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/app/kustomizeconfig.yaml.j2 @@ -0,0 +1,7 @@ +--- +nameReference: + - kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease diff --git a/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/ks.yaml.j2 new file mode 100644 index 00000000..8fc9282f --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/kubelet-csr-approver/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app kubelet-csr-approver + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/kube-system/kubelet-csr-approver/app + prune: false # never should be deleted + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/kube-system/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/kustomization.yaml.j2 new file mode 100644 index 00000000..7a71f70f --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/kustomization.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./namespace.yaml + - ./cilium/ks.yaml + - ./coredns/ks.yaml + - ./metrics-server/ks.yaml + - ./reloader/ks.yaml + - ./kubelet-csr-approver/ks.yaml + - ./spegel/ks.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..9c0b22b5 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/app/helmrelease.yaml.j2 @@ -0,0 +1,31 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: metrics-server +spec: + interval: 30m + chart: + spec: + chart: metrics-server + version: 3.12.2 + sourceRef: + kind: HelmRepository + name: metrics-server + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + args: + - --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname + - --kubelet-use-node-status-port + - --metric-resolution=15s + metrics: + enabled: true + serviceMonitor: + enabled: true diff --git a/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 new file mode 100644 index 00000000..10828aaa --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/metrics-server/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app metrics-server + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/kube-system/metrics-server/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/kube-system/namespace.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/namespace.yaml.j2 new file mode 100644 index 00000000..5eeb2c91 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/namespace.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: kube-system + labels: + kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/bootstrap/templates/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..64dc3493 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/reloader/app/helmrelease.yaml.j2 @@ -0,0 +1,29 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: reloader +spec: + interval: 30m + chart: + spec: + chart: reloader + version: 1.1.0 + sourceRef: + kind: HelmRepository + name: stakater + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + fullnameOverride: reloader + reloader: + readOnlyRootFileSystem: true + podMonitor: + enabled: true + namespace: "{{ .Release.Namespace }}" diff --git a/bootstrap/templates/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/reloader/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/reloader/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/reloader/ks.yaml.j2 new file mode 100644 index 00000000..c0e669e2 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/reloader/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app reloader + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/kube-system/reloader/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/helm-values.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/helm-values.yaml.j2 new file mode 100644 index 00000000..a4185ae3 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/helm-values.yaml.j2 @@ -0,0 +1,7 @@ +--- +spegel: + containerdSock: /run/containerd/containerd.sock + containerdRegistryConfigPath: /etc/cri/conf.d/hosts +service: + registry: + hostPort: 29999 diff --git a/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..ea255fc4 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/helmrelease.yaml.j2 @@ -0,0 +1,30 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: spegel +spec: + interval: 30m + chart: + spec: + chart: spegel + version: v0.0.27 + sourceRef: + kind: HelmRepository + name: spegel + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + valuesFrom: + - kind: ConfigMap + name: spegel-helm-values + values: + grafanaDashboard: + enabled: true + serviceMonitor: + enabled: true diff --git a/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 new file mode 100644 index 00000000..1e1aa1d1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/kustomization.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml +configMapGenerator: + - name: spegel-helm-values + files: + - values.yaml=./helm-values.yaml +configurations: + - kustomizeconfig.yaml diff --git a/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/kustomizeconfig.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/kustomizeconfig.yaml.j2 new file mode 100644 index 00000000..58f92ba1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/spegel/app/kustomizeconfig.yaml.j2 @@ -0,0 +1,7 @@ +--- +nameReference: + - kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease diff --git a/bootstrap/templates/kubernetes/apps/kube-system/spegel/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/kube-system/spegel/ks.yaml.j2 new file mode 100644 index 00000000..866bb6b9 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/kube-system/spegel/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app spegel + namespace: flux-system +spec: + targetNamespace: kube-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/kube-system/spegel/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/network/.mjfilter.py b/bootstrap/templates/kubernetes/apps/network/.mjfilter.py new file mode 100644 index 00000000..d9ae82b4 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/.mjfilter.py @@ -0,0 +1 @@ +main = lambda data: data.get("bootstrap_cloudflare", {}).get("enabled", False) == True diff --git a/bootstrap/templates/kubernetes/apps/network/cloudflared/app/configs/config.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/configs/config.yaml.j2 new file mode 100644 index 00000000..05bcef5c --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/configs/config.yaml.j2 @@ -0,0 +1,10 @@ +--- +originRequest: + originServerName: "external.${SECRET_DOMAIN}" + +ingress: + - hostname: "${SECRET_DOMAIN}" + service: https://ingress-nginx-external-controller.network.svc.cluster.local:443 + - hostname: "*.${SECRET_DOMAIN}" + service: https://ingress-nginx-external-controller.network.svc.cluster.local:443 + - service: http_status:404 diff --git a/bootstrap/templates/kubernetes/apps/network/cloudflared/app/dnsendpoint.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/dnsendpoint.yaml.j2 new file mode 100644 index 00000000..43d7d7b2 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/dnsendpoint.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: externaldns.k8s.io/v1alpha1 +kind: DNSEndpoint +metadata: + name: cloudflared +spec: + endpoints: + - dnsName: "external.${SECRET_DOMAIN}" + recordType: CNAME + targets: ["${SECRET_CLOUDFLARE_TUNNEL_ID}.cfargotunnel.com"] diff --git a/bootstrap/templates/kubernetes/apps/network/cloudflared/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..e94d6723 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/helmrelease.yaml.j2 @@ -0,0 +1,109 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: cloudflared +spec: + interval: 30m + chart: + spec: + chart: app-template + version: 3.5.1 + sourceRef: + kind: HelmRepository + name: bjw-s + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + controllers: + cloudflared: + strategy: RollingUpdate + annotations: + reloader.stakater.com/auto: "true" + containers: + app: + image: + repository: docker.io/cloudflare/cloudflared + tag: 2024.10.1 + env: + NO_AUTOUPDATE: true + TUNNEL_CRED_FILE: /etc/cloudflared/creds/credentials.json + TUNNEL_METRICS: 0.0.0.0:8080 + TUNNEL_ORIGIN_ENABLE_HTTP2: true + TUNNEL_TRANSPORT_PROTOCOL: quic + TUNNEL_POST_QUANTUM: true + TUNNEL_ID: + valueFrom: + secretKeyRef: + name: cloudflared-secret + key: TUNNEL_ID + args: + - tunnel + - --config + - /etc/cloudflared/config/config.yaml + - run + - "$(TUNNEL_ID)" + probes: + liveness: &probes + enabled: true + custom: true + spec: + httpGet: + path: /ready + port: &port 8080 + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + readiness: *probes + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: { drop: ["ALL"] } + resources: + requests: + cpu: 10m + limits: + memory: 256Mi + defaultPodOptions: + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + seccompProfile: { type: RuntimeDefault } + service: + app: + controller: cloudflared + ports: + http: + port: *port + serviceMonitor: + app: + serviceName: cloudflared + endpoints: + - port: http + scheme: http + path: /metrics + interval: 1m + scrapeTimeout: 10s + persistence: + config: + type: configMap + name: cloudflared-configmap + globalMounts: + - path: /etc/cloudflared/config/config.yaml + subPath: config.yaml + readOnly: true + creds: + type: secret + name: cloudflared-secret + globalMounts: + - path: /etc/cloudflared/creds/credentials.json + subPath: credentials.json + readOnly: true diff --git a/bootstrap/templates/kubernetes/apps/network/cloudflared/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/kustomization.yaml.j2 new file mode 100644 index 00000000..891a864a --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/kustomization.yaml.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./dnsendpoint.yaml + - ./secret.sops.yaml + - ./helmrelease.yaml +configMapGenerator: + - name: cloudflared-configmap + files: + - ./configs/config.yaml +generatorOptions: + disableNameSuffixHash: true diff --git a/bootstrap/templates/kubernetes/apps/network/cloudflared/app/secret.sops.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/secret.sops.yaml.j2 new file mode 100644 index 00000000..67d169ed --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/cloudflared/app/secret.sops.yaml.j2 @@ -0,0 +1,13 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: cloudflared-secret +stringData: + TUNNEL_ID: "#{ bootstrap_cloudflare.tunnel.id }#" + credentials.json: | + { + "AccountTag": "#{ bootstrap_cloudflare.tunnel.account_id }#", + "TunnelSecret": "#{ bootstrap_cloudflare.tunnel.secret }#", + "TunnelID": "#{ bootstrap_cloudflare.tunnel.id }#" + } diff --git a/bootstrap/templates/kubernetes/apps/network/cloudflared/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/cloudflared/ks.yaml.j2 new file mode 100644 index 00000000..01eb3909 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/cloudflared/ks.yaml.j2 @@ -0,0 +1,21 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app cloudflared + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + dependsOn: + - name: external-dns + path: ./kubernetes/apps/network/cloudflared/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/network/echo-server/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/echo-server/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..26c17a56 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/echo-server/app/helmrelease.yaml.j2 @@ -0,0 +1,91 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: echo-server +spec: + interval: 30m + chart: + spec: + chart: app-template + version: 3.5.1 + sourceRef: + kind: HelmRepository + name: bjw-s + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + controllers: + echo-server: + strategy: RollingUpdate + containers: + app: + image: + repository: ghcr.io/mendhak/http-https-echo + tag: 35 + env: + HTTP_PORT: &port 8080 + LOG_WITHOUT_NEWLINE: true + LOG_IGNORE_PATH: /healthz + PROMETHEUS_ENABLED: true + probes: + liveness: &probes + enabled: true + custom: true + spec: + httpGet: + path: /healthz + port: *port + initialDelaySeconds: 0 + periodSeconds: 10 + timeoutSeconds: 1 + failureThreshold: 3 + readiness: *probes + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: { drop: ["ALL"] } + resources: + requests: + cpu: 10m + limits: + memory: 64Mi + defaultPodOptions: + securityContext: + runAsNonRoot: true + runAsUser: 65534 + runAsGroup: 65534 + seccompProfile: { type: RuntimeDefault } + service: + app: + controller: echo-server + ports: + http: + port: *port + serviceMonitor: + app: + serviceName: echo-server + endpoints: + - port: http + scheme: http + path: /metrics + interval: 1m + scrapeTimeout: 10s + ingress: + app: + className: external + annotations: + external-dns.alpha.kubernetes.io/target: "external.${SECRET_DOMAIN}" + hosts: + - host: "{{ .Release.Name }}.${SECRET_DOMAIN}" + paths: + - path: / + service: + identifier: app + port: http diff --git a/bootstrap/templates/kubernetes/apps/network/echo-server/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/echo-server/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/echo-server/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/network/echo-server/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/echo-server/ks.yaml.j2 new file mode 100644 index 00000000..6440fc8a --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/echo-server/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app echo-server + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/network/echo-server/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/network/external-dns/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/external-dns/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..5ce6867e --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/external-dns/app/helmrelease.yaml.j2 @@ -0,0 +1,48 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: &app external-dns +spec: + interval: 30m + chart: + spec: + chart: external-dns + version: 1.15.0 + sourceRef: + kind: HelmRepository + name: external-dns + namespace: flux-system + install: + crds: CreateReplace + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + crds: CreateReplace + remediation: + strategy: rollback + retries: 3 + values: + fullnameOverride: *app + provider: cloudflare + env: + - name: CF_API_TOKEN + valueFrom: + secretKeyRef: + name: external-dns-secret + key: api-token + extraArgs: + - --ingress-class=external + - --cloudflare-proxied + - --crd-source-apiversion=externaldns.k8s.io/v1alpha1 + - --crd-source-kind=DNSEndpoint + policy: sync + sources: ["crd", "ingress"] + txtPrefix: k8s. + txtOwnerId: default + domainFilters: ["${SECRET_DOMAIN}"] + serviceMonitor: + enabled: true + podAnnotations: + secret.reloader.stakater.com/reload: external-dns-secret diff --git a/bootstrap/templates/kubernetes/apps/network/external-dns/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/external-dns/app/kustomization.yaml.j2 new file mode 100644 index 00000000..95bf4747 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/external-dns/app/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./secret.sops.yaml + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/network/external-dns/app/secret.sops.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/external-dns/app/secret.sops.yaml.j2 new file mode 100644 index 00000000..c067b329 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/external-dns/app/secret.sops.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: external-dns-secret +stringData: + api-token: "#{ bootstrap_cloudflare.token }#" diff --git a/bootstrap/templates/kubernetes/apps/network/external-dns/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/external-dns/ks.yaml.j2 new file mode 100644 index 00000000..ca5826cc --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/external-dns/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app external-dns + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/network/external-dns/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: true + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/kustomization.yaml.j2 new file mode 100644 index 00000000..94d1afbf --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/kustomization.yaml.j2 @@ -0,0 +1,8 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./staging.yaml + #% if bootstrap_cloudflare.acme.production %# + - ./production.yaml + #% endif %# diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/production.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/production.yaml.j2 new file mode 100644 index 00000000..b5afdf41 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/production.yaml.j2 @@ -0,0 +1,14 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: "${SECRET_DOMAIN/./-}-production" +spec: + secretName: "${SECRET_DOMAIN/./-}-production-tls" + issuerRef: + name: letsencrypt-production + kind: ClusterIssuer + commonName: "${SECRET_DOMAIN}" + dnsNames: + - "${SECRET_DOMAIN}" + - "*.${SECRET_DOMAIN}" diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/staging.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/staging.yaml.j2 new file mode 100644 index 00000000..9c869425 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/certificates/staging.yaml.j2 @@ -0,0 +1,14 @@ +--- +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: "${SECRET_DOMAIN/./-}-staging" +spec: + secretName: "${SECRET_DOMAIN/./-}-staging-tls" + issuerRef: + name: letsencrypt-staging + kind: ClusterIssuer + commonName: "${SECRET_DOMAIN}" + dnsNames: + - "${SECRET_DOMAIN}" + - "*.${SECRET_DOMAIN}" diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/external/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/external/helmrelease.yaml.j2 new file mode 100644 index 00000000..1a5c10ab --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/external/helmrelease.yaml.j2 @@ -0,0 +1,83 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: ingress-nginx-external +spec: + interval: 30m + chart: + spec: + chart: ingress-nginx + version: 4.11.2 + sourceRef: + kind: HelmRepository + name: ingress-nginx + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + dependsOn: + - name: cloudflared + namespace: network + values: + fullnameOverride: ingress-nginx-external + controller: + service: + annotations: + external-dns.alpha.kubernetes.io/hostname: "external.${SECRET_DOMAIN}" + lbipam.cilium.io/ips: "#{ bootstrap_cloudflare.tunnel.ingress_vip }#" + #% if bgp.enabled %# + externalTrafficPolicy: Local + #% else %# + externalTrafficPolicy: Cluster + #% endif %# + ingressClassResource: + name: external + default: false + controllerValue: k8s.io/external + admissionWebhooks: + objectSelector: + matchExpressions: + - key: ingress-class + operator: In + values: ["external"] + config: + client-body-buffer-size: 100M + client-body-timeout: 120 + client-header-timeout: 120 + enable-brotli: "true" + enable-real-ip: "true" + hsts-max-age: 31449600 + keep-alive-requests: 10000 + keep-alive: 120 + log-format-escape-json: "true" + log-format-upstream: > + {"time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr", "x_forwarded_for": "$proxy_add_x_forwarded_for", + "request_id": "$req_id", "remote_user": "$remote_user", "bytes_sent": $bytes_sent, "request_time": $request_time, + "status": $status, "vhost": "$host", "request_proto": "$server_protocol", "path": "$uri", "request_query": "$args", + "request_length": $request_length, "duration": $request_time, "method": "$request_method", "http_referrer": "$http_referer", + "http_user_agent": "$http_user_agent"} + proxy-body-size: 0 + proxy-buffer-size: 16k + ssl-protocols: TLSv1.3 TLSv1.2 + metrics: + enabled: true + serviceMonitor: + enabled: true + namespaceSelector: + any: true + extraArgs: + #% if bootstrap_cloudflare.acme.production %# + default-ssl-certificate: "network/${SECRET_DOMAIN/./-}-production-tls" + #% else %# + default-ssl-certificate: "network/${SECRET_DOMAIN/./-}-staging-tls" + #% endif %# + resources: + requests: + cpu: 100m + limits: + memory: 500Mi diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/external/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/external/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/external/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/internal/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/internal/helmrelease.yaml.j2 new file mode 100644 index 00000000..f924f402 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/internal/helmrelease.yaml.j2 @@ -0,0 +1,80 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: ingress-nginx-internal + namespace: network +spec: + interval: 30m + chart: + spec: + chart: ingress-nginx + version: 4.11.2 + sourceRef: + kind: HelmRepository + name: ingress-nginx + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + fullnameOverride: ingress-nginx-internal + controller: + service: + annotations: + lbipam.cilium.io/ips: "#{ bootstrap_cloudflare.ingress_vip }#" + #% if bgp.enabled %# + externalTrafficPolicy: Local + #% else %# + externalTrafficPolicy: Cluster + #% endif %# + ingressClassResource: + name: internal + default: true + controllerValue: k8s.io/internal + admissionWebhooks: + objectSelector: + matchExpressions: + - key: ingress-class + operator: In + values: ["internal"] + config: + client-body-buffer-size: 100M + client-body-timeout: 120 + client-header-timeout: 120 + enable-brotli: "true" + enable-real-ip: "true" + hsts-max-age: 31449600 + keep-alive-requests: 10000 + keep-alive: 120 + log-format-escape-json: "true" + log-format-upstream: > + {"time": "$time_iso8601", "remote_addr": "$proxy_protocol_addr", "x_forwarded_for": "$proxy_add_x_forwarded_for", + "request_id": "$req_id", "remote_user": "$remote_user", "bytes_sent": $bytes_sent, "request_time": $request_time, + "status": $status, "vhost": "$host", "request_proto": "$server_protocol", "path": "$uri", "request_query": "$args", + "request_length": $request_length, "duration": $request_time, "method": "$request_method", "http_referrer": "$http_referer", + "http_user_agent": "$http_user_agent"} + proxy-body-size: 0 + proxy-buffer-size: 16k + ssl-protocols: TLSv1.3 TLSv1.2 + metrics: + enabled: true + serviceMonitor: + enabled: true + namespaceSelector: + any: true + extraArgs: + #% if bootstrap_cloudflare.acme.production %# + default-ssl-certificate: "network/${SECRET_DOMAIN/./-}-production-tls" + #% else %# + default-ssl-certificate: "network/${SECRET_DOMAIN/./-}-staging-tls" + #% endif %# + resources: + requests: + cpu: 100m + limits: + memory: 500Mi diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/internal/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/internal/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/internal/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/network/ingress-nginx/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/ks.yaml.j2 new file mode 100644 index 00000000..f7547d35 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/ingress-nginx/ks.yaml.j2 @@ -0,0 +1,63 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app ingress-nginx-certificates + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + dependsOn: + - name: cert-manager-issuers + path: ./kubernetes/apps/network/ingress-nginx/certificates + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: true + interval: 30m + timeout: 5m +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app ingress-nginx-internal + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + dependsOn: + - name: ingress-nginx-certificates + path: ./kubernetes/apps/network/ingress-nginx/internal + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app ingress-nginx-external + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + dependsOn: + - name: ingress-nginx-certificates + path: ./kubernetes/apps/network/ingress-nginx/external + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..982dfd93 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/k8s-gateway/app/helmrelease.yaml.j2 @@ -0,0 +1,37 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: k8s-gateway +spec: + interval: 30m + chart: + spec: + chart: k8s-gateway + version: 2.4.0 + sourceRef: + kind: HelmRepository + name: k8s-gateway + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + fullnameOverride: k8s-gateway + domain: "${SECRET_DOMAIN}" + ttl: 1 + service: + type: LoadBalancer + port: 53 + annotations: + lbipam.cilium.io/ips: "#{ bootstrap_cloudflare.gateway_vip }#" + #% if bgp.enabled %# + externalTrafficPolicy: Local + #% else %# + externalTrafficPolicy: Cluster + #% endif %# + watchedResources: ["Ingress", "Service"] diff --git a/bootstrap/templates/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/k8s-gateway/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 new file mode 100644 index 00000000..c5fcad8a --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/k8s-gateway/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app k8s-gateway + namespace: flux-system +spec: + targetNamespace: network + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/network/k8s-gateway/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/network/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/kustomization.yaml.j2 new file mode 100644 index 00000000..e6f8ddc1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/kustomization.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./namespace.yaml + - ./cloudflared/ks.yaml + - ./echo-server/ks.yaml + - ./external-dns/ks.yaml + - ./ingress-nginx/ks.yaml + - ./k8s-gateway/ks.yaml diff --git a/bootstrap/templates/kubernetes/apps/network/namespace.yaml.j2 b/bootstrap/templates/kubernetes/apps/network/namespace.yaml.j2 new file mode 100644 index 00000000..4d78d7b1 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/network/namespace.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: network + labels: + kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/bootstrap/templates/kubernetes/apps/observability/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/observability/kustomization.yaml.j2 new file mode 100644 index 00000000..b213c83e --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/observability/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./namespace.yaml + - ./prometheus-operator-crds/ks.yaml diff --git a/bootstrap/templates/kubernetes/apps/observability/namespace.yaml.j2 b/bootstrap/templates/kubernetes/apps/observability/namespace.yaml.j2 new file mode 100644 index 00000000..ce3a5bd2 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/observability/namespace.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: observability + labels: + kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..8e18d790 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/app/helmrelease.yaml.j2 @@ -0,0 +1,22 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: prometheus-operator-crds +spec: + interval: 30m + chart: + spec: + chart: prometheus-operator-crds + version: 15.0.0 + sourceRef: + kind: HelmRepository + name: prometheus-community + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 diff --git a/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/ks.yaml.j2 new file mode 100644 index 00000000..8f532a12 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/observability/prometheus-operator-crds/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app prometheus-operator-crds + namespace: flux-system +spec: + targetNamespace: observability + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/observability/prometheus-operator-crds/app + prune: false # never should be deleted + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/apps/openebs-system/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/openebs-system/kustomization.yaml.j2 new file mode 100644 index 00000000..9cd8d4e4 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/openebs-system/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./namespace.yaml + - ./openebs/ks.yaml diff --git a/bootstrap/templates/kubernetes/apps/openebs-system/namespace.yaml.j2 b/bootstrap/templates/kubernetes/apps/openebs-system/namespace.yaml.j2 new file mode 100644 index 00000000..f173c6c9 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/openebs-system/namespace.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: v1 +kind: Namespace +metadata: + name: openebs-system + labels: + kustomize.toolkit.fluxcd.io/prune: disabled diff --git a/bootstrap/templates/kubernetes/apps/openebs-system/openebs/app/helmrelease.yaml.j2 b/bootstrap/templates/kubernetes/apps/openebs-system/openebs/app/helmrelease.yaml.j2 new file mode 100644 index 00000000..8cb7c52e --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/openebs-system/openebs/app/helmrelease.yaml.j2 @@ -0,0 +1,48 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2 +kind: HelmRelease +metadata: + name: openebs +spec: + interval: 30m + chart: + spec: + chart: openebs + version: 4.1.1 + sourceRef: + kind: HelmRepository + name: openebs + namespace: flux-system + install: + remediation: + retries: 3 + upgrade: + cleanupOnFail: true + remediation: + retries: 3 + values: + engines: + local: + lvm: + enabled: false + zfs: + enabled: false + replicated: + mayastor: + enabled: false + openebs-crds: + csi: + volumeSnapshots: + enabled: false + localpv-provisioner: + localpv: + image: + registry: quay.io/ + helperPod: + image: + registry: quay.io/ + hostpathClass: + enabled: true + name: openebs-hostpath + isDefaultClass: false + basePath: /var/openebs/local diff --git a/bootstrap/templates/kubernetes/apps/openebs-system/openebs/app/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/apps/openebs-system/openebs/app/kustomization.yaml.j2 new file mode 100644 index 00000000..5dd7baca --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/openebs-system/openebs/app/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./helmrelease.yaml diff --git a/bootstrap/templates/kubernetes/apps/openebs-system/openebs/ks.yaml.j2 b/bootstrap/templates/kubernetes/apps/openebs-system/openebs/ks.yaml.j2 new file mode 100644 index 00000000..0a650df3 --- /dev/null +++ b/bootstrap/templates/kubernetes/apps/openebs-system/openebs/ks.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: &app openebs + namespace: flux-system +spec: + targetNamespace: openebs-system + commonMetadata: + labels: + app.kubernetes.io/name: *app + path: ./kubernetes/apps/openebs-system/openebs/app + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + wait: false + interval: 30m + timeout: 5m diff --git a/bootstrap/templates/kubernetes/bootstrap/flux/github-deploy-key.sops.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/flux/github-deploy-key.sops.yaml.j2 new file mode 100644 index 00000000..0ef1f6e8 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/flux/github-deploy-key.sops.yaml.j2 @@ -0,0 +1,17 @@ +#% if bootstrap_github_private_key %# +--- +apiVersion: v1 +kind: Secret +metadata: + name: github-deploy-key + namespace: flux-system +stringData: + identity: | + #% filter indent(width=4, first=False) %# + #{ bootstrap_github_private_key }# + #%- endfilter %# + known_hosts: | + github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl + github.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg= + github.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk= +#% endif %# diff --git a/bootstrap/templates/kubernetes/bootstrap/flux/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/flux/kustomization.yaml.j2 new file mode 100644 index 00000000..30f33642 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/flux/kustomization.yaml.j2 @@ -0,0 +1,61 @@ +# IMPORTANT: This file is not tracked by flux and should never be. Its +# purpose is to only install the Flux components and CRDs into your cluster. +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - github.com/fluxcd/flux2/manifests/install?ref=v2.4.0 +patches: + # Remove the default network policies + - patch: |- + $patch: delete + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: not-used + target: + group: networking.k8s.io + kind: NetworkPolicy + # Resources renamed to match those installed by oci://ghcr.io/fluxcd/flux-manifests + - target: + kind: ResourceQuota + name: critical-pods + patch: | + - op: replace + path: /metadata/name + value: critical-pods-flux-system + - target: + kind: ClusterRoleBinding + name: cluster-reconciler + patch: | + - op: replace + path: /metadata/name + value: cluster-reconciler-flux-system + - target: + kind: ClusterRoleBinding + name: crd-controller + patch: | + - op: replace + path: /metadata/name + value: crd-controller-flux-system + - target: + kind: ClusterRole + name: crd-controller + patch: | + - op: replace + path: /metadata/name + value: crd-controller-flux-system + - target: + kind: ClusterRole + name: flux-edit + patch: | + - op: replace + path: /metadata/name + value: flux-edit-flux-system + - target: + kind: ClusterRole + name: flux-view + patch: | + - op: replace + path: /metadata/name + value: flux-view-flux-system diff --git a/bootstrap/templates/kubernetes/bootstrap/helmfile.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/helmfile.yaml.j2 new file mode 100644 index 00000000..78a8b6e7 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/helmfile.yaml.j2 @@ -0,0 +1,59 @@ +--- +helmDefaults: + wait: true + waitForJobs: true + timeout: 600 + recreatePods: true + force: true + +repositories: + - name: cilium + url: https://helm.cilium.io + - name: coredns + url: https://coredns.github.io/helm + - name: postfinance + url: https://postfinance.github.io/kubelet-csr-approver + +releases: + - name: prometheus-operator-crds + namespace: observability + chart: oci://ghcr.io/prometheus-community/charts/prometheus-operator-crds + version: 15.0.0 + - name: cilium + namespace: kube-system + chart: cilium/cilium + version: 1.16.3 + values: + - ../apps/kube-system/cilium/app/helm-values.yaml + needs: + - observability/prometheus-operator-crds + - name: coredns + namespace: kube-system + chart: coredns/coredns + version: 1.36.1 + values: + - ../apps/kube-system/coredns/app/helm-values.yaml + needs: + - observability/prometheus-operator-crds + - kube-system/cilium + - name: kubelet-csr-approver + namespace: kube-system + chart: postfinance/kubelet-csr-approver + version: 1.2.3 + values: + - ../apps/kube-system/kubelet-csr-approver/app/helm-values.yaml + needs: + - observability/prometheus-operator-crds + - kube-system/cilium + - kube-system/coredns + - name: spegel + namespace: kube-system + chart: oci://ghcr.io/spegel-org/helm-charts/spegel + version: v0.0.27 + values: + - ../apps/kube-system/spegel/app/helm-values.yaml + needs: + - observability/prometheus-operator-crds + - kube-system/cilium + - kube-system/coredns + - kube-system/kubelet-csr-approver diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/README.md.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/README.md.j2 new file mode 100644 index 00000000..b9681888 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/README.md.j2 @@ -0,0 +1,15 @@ +# Talos Patching + +This directory contains Kustomization patches that are added to the talhelper configuration file. + + + +## Patch Directories + +Under this `patches` directory, there are several sub-directories that can contain patches that are added to the talhelper configuration file. +Each directory is optional and therefore might not created by default. + +- `global/`: patches that are applied to both the controller and worker configurations +- `controller/`: patches that are applied to the controller configurations +- `worker/`: patches that are applied to the worker configurations +- `${node-hostname}/`: patches that are applied to the node with the specified name diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/api-access.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/api-access.yaml.j2 new file mode 100644 index 00000000..77232844 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/api-access.yaml.j2 @@ -0,0 +1,8 @@ +machine: + features: + kubernetesTalosAPIAccess: + enabled: true + allowedRoles: + - os:admin + allowedKubernetesNamespaces: + - system-upgrade diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/cluster.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/cluster.yaml.j2 new file mode 100644 index 00000000..b4a9685b --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/cluster.yaml.j2 @@ -0,0 +1,25 @@ +cluster: + allowSchedulingOnControlPlanes: true + controllerManager: + extraArgs: + bind-address: 0.0.0.0 + coreDNS: + disabled: true + proxy: + disabled: true + scheduler: + extraArgs: + bind-address: 0.0.0.0 + config: + apiVersion: kubescheduler.config.k8s.io/v1 + kind: KubeSchedulerConfiguration + profiles: + - schedulerName: default-scheduler + pluginConfig: + - name: PodTopologySpread + args: + defaultingType: List + defaultConstraints: + - maxSkew: 1 + topologyKey: kubernetes.io/hostname + whenUnsatisfiable: ScheduleAnyway diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/disable-admission-controller.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/disable-admission-controller.yaml.j2 new file mode 100644 index 00000000..e311789f --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/disable-admission-controller.yaml.j2 @@ -0,0 +1,2 @@ +- op: remove + path: /cluster/apiServer/admissionControl diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/etcd.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/etcd.yaml.j2 new file mode 100644 index 00000000..df35aa5d --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/controller/etcd.yaml.j2 @@ -0,0 +1,6 @@ +cluster: + etcd: + extraArgs: + listen-metrics-urls: http://0.0.0.0:2381 + advertisedSubnets: + - #{ bootstrap_node_network }# diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/cluster-discovery.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/cluster-discovery.yaml.j2 new file mode 100644 index 00000000..ecafec6e --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/cluster-discovery.yaml.j2 @@ -0,0 +1,7 @@ +cluster: + discovery: + registries: + kubernetes: + disabled: false + service: + disabled: true diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/containerd.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/containerd.yaml.j2 new file mode 100644 index 00000000..2952d6b4 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/containerd.yaml.j2 @@ -0,0 +1,12 @@ +machine: + files: + - op: create + path: /etc/cri/conf.d/20-customization.part + content: |- + [plugins."io.containerd.grpc.v1.cri"] + enable_unprivileged_ports = true + enable_unprivileged_icmp = true + [plugins."io.containerd.grpc.v1.cri".containerd] + discard_unpacked_layers = false + [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] + discard_unpacked_layers = false diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/disable-search-domain.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/disable-search-domain.yaml.j2 new file mode 100644 index 00000000..8ba647c4 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/disable-search-domain.yaml.j2 @@ -0,0 +1,3 @@ +machine: + network: + disableSearchDomain: true diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/dns.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/dns.yaml.j2 new file mode 100644 index 00000000..707a03ee --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/dns.yaml.j2 @@ -0,0 +1,6 @@ +machine: + network: + nameservers: + #% for item in bootstrap_dns_servers | default(["1.1.1.1","1.0.0.1"], true) %# + - #{ item }# + #% endfor %# diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/hostdns.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/hostdns.yaml.j2 new file mode 100644 index 00000000..6033ccd2 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/hostdns.yaml.j2 @@ -0,0 +1,6 @@ +machine: + features: + hostDNS: + enabled: true + resolveMemberNames: true + forwardKubeDNSToHost: true # Requires Cilium `bpf.masquerade: false` diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/kubelet.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/kubelet.yaml.j2 new file mode 100644 index 00000000..ee71c280 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/kubelet.yaml.j2 @@ -0,0 +1,7 @@ +machine: + kubelet: + extraArgs: + rotate-server-certificates: true + nodeIP: + validSubnets: + - #{ bootstrap_node_network }# diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/ntp.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/ntp.yaml.j2 new file mode 100644 index 00000000..3b3fad13 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/ntp.yaml.j2 @@ -0,0 +1,7 @@ +machine: + time: + disabled: false + servers: + #% for item in bootstrap_ntp_servers | default(["time.cloudflare.com"], true) %# + - #{ item }# + #% endfor %# diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/openebs-local.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/openebs-local.yaml.j2 new file mode 100644 index 00000000..e4095d17 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/openebs-local.yaml.j2 @@ -0,0 +1,10 @@ +machine: + kubelet: + extraMounts: + - destination: /var/openebs/local + type: bind + source: /var/openebs/local + options: + - bind + - rshared + - rw diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/sysctl.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/sysctl.yaml.j2 new file mode 100644 index 00000000..a7012f3a --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/patches/global/sysctl.yaml.j2 @@ -0,0 +1,7 @@ +machine: + sysctls: + fs.inotify.max_user_watches: "1048576" + fs.inotify.max_user_instances: "8192" + net.core.rmem_max: "7500000" + net.core.wmem_max: "7500000" + vm.nr_hugepages: "1024" diff --git a/bootstrap/templates/kubernetes/bootstrap/talos/talconfig.yaml.j2 b/bootstrap/templates/kubernetes/bootstrap/talos/talconfig.yaml.j2 new file mode 100644 index 00000000..232adb52 --- /dev/null +++ b/bootstrap/templates/kubernetes/bootstrap/talos/talconfig.yaml.j2 @@ -0,0 +1,153 @@ +# yaml-language-server: $schema=https://raw.githubusercontent.com/budimanjojo/talhelper/master/pkg/config/schemas/talconfig.json +--- +# renovate: datasource=docker depName=ghcr.io/siderolabs/installer +talosVersion: v1.8.2 +# renovate: datasource=docker depName=ghcr.io/siderolabs/kubelet +kubernetesVersion: v1.31.2 + +clusterName: "#{ bootstrap_cluster_name | default('home-kubernetes', true) }#" +endpoint: https://#{ bootstrap_controller_vip }#:6443 +clusterPodNets: + - "#{ bootstrap_pod_network.split(',')[0] }#" +clusterSvcNets: + - "#{ bootstrap_service_network.split(',')[0] }#" +additionalApiServerCertSans: &sans + - "#{ bootstrap_controller_vip }#" + - 127.0.0.1 # KubePrism + #% for item in bootstrap_tls_sans %# + - "#{ item }#" + #% endfor %# +additionalMachineCertSans: *sans + +# Disable built-in Flannel to use Cilium +cniConfig: + name: none + +nodes: + #% for item in bootstrap_node_inventory %# + - hostname: "#{ item.name }#" + ipAddress: "#{ item.address }#" + #% if item.disk.startswith('/') %# + installDisk: "#{ item.disk }#" + #% else %# + installDiskSelector: + serial: "#{ item.disk }#" + #% endif %# + #% if bootstrap_secureboot.enabled %# + machineSpec: + secureboot: true + talosImageURL: factory.talos.dev/installer-secureboot/#{ item.schematic_id | default(bootstrap_schematic_id, true) }# + #% else %# + talosImageURL: factory.talos.dev/installer/#{ item.schematic_id | default(bootstrap_schematic_id, true) }# + #% endif %# + controlPlane: #{ (item.controller) | string | lower }# + networkInterfaces: + - deviceSelector: + hardwareAddr: "#{ item.mac_addr | lower }#" + #% if bootstrap_vlan %# + vlans: + - vlanId: #{ bootstrap_vlan }# + addresses: + - "#{ item.address }#/#{ bootstrap_node_network.split('/') | last }#" + mtu: #{ item.mtu | default(1500, true) }# + routes: + - network: 0.0.0.0/0 + #% if bootstrap_node_default_gateway %# + gateway: "#{ bootstrap_node_default_gateway }#" + #% else %# + gateway: "#{ bootstrap_node_network | nthhost(1) }#" + #% endif %# + #% if item.controller %# + vip: + ip: "#{ bootstrap_controller_vip }#" + #% endif %# + #% else %# + dhcp: false + addresses: + - "#{ item.address }#/#{ bootstrap_node_network.split('/') | last }#" + routes: + - network: 0.0.0.0/0 + #% if bootstrap_node_default_gateway %# + gateway: "#{ bootstrap_node_default_gateway }#" + #% else %# + gateway: "#{ bootstrap_node_network | nthhost(1) }#" + #% endif %# + mtu: #{ item.mtu | default(1500, true) }# + #% if item.controller %# + vip: + ip: "#{ bootstrap_controller_vip }#" + #% endif %# + #% endif %# + #% if item.manifests %# + extraManifests: + #% for manifest in item.manifests %# + - #{ manifest }# + #% endfor %# + #% endif %# + #% if item.extension_services %# + extensionServices: + #% for es in item.extension_services %# + - name: #{ es.name }# + configFiles: + #% for cf in es.configFiles %# + - content: |- + #{ cf.content | indent(14, yes) }# + mountPath: #{ cf.mountPath }# + #% endfor %# + #% if es.environment %# + environment: + #% for env in es.environment %# + - #{ env }# + #% endfor %# + #% endif %# + #% endfor %# + #% endif %# + #% for file in talos_patches('%s' % (item.name)) %# + #% if loop.index == 1 %# + patches: + #% endif %# + - "@./patches/#{ item.name }#/#{ file | basename }#" + #% endfor %# + #% endfor %# + +# Global patches +patches: + #% if bootstrap_secureboot.enabled and bootstrap_secureboot.encrypt_disk_with_tpm %# + - # Encrypt system disk with TPM + |- + machine: + systemDiskEncryption: + ephemeral: + provider: luks2 + keys: + - slot: 0 + tpm: {} + state: + provider: luks2 + keys: + - slot: 0 + tpm: {} + #% endif %# + #% for file in talos_patches('global') %# + - "@./patches/global/#{ file | basename }#" + #% endfor %# + +#% for file in talos_patches('controller') %# +#% if loop.index == 1 %# +# Controller patches +controlPlane: + patches: +#% endif %# + - "@./patches/controller/#{ file | basename }#" +#% endfor %# + +#% if (bootstrap_node_inventory | selectattr('controller', 'equalto', False) | list | length) and (talos_patches('worker') | length) %# +#% for file in talos_patches('worker') %# +#% if loop.index == 1 %# +# Worker patches +worker: + patches: +#% endif %# + - "@./patches/worker/#{ file | basename }#" +#% endfor %# +#% endif %# diff --git a/bootstrap/templates/kubernetes/flux/apps.yaml.j2 b/bootstrap/templates/kubernetes/flux/apps.yaml.j2 new file mode 100644 index 00000000..c4ebba99 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/apps.yaml.j2 @@ -0,0 +1,56 @@ +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: cluster-apps + namespace: flux-system +spec: + interval: 30m + path: ./kubernetes/apps + prune: true + sourceRef: + kind: GitRepository + name: home-kubernetes + decryption: + provider: sops + secretRef: + name: sops-age + postBuild: + substituteFrom: + - kind: ConfigMap + name: cluster-settings + - kind: Secret + name: cluster-secrets + - kind: ConfigMap + name: cluster-user-settings + optional: true + - kind: Secret + name: cluster-user-secrets + optional: true + patches: + - patch: |- + apiVersion: kustomize.toolkit.fluxcd.io/v1 + kind: Kustomization + metadata: + name: not-used + spec: + decryption: + provider: sops + secretRef: + name: sops-age + postBuild: + substituteFrom: + - kind: ConfigMap + name: cluster-settings + - kind: Secret + name: cluster-secrets + - kind: ConfigMap + name: cluster-user-settings + optional: true + - kind: Secret + name: cluster-user-secrets + optional: true + target: + group: kustomize.toolkit.fluxcd.io + kind: Kustomization + labelSelector: substitution.flux.home.arpa/disabled notin (true) diff --git a/bootstrap/templates/kubernetes/flux/config/cluster.yaml.j2 b/bootstrap/templates/kubernetes/flux/config/cluster.yaml.j2 new file mode 100644 index 00000000..5c385b7b --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/config/cluster.yaml.j2 @@ -0,0 +1,44 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: GitRepository +metadata: + name: home-kubernetes + namespace: flux-system +spec: + interval: 30m + url: "#{ bootstrap_github_address }#" + #% if bootstrap_github_private_key %# + secretRef: + name: github-deploy-key + #% endif %# + ref: + branch: "#{ bootstrap_github_branch | default('main', true) }#" + ignore: | + # exclude all + /* + # include kubernetes directory + !/kubernetes +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: cluster + namespace: flux-system +spec: + interval: 30m + path: ./kubernetes/flux + prune: true + wait: false + sourceRef: + kind: GitRepository + name: home-kubernetes + decryption: + provider: sops + secretRef: + name: sops-age + postBuild: + substituteFrom: + - kind: ConfigMap + name: cluster-settings + - kind: Secret + name: cluster-secrets diff --git a/bootstrap/templates/kubernetes/flux/config/flux.yaml.j2 b/bootstrap/templates/kubernetes/flux/config/flux.yaml.j2 new file mode 100644 index 00000000..973cbfe5 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/config/flux.yaml.j2 @@ -0,0 +1,86 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1beta2 +kind: OCIRepository +metadata: + name: flux-manifests + namespace: flux-system +spec: + interval: 10m + url: oci://ghcr.io/fluxcd/flux-manifests + ref: + tag: v2.4.0 +--- +apiVersion: kustomize.toolkit.fluxcd.io/v1 +kind: Kustomization +metadata: + name: flux + namespace: flux-system +spec: + interval: 10m + path: ./ + prune: true + wait: true + sourceRef: + kind: OCIRepository + name: flux-manifests + patches: + # Remove the network policies + - patch: | + $patch: delete + apiVersion: networking.k8s.io/v1 + kind: NetworkPolicy + metadata: + name: not-used + target: + group: networking.k8s.io + kind: NetworkPolicy + # Increase the number of reconciliations that can be performed in parallel and bump the resources limits + # https://fluxcd.io/flux/cheatsheets/bootstrap/#increase-the-number-of-workers + - patch: | + - op: add + path: /spec/template/spec/containers/0/args/- + value: --concurrent=8 + - op: add + path: /spec/template/spec/containers/0/args/- + value: --kube-api-qps=500 + - op: add + path: /spec/template/spec/containers/0/args/- + value: --kube-api-burst=1000 + - op: add + path: /spec/template/spec/containers/0/args/- + value: --requeue-dependency=5s + target: + kind: Deployment + name: (kustomize-controller|helm-controller|source-controller) + - patch: | + apiVersion: apps/v1 + kind: Deployment + metadata: + name: not-used + spec: + template: + spec: + containers: + - name: manager + resources: + limits: + cpu: 2000m + memory: 2Gi + target: + kind: Deployment + name: (kustomize-controller|helm-controller|source-controller) + # Enable Helm near OOM detection + # https://fluxcd.io/flux/cheatsheets/bootstrap/#enable-helm-near-oom-detection + - patch: | + - op: add + path: /spec/template/spec/containers/0/args/- + value: --feature-gates=OOMWatch=true + - op: add + path: /spec/template/spec/containers/0/args/- + value: --oom-watch-memory-threshold=95 + - op: add + path: /spec/template/spec/containers/0/args/- + value: --oom-watch-interval=500ms + target: + kind: Deployment + name: helm-controller diff --git a/bootstrap/templates/kubernetes/flux/config/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/flux/config/kustomization.yaml.j2 new file mode 100644 index 00000000..ef231746 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/config/kustomization.yaml.j2 @@ -0,0 +1,6 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./flux.yaml + - ./cluster.yaml diff --git a/bootstrap/templates/kubernetes/flux/repositories/git/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/git/kustomization.yaml.j2 new file mode 100644 index 00000000..fe0f332a --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/git/kustomization.yaml.j2 @@ -0,0 +1,4 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: [] diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/bjw-s.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/bjw-s.yaml.j2 new file mode 100644 index 00000000..a40b5d77 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/bjw-s.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: bjw-s + namespace: flux-system +spec: + type: oci + interval: 5m + url: oci://ghcr.io/bjw-s/helm diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/cilium.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/cilium.yaml.j2 new file mode 100644 index 00000000..3aee3678 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/cilium.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: cilium + namespace: flux-system +spec: + interval: 1h + url: https://helm.cilium.io diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/coredns.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/coredns.yaml.j2 new file mode 100644 index 00000000..3bdbbafb --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/coredns.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: coredns + namespace: flux-system +spec: + interval: 1h + url: https://coredns.github.io/helm diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/external-dns.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/external-dns.yaml.j2 new file mode 100644 index 00000000..b5b66a36 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/external-dns.yaml.j2 @@ -0,0 +1,11 @@ +#% if bootstrap_cloudflare.enabled %# +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: external-dns + namespace: flux-system +spec: + interval: 1h + url: https://kubernetes-sigs.github.io/external-dns +#% endif %# diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/ingress-nginx.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/ingress-nginx.yaml.j2 new file mode 100644 index 00000000..db1ddad3 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/ingress-nginx.yaml.j2 @@ -0,0 +1,11 @@ +#% if bootstrap_cloudflare.enabled %# +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: ingress-nginx + namespace: flux-system +spec: + interval: 1h + url: https://kubernetes.github.io/ingress-nginx +#% endif %# diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/jetstack.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/jetstack.yaml.j2 new file mode 100644 index 00000000..737e06af --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/jetstack.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: jetstack + namespace: flux-system +spec: + interval: 1h + url: https://charts.jetstack.io diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/k8s-gateway.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/k8s-gateway.yaml.j2 new file mode 100644 index 00000000..abfa8c14 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/k8s-gateway.yaml.j2 @@ -0,0 +1,11 @@ +#% if bootstrap_cloudflare.enabled %# +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: k8s-gateway + namespace: flux-system +spec: + interval: 1h + url: https://ori-edge.github.io/k8s_gateway +#% endif %# diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/kustomization.yaml.j2 new file mode 100644 index 00000000..71a5903c --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/kustomization.yaml.j2 @@ -0,0 +1,19 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./bjw-s.yaml + - ./cilium.yaml + - ./coredns.yaml + - ./jetstack.yaml + - ./metrics-server.yaml + - ./openebs.yaml + - ./postfinance.yaml + - ./prometheus-community.yaml + - ./spegel.yaml + - ./stakater.yaml + #% if bootstrap_cloudflare.enabled %# + - ./external-dns.yaml + - ./ingress-nginx.yaml + - ./k8s-gateway.yaml + #% endif %# diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/metrics-server.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/metrics-server.yaml.j2 new file mode 100644 index 00000000..27a44828 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/metrics-server.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: metrics-server + namespace: flux-system +spec: + interval: 1h + url: https://kubernetes-sigs.github.io/metrics-server diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/openebs.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/openebs.yaml.j2 new file mode 100644 index 00000000..4f48013e --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/openebs.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: openebs + namespace: flux-system +spec: + interval: 1h + url: https://openebs.github.io/openebs diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/postfinance.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/postfinance.yaml.j2 new file mode 100644 index 00000000..b14a64d8 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/postfinance.yaml.j2 @@ -0,0 +1,9 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: postfinance + namespace: flux-system +spec: + interval: 1h + url: https://postfinance.github.io/kubelet-csr-approver diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/prometheus-community.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/prometheus-community.yaml.j2 new file mode 100644 index 00000000..318a1a51 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/prometheus-community.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: prometheus-community + namespace: flux-system +spec: + type: oci + interval: 5m + url: oci://ghcr.io/prometheus-community/charts diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/spegel.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/spegel.yaml.j2 new file mode 100644 index 00000000..d9a8b2cd --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/spegel.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: spegel + namespace: flux-system +spec: + type: oci + interval: 5m + url: oci://ghcr.io/spegel-org/helm-charts diff --git a/bootstrap/templates/kubernetes/flux/repositories/helm/stakater.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/helm/stakater.yaml.j2 new file mode 100644 index 00000000..c727f37f --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/helm/stakater.yaml.j2 @@ -0,0 +1,10 @@ +--- +apiVersion: source.toolkit.fluxcd.io/v1 +kind: HelmRepository +metadata: + name: stakater + namespace: flux-system +spec: + type: oci + interval: 5m + url: oci://ghcr.io/stakater/charts diff --git a/bootstrap/templates/kubernetes/flux/repositories/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/kustomization.yaml.j2 new file mode 100644 index 00000000..d158d426 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/kustomization.yaml.j2 @@ -0,0 +1,7 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./git + - ./helm + - ./oci diff --git a/bootstrap/templates/kubernetes/flux/repositories/oci/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/flux/repositories/oci/kustomization.yaml.j2 new file mode 100644 index 00000000..fe0f332a --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/repositories/oci/kustomization.yaml.j2 @@ -0,0 +1,4 @@ +--- +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: [] diff --git a/bootstrap/templates/kubernetes/flux/vars/cluster-secrets.sops.yaml.j2 b/bootstrap/templates/kubernetes/flux/vars/cluster-secrets.sops.yaml.j2 new file mode 100644 index 00000000..549fad34 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/vars/cluster-secrets.sops.yaml.j2 @@ -0,0 +1,14 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: cluster-secrets + namespace: flux-system +stringData: + #% if bootstrap_cloudflare.enabled %# + SECRET_DOMAIN: "#{ bootstrap_cloudflare.domain }#" + SECRET_ACME_EMAIL: "#{ bootstrap_cloudflare.acme.email }#" + SECRET_CLOUDFLARE_TUNNEL_ID: "#{ bootstrap_cloudflare.tunnel.id }#" + #% else %# + SECRET_EXAMPLE: Global secrets for your cluster go in this file, this file is encrypted with sops + #% endif %# diff --git a/bootstrap/templates/kubernetes/flux/vars/cluster-settings.yaml.j2 b/bootstrap/templates/kubernetes/flux/vars/cluster-settings.yaml.j2 new file mode 100644 index 00000000..b64f194e --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/vars/cluster-settings.yaml.j2 @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: cluster-settings + namespace: flux-system +data: + SETTING_EXAMPLE: Global settings for your cluster go in this file, this file is NOT encrypted diff --git a/bootstrap/templates/kubernetes/flux/vars/kustomization.yaml.j2 b/bootstrap/templates/kubernetes/flux/vars/kustomization.yaml.j2 new file mode 100644 index 00000000..8db2fe91 --- /dev/null +++ b/bootstrap/templates/kubernetes/flux/vars/kustomization.yaml.j2 @@ -0,0 +1,5 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./cluster-settings.yaml + - ./cluster-secrets.sops.yaml diff --git a/config.sample.yaml b/config.sample.yaml new file mode 100644 index 00000000..a7beb908 --- /dev/null +++ b/config.sample.yaml @@ -0,0 +1,213 @@ +--- + +# +# 1. (Required) Cluster details - Cluster represents the Kubernetes cluster layer and any additional customizations +# + +# (Optional) Cluster name; affects Cilium and Talos +# DEFAULT: "home-kubernetes" +bootstrap_cluster_name: "" + +# (Required) Generated schematic id from https://factory.talos.dev/ +bootstrap_schematic_id: "" + +# (Required) The CIDR your nodes are on (e.g. 192.168.1.0/24) +bootstrap_node_network: "" + +# (Required) Use only 1, 3 or more ODD number of controller nodes, recommended is 3 +# Worker nodes are optional +bootstrap_node_inventory: [] + # - name: "" # (Required) Name of the node (must match [a-z0-9-\]+) + # address: "" # (Optional) IP address of the node + # controller: true # (Required) Set to true if this is a controller node + # disk: "" # (Required) Device path or serial number of the disk for this node (talosctl disks -n --insecure) + # mac_addr: "" # (Required) MAC address of the NIC for this node (talosctl get links -n --insecure) + # schematic_id: "" # (Optional) Override the 'bootstrap_schematic_id' with a node specific schematic ID from https://factory.talos.dev/ + # mtu: "" # (Optional) MTU for the NIC. DEFAULT: 1500 + # manifests: # (Optional) Additional manifests to include after MachineConfig + # - extra.yaml # Ref: https://www.talos.dev/v1.7/reference/configuration/extensions/extensionserviceconfig/ + # extension_services: # (Optional) Additional talhelper ExtensionServices (supports talenv.sops.yaml envsubst) + # - name: name + # configFiles: + # - content: |- + # ... + # mountPath: ... + # environment: + # - key=value + # ... + +# The DNS servers to use for the cluster nodes. +# DEFAULT: [1.1.1.1, 1.0.0.1] +# If using a local DNS server make sure it meets the following requirements: +# 1. your nodes can reach it +# 2. it is configured to forward requests to a public DNS server +bootstrap_dns_servers: [] + +# The NTP servers to use for the cluster nodes. +# DEFAULT: [time.cloudflare.com] +bootstrap_ntp_servers: [] + +# (Required) The pod CIDR for the cluster, this must NOT overlap with any +# existing networks and is usually a /16 (64K IPs). +# If you want to use IPv6 check the advanced flags below and be aware of +# https://github.com/onedr0p/cluster-template/issues/1148 +bootstrap_pod_network: "10.69.0.0/16" + +# (Required) The service CIDR for the cluster, this must NOT overlap with any +# existing networks and is usually a /16 (64K IPs). +# If you want to use IPv6 check the advanced flags below and be aware of +# https://github.com/onedr0p/cluster-template/issues/1148 +bootstrap_service_network: "10.96.0.0/16" + +# (Required) The IP address of the Kube API, choose an available IP in +# your nodes host network that is NOT being used. This is announced over L2. +bootstrap_controller_vip: "" + +# (Optional) Add additional SANs to the Kube API cert, this is useful +# if you want to call the Kube API by hostname rather than IP +bootstrap_tls_sans: [] + +# (Optional) The default gateway for the nodes +# DEFAULT: .1 derrived from bootstrap_node_network (e.g. 192.168.1.1) +bootstrap_node_default_gateway: "" + +# (Optional) Add vlan tag to network master device, leave blank if you tag ports on your switch instead +# Ref: https://www.talos.dev/latest/advanced/advanced-networking/#vlans +bootstrap_vlan: "" + +# (Required) Age Public Key (e.g. age1...) +# 1. Generate a new key with the following command: +# > task bootstrap:age-keygen +# 2. Copy the PUBLIC key and paste it below +bootstrap_age_pubkey: "" + +# (Optional) Use cilium BGP control plane when L2 announcements won't traverse VLAN network segments. +# Needs a BGP capable router setup with the node IPs as peers. +# Ref: https://docs.cilium.io/en/latest/network/bgp-control-plane/ +bootstrap_bgp: + enabled: false + # (Optional) If using multiple BGP peers add them here. + # DEFAULT: .1 derrived from host_network: ['x.x.x.1'] + peers: [] + # (Required) Set the BGP Autonomous System Number for the router(s) and nodes. + # If these match, iBGP will be used. If not, eBGP will be used. + peer_asn: "" # Router(s) AS + local_asn: "" # Node(s) AS + peer_port: 179 # BGP Port. DEFAULT: 179 + # (Required) The advertised CIDR for the cluster, this must NOT overlap with any + # existing networks and is usually a /16 (64K IPs). + # If you want to use IPv6 check the advanced flags below + advertised_network: "" + +# (Optional) Secureboot and TPM-based disk encryption +bootstrap_secureboot: + # (Optional) Enable secureboot on UEFI systems. Not supported on x86 platforms in BIOS mode. + # Ref: https://www.talos.dev/latest/talos-guides/install/bare-metal-platforms/secureboot + enabled: false + # (Optional) Enable TPM-based disk encryption. Requires TPM 2.0 + # Ref: https://www.talos.dev/v1.6/talos-guides/install/bare-metal-platforms/secureboot/#disk-encryption-with-tpm + encrypt_disk_with_tpm: false + +# (Optional) Change Cilium load balancer mode +# DEFAULT: "dsr" +# Ref: https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/ +bootstrap_loadbalancer_mode: "" + +# +# 2. (Required) Flux details - Flux is used to manage the cluster configuration. +# + +# (Required) GitHub repository URL +# For a public repo use the 'https://' URL (e.g. "https://github.com/onedr0p/cluster-template.git") +# For a private repo use the 'ssh://' URL (e.g. "ssh://git@github.com/onedr0p/cluster-template.git") +# If using a private repo make sure to following the instructions with the 'bootstrap_github_private_key' option below. +bootstrap_github_address: "" + +# (Required) GitHub repository branch +bootstrap_github_branch: "main" + +# (Required) Token for GitHub push-based sync +# 1. Generate a new token with the following command: +# > openssl rand -hex 16 +# 2. Copy the token and paste it below +bootstrap_github_webhook_token: "" + +# (Optional) Private key for Flux to access the GitHub repository +# 1. Generate a new key with the following command: +# > ssh-keygen -t ecdsa -b 521 -C "github-deploy-key" -f github-deploy.key -q -P "" +# 2. Make sure to paste public key from "github-deploy.key.pub" into +# the deploy keys section of your GitHub repository settings. +# 3. Uncomment and paste the private key below +# 4. Optionally set your repository on GitHub to private +# bootstrap_github_private_key: | +# -----BEGIN OPENSSH PRIVATE KEY----- +# ... +# -----END OPENSSH PRIVATE KEY----- + +# +# 3. (Optional) Cloudflare details - Cloudflare is used for DNS, TLS certificates and tunneling. +# + +bootstrap_cloudflare: + # (Required) Disable to manually setup and use a different DNS provider - setting this + # to false will not deploy a network namespace or the workloads contained within. + enabled: true + # (Required) Cloudflare Domain + domain: "" + # (Required) Cloudflare API Token (NOT API Key) + # 1. Head over to Cloudflare and create a API Token by going to + # https://dash.cloudflare.com/profile/api-tokens + # 2. Under the `API Tokens` section click the blue `Create Token` button. + # 3. Click the blue `Use template` button for the `Edit zone DNS` template. + # 4. Name your token something like `home-kubernetes` + # 5. Under `Permissions`, click `+ Add More` and add each permission below: + # `Zone - DNS - Edit` + # `Account - Cloudflare Tunnel - Read` + # 6. Limit the permissions to a specific account and zone resources. + # 7. Click the blue `Continue to Summary` button and then the blue `Create Token` button. + # 8. Copy the token and paste it below. + token: "" + # (Required) Optionals for Cloudflare Acme + acme: + # (Required) Any email you want to be associated with the ACME account (used for TLS certs via letsencrypt.org) + email: "" + # (Required) Use the ACME production server when requesting the wildcard certificate. + # By default the ACME staging server is used. This is to prevent being rate-limited. + # Update this option to `true` when you have verified the staging certificate + # works and then re-run `task configure` and push your changes to Github. + production: false + # (Required) Provide LAN access to the cluster ingresses for internal ingress classes + # The Load balancer IP for internal ingress, choose an available IP + # in your nodes host network that is NOT being used. This is announced over L2. + ingress_vip: "" + # (Required) Gateway is used for providing DNS to your cluster on LAN + # The Load balancer IP for k8s_gateway, choose an available IP + # in your nodes host network that is NOT being used. This is announced over L2. + gateway_vip: "" + # (Required) Options for Cloudflare Tunnel + # 1. Authenticate cloudflared to your domain with the following command: + # > cloudflared tunnel login + # 2. Create the tunnel with the following command: + # > cloudflared tunnel create k8s + tunnel: + # (Required) Get the Cloudflared Tunnel ID with the following command: + # > jq -r .TunnelID ~/.cloudflared/*.json + id: "" + # (Required) Get the Cloudflare Account ID with the following command: + # > jq -r .AccountTag ~/.cloudflared/*.json + account_id: "" + # (Required) Get the Cloudflared Tunnel Secret with the following command: + # > jq -r .TunnelSecret ~/.cloudflared/*.json + secret: "" + # (Required) Provide WAN access to the cluster ingresses for external ingress classes + # The Load balancer IP for external ingress, choose an available IP + # in your nodes host network that is NOT being used. This is announced over L2. + ingress_vip: "" + +# (Optional) Feature gates are used to enable experimental features +# bootstrap_feature_gates: +# # Enable Dual Stack IPv4 first +# # IMPORTANT: I am looking for people to help maintain IPv6 support since I cannot test it. +# # Ref: https://github.com/onedr0p/cluster-template/discussions/1510 +# # IMPORTANT: Cilium does not currently support IPv6 L2 announcements. +# dual_stack_ipv4_first: false diff --git a/makejinja.toml b/makejinja.toml new file mode 100644 index 00000000..52845a37 --- /dev/null +++ b/makejinja.toml @@ -0,0 +1,18 @@ +[makejinja] +inputs = ["./bootstrap/overrides","./bootstrap/templates"] +output = "./" +exclude_patterns = [".mjfilter.py", "*.partial.yaml.j2"] +data = ["./config.yaml"] +import_paths = ["./bootstrap/scripts"] +loaders = ["plugin:Plugin"] +jinja_suffix = ".j2" +force = true +undefined = "chainable" + +[makejinja.delimiter] +block_start = "#%" +block_end = "%#" +comment_start = "#|" +comment_end = "#|" +variable_start = "#{" +variable_end = "}#" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..011ff37e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +cloudflare==3.1.0 +email-validator==2.2.0 +makejinja==2.6.2 +netaddr==1.3.0 diff --git a/scripts/kubeconform.sh b/scripts/kubeconform.sh new file mode 100755 index 00000000..20ba1ad4 --- /dev/null +++ b/scripts/kubeconform.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +set -o errexit +set -o pipefail + +KUBERNETES_DIR=$1 + +[[ -z "${KUBERNETES_DIR}" ]] && echo "Kubernetes location not specified" && exit 1 + +kustomize_args=("--load-restrictor=LoadRestrictionsNone") +kustomize_config="kustomization.yaml" +kubeconform_args=( + "-strict" + "-ignore-missing-schemas" + "-skip" + "Secret" + "-schema-location" + "default" + "-schema-location" + "https://kubernetes-schemas.pages.dev/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" + "-verbose" +) + +echo "=== Validating standalone manifests in ${KUBERNETES_DIR}/flux ===" +find "${KUBERNETES_DIR}/flux" -maxdepth 1 -type f -name '*.yaml' -print0 | while IFS= read -r -d $'\0' file; +do + kubeconform "${kubeconform_args[@]}" "${file}" + if [[ ${PIPESTATUS[0]} != 0 ]]; then + exit 1 + fi +done + +echo "=== Validating kustomizations in ${KUBERNETES_DIR}/flux ===" +find "${KUBERNETES_DIR}/flux" -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file; +do + echo "=== Validating kustomizations in ${file/%$kustomize_config} ===" + kustomize build "${file/%$kustomize_config}" "${kustomize_args[@]}" | kubeconform "${kubeconform_args[@]}" + if [[ ${PIPESTATUS[0]} != 0 ]]; then + exit 1 + fi +done + +echo "=== Validating kustomizations in ${KUBERNETES_DIR}/apps ===" +find "${KUBERNETES_DIR}/apps" -type f -name $kustomize_config -print0 | while IFS= read -r -d $'\0' file; +do + echo "=== Validating kustomizations in ${file/%$kustomize_config} ===" + kustomize build "${file/%$kustomize_config}" "${kustomize_args[@]}" | kubeconform "${kubeconform_args[@]}" + if [[ ${PIPESTATUS[0]} != 0 ]]; then + exit 1 + fi +done