├── .github └── workflows │ └── pull-request.yaml ├── .gitignore ├── LICENSE ├── README.md ├── argo-cd ├── argo-git-repository.yaml └── argo-project.yaml ├── go.mod ├── go.sum ├── hosts-sample.yml ├── images ├── grafana.png ├── longhorn.png └── turing.jpg ├── kubernetes-services ├── Chart.yaml ├── additions │ ├── cert-manager │ │ ├── issuer-letsencrypt-staging.yaml │ │ └── issuer-self-signed.yaml │ ├── cloudflare-tunnel │ │ └── deployment.yaml │ ├── http-echo-server │ │ ├── deployment.yaml │ │ ├── istio-pod-monitor.yaml │ │ └── virtualservice.yaml │ ├── istio-gateway │ │ ├── certificate-self-signed.yaml │ │ ├── gateway-metrics-service.yaml │ │ ├── gateway-servicemonitor.yaml │ │ ├── istio-ingress.yaml │ │ └── non-tls-gateway.yaml │ ├── k3s-upgrade-controller │ │ ├── kustomization.yaml │ │ └── upgrade-plan.yaml │ ├── longhorn │ │ └── volume-snapshot-class.yaml │ └── prometheus │ │ ├── loki-datasource.yaml │ │ └── longhorn-example-dashboard.yaml └── templates │ ├── argo-cd.yaml │ ├── argo-rollouts.yaml │ ├── cert-manager.yaml │ ├── cloudflare-tunnel.yaml │ ├── http-echo-server.yaml │ ├── istio-gateway.yaml │ ├── istio.yaml │ ├── k3s-system-upgrade-controller.yaml │ ├── kargo.yaml │ ├── kured.yaml │ ├── longhorn.yaml │ ├── prometheus.yaml │ ├── sealed-secrets.yaml │ └── snapshot-controller.yaml ├── renovate.json ├── roles ├── argo-cd │ └── tasks │ │ └── main.yml ├── cloudflare │ └── tasks │ │ └── main.yml ├── k3s-agent │ └── tasks │ │ └── main.yml ├── k3s-post │ └── tasks │ │ └── main.yml ├── k3s-preparation │ └── tasks │ │ └── main.yml ├── k3s-server │ └── tasks │ │ └── main.yml ├── storage │ └── tasks │ │ └── main.yml └── ubuntu │ └── tasks │ └── main.yml ├── test ├── argo_cd_test.go ├── argo_rollouts_test.go ├── cert_manager_test.go ├── cloudflare_tunnel_test.go ├── http_echo_server_test.go ├── istio_test.go ├── k3s_system_upgrade_controller_test.go ├── kargo_test.go ├── kind.yaml ├── kured_test.go ├── main_test.go ├── pkg │ ├── api │ │ └── api.go │ ├── argo │ │ ├── application.go │ │ └── argo.go │ ├── git │ │ └── git.go │ ├── helm │ │ └── helm.go │ ├── manifest │ │ └── manifest.go │ └── test │ │ └── test.go ├── prometheus_test.go ├── sealed_secrets_test.go └── storage_test.go └── turingpi.yml /.github/workflows/pull-request.yaml: -------------------------------------------------------------------------------- 1 | name: K8S E2E Tests 2 | on: 3 | merge_group: 4 | pull_request: 5 | paths: 6 | - kubernetes-services/** 7 | - test/** 8 | - go.mod 9 | - renovate.json 10 | - .github/** 11 | 12 | concurrency: 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | analyze-changes: 18 | runs-on: ubuntu-latest 19 | outputs: 20 | apps_any_changed: ${{ steps.changed-files.outputs.apps_any_changed }} 21 | apps_all_changed_files: ${{ steps.changed-files.outputs.apps_all_changed_files }} 22 | istio_any_changed: ${{ steps.changed-files.outputs.istio_any_changed }} 23 | istio_all_changed_files: ${{ steps.changed-files.outputs.istio_all_changed_files }} 24 | storage_any_changed: ${{ steps.changed-files.outputs.storage_any_changed }} 25 | storage_all_changed_files: ${{ steps.changed-files.outputs.storage_all_changed_files }} 26 | k3s_upgrade_any_changed: ${{ steps.changed-files.outputs.k3s_upgrade_any_changed }} 27 | k3s_upgrade_all_changed_files: ${{ steps.changed-files.outputs.k3s_upgrade_all_changed_files }} 28 | cloudflare_any_changed: ${{ steps.changed-files.outputs.cloudflare_any_changed }} 29 | cloudflare_all_changed_files: ${{ steps.changed-files.outputs.cloudflare_all_changed_files }} 30 | http_echo_server_any_changed: ${{ steps.changed-files.outputs.http_echo_server_any_changed }} 31 | http_echo_server_all_changed_files: ${{ steps.changed-files.outputs.http_echo_server_all_changed_files }} 32 | go_test_any_changed: ${{ steps.changed-files.outputs.go_test_any_changed }} 33 | go_test_all_changed_files: ${{ steps.changed-files.outputs.go_test_all_changed_files }} 34 | go_helper_any_changed: ${{ steps.changed-files.outputs.go_helper_any_changed }} 35 | steps: 36 | - uses: tj-actions/changed-files@v46.0.5 37 | id: changed-files 38 | with: 39 | files_yaml: | 40 | apps: 41 | - kubernetes-services/templates/** 42 | - '!kubernetes-services/templates/istio**.yaml' 43 | - '!kubernetes-services/templates/snapshot-controller.yaml' 44 | - '!kubernetes-services/templates/longhorn.yaml' 45 | istio: 46 | - kubernetes-services/templates/istio** 47 | storage: 48 | - kubernetes-services/templates/snapshot-controller.yaml 49 | - kubernetes-services/templates/longhorn.yaml 50 | k3s_upgrade: 51 | - 'kubernetes-services/additions/k3s-upgrade-controller/*' 52 | cloudflare: 53 | - 'kubernetes-services/additions/cloudflare-tunnel/*' 54 | http_echo_server: 55 | - 'kubernetes-services/additions/http-echo-server/*' 56 | go_test: 57 | - test/**_test.go 58 | go_helper: 59 | - test/pkg/**/**.go 60 | - go.mod 61 | - .github/** 62 | 63 | test-kubernetes-manifest: 64 | needs: analyze-changes 65 | runs-on: ubuntu-latest 66 | steps: 67 | - uses: actions/checkout@v4 68 | - uses: yokawasa/action-setup-kube-tools@v0.11.2 69 | with: 70 | kubectl: '1.29.0' 71 | helm: '3.15.1' 72 | - uses: actions/setup-go@v5 73 | with: 74 | go-version: '1.24' 75 | cache-dependency-path: go.sum 76 | 77 | - name: Test Common Helm Chart 78 | if: ${{ needs.analyze-changes.outputs.apps_any_changed == 'true' }} 79 | env: 80 | HELM_CHARTS_CHANGED_FILES: ${{ needs.analyze-changes.outputs.apps_all_changed_files }} 81 | run: | 82 | echo "List all helm charts that have changed" 83 | for chart in ${HELM_CHARTS_CHANGED_FILES}; do 84 | echo "${chart} was changed" 85 | 86 | test="test/$(basename ${chart} | sed -E 's/-([a-z])/_\1/g' | sed 's/.yaml/_test.go/')" 87 | if [ ! -f ${test} ]; then 88 | echo "Test coverage ${test} not found. bye!" 89 | exit 2 90 | fi 91 | 92 | go test -v ./test -run $(basename ${chart} .yaml | sed 's/.*/Test\u&/' | sed -E 's/-([a-z])/\U\1/g') 93 | done 94 | 95 | - name: Test Istio Helm Chart 96 | if: ${{ needs.analyze-changes.outputs.istio_any_changed == 'true' && needs.analyze-changes.outputs.go_helper_any_changed != 'true' }} 97 | run: | 98 | go test -v ./test -run TestIstio 99 | 100 | - name: Test Longhorn and Snapshot Controller Charts 101 | if: ${{ needs.analyze-changes.outputs.storage_any_changed == 'true' && needs.analyze-changes.outputs.go_helper_any_changed != 'true' }} 102 | run: | 103 | go test -v ./test -run TestStorage 104 | 105 | - name: Test K3S System Upgrade Controller 106 | if: ${{ needs.analyze-changes.outputs.k3s_upgrade_any_changed == 'true' && needs.analyze-changes.outputs.go_helper_any_changed != 'true' }} 107 | run: | 108 | go test -v ./test -run TestK3sSystemUpgradeController 109 | 110 | - name: Test Cloudflare Tunnel 111 | if: ${{ needs.analyze-changes.outputs.cloudflare_any_changed == 'true' && needs.analyze-changes.outputs.go_helper_any_changed != 'true' }} 112 | run: | 113 | go test -v ./test -run TestCloudflareTunnel 114 | 115 | - name: Http Echo Server 116 | if: ${{ needs.analyze-changes.outputs.http_echo_server == 'true' && needs.analyze-changes.outputs.go_helper_any_changed != 'true' }} 117 | run: | 118 | go test -v ./test -run TestHttpEchoServer 119 | 120 | - name: Golang Tests 121 | if: ${{ needs.analyze-changes.outputs.go_test_any_changed == 'true' && needs.analyze-changes.outputs.go_helper_any_changed != 'true' }} 122 | env: 123 | GO_TEST_CHANGED_FILES: ${{ needs.analyze-changes.outputs.go_test_all_changed_files }} 124 | run: | 125 | for test in ${GO_TEST_CHANGED_FILES}; do 126 | echo "${test} was changed" 127 | go test -v ./test -run $(basename ${test} _test.go | sed 's/.*/Test\u&/' | sed -E 's/_([a-z])/\U\1/g') 128 | done 129 | 130 | - name: Golang Test Helper 131 | if: ${{ needs.analyze-changes.outputs.go_helper_any_changed == 'true' }} 132 | run: | 133 | go test -v ./test 134 | 135 | - name: Upload KinD logs 136 | if: always() 137 | uses: actions/upload-artifact@v4.6.2 138 | with: 139 | name: kind 140 | path: 'test/kind-logs' 141 | retention-days: 3 142 | 143 | create-argocd-diff: 144 | needs: analyze-changes 145 | if: ${{ needs.analyze-changes.outputs.apps_any_changed == 'true' }} 146 | runs-on: ubuntu-latest 147 | permissions: 148 | contents: read 149 | pull-requests: write 150 | 151 | steps: 152 | - uses: actions/checkout@v4 153 | with: 154 | path: pull-request 155 | 156 | - uses: actions/checkout@v4 157 | with: 158 | ref: main 159 | path: main 160 | 161 | - name: Generate Diff 162 | run: | 163 | docker run \ 164 | --network=host \ 165 | -v /var/run/docker.sock:/var/run/docker.sock \ 166 | -v $(pwd)/main:/base-branch \ 167 | -v $(pwd)/pull-request:/target-branch \ 168 | -v $(pwd)/output:/output \ 169 | -e TARGET_BRANCH=${{ github.head_ref }} \ 170 | -e REPO=${{ github.repository }} \ 171 | dagandersen/argocd-diff-preview:v0.0.25 172 | 173 | - name: Post diff as comment 174 | run: | 175 | gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} --body-file output/diff.md --edit-last || \ 176 | gh pr comment ${{ github.event.number }} --repo ${{ github.repository }} --body-file output/diff.md 177 | env: 178 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/* 2 | .vscode/* 3 | kubeconfig 4 | hosts.yml 5 | vendor/* 6 | tls.crt 7 | tls.key 8 | public-key-cert.pem 9 | **/kind-logs -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Turing Pi v2 K3S Cluster with RK 1 Compute Modules 2 | This project uses an Ansible playbook to install a K3S 4 node cluster on the RK1 computer modules from the [Turing Pi Project](https://turingpi.com/). 3 | This involves installing 3 control-planes and 1 agent node. 4 | 5 | After the successful K3S installation, Argo CD is installed and linked to this repository using the [App of Apps pattern (cluster bootstrapping)](https://argo-cd.readthedocs.io/en/stable/operator-manual/cluster-bootstrapping/). 6 | This simplifies the handling of the various Helm charts, their configurations and updates. 7 | 8 | Includes tools and operators 9 | - [Cloudflared](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) 10 | - [Argo CD](https://github.com/argoproj/argo-helm/tree/main/charts/argo-cd) 11 | - [Prometheus Kube Stack](https://github.com/prometheus-community/helm-charts/tree/main/charts/kube-prometheus-stack) 12 | - [Longhorn CSI](https://github.com/longhorn/longhorn) 13 | - [Istio & Gateway](https://github.com/istio/istio/tree/master/manifests/charts) 14 | - [http echo server](https://github.com/mendhak/docker-http-https-echo) 15 | - [K3S System Upgrade Controller](https://github.com/rancher/system-upgrade-controller/tree/master/manifests) 16 | - [Jaeger Tracing](https://github.com/jaegertracing/helm-charts) 17 | - [Kiali](https://github.com/kiali/helm-charts/tree/master/kiali-server) 18 | - [Bitnami Sealed Secrets](https://github.com/bitnami/charts/tree/main/bitnami/sealed-secrets) 19 | - [Kured](https://github.com/kubereboot/kured) 20 | - [Cert Manager](https://github.com/cert-manager/cert-manager) 21 | - [Argo Rollouts](https://github.com/argoproj/argo-rollouts) 22 | - [Kargo.io](https://github.com/akuity/kargo) 23 | 24 | # Install 25 | ## Prerequisites 26 | * The RK1 Ubuntu server image must be flashed to the modules as described in the [Turing Pi Docs - Flashing OS](https://docs.turingpi.com/docs/turing-rk1-flashing-os). 27 | * Passwordless authentication via SSH key must be set up on all nodes. 28 | 29 | ## Setup K3S on RK1 30 | Copy the hosts-sample.yml and adjust the settings like ip addresses and NVME of the RK1 Modules. If you want to expose 31 | your cluster on the Internet, you can set up a cloudflare tunnel add the data to `hosts.yaml`. 32 | Afterward run the Ansible playbook. 33 | ```bash 34 | $ cloudflared login # (optional) 35 | $ cloudflared tunnel create my-tunnel # (optional) 36 | $ cloudflared tunnel token my-tunnel # (optional) 37 | $ cp hosts-sample.yml hosts.yml 38 | # adjust the ip addresses, nvme and cloudflare settings 39 | # and run the playbook 40 | $ ansible-playbook -i hosts.yml turingpi.yml 41 | ``` 42 | After successful provisioning, all nodes should be available. 43 | ``` 44 | $ ssh ubuntu@ip-of-rk1-module 45 | $ kubectl get nodes -o wide 46 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME 47 | turing-01 Ready control-plane,etcd,master 23h v1.28.6+k3s2 192.168.100.231 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 48 | turing-02 Ready control-plane,etcd,master 23h v1.28.6+k3s2 192.168.100.232 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 49 | turing-03 Ready control-plane,etcd,master 23h v1.28.6+k3s2 192.168.100.233 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 50 | turing-04 Ready 23h v1.28.6+k3s2 192.168.100.234 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 51 | ``` 52 | 53 | ### Cloudflare (optional) 54 | If the Cloudflare tunnel has been set up, two ingress routes have been created to your hostname.These ingress routes 55 | resolve to the Istio gateway in the cluster. 56 | ``` 57 | - hostname: example.net 58 | service: https://istio-gateway.istio-gateway.svc.cluster.local:443 59 | originRequest: 60 | noTLSVerify: true 61 | - hostname: *.example.net 62 | service: https://istio-gateway.istio-gateway.svc.cluster.local:443 63 | originRequest: 64 | noTLSVerify: true 65 | ``` 66 | In order for these routes to be resolved, two additional records must be added to your Cloudflare DNS 67 | in your Cloudflare Account. 68 | 69 | | TYPE | Name | Content | 70 | |-------|------|---------------------------------| 71 | | CNAME | @ | uuid-of-tunnel.cfargotunnel.com | 72 | | CNAME | * | uuid-of-tunnel.cfargotunnel.com | 73 | 74 | If a service is to be exposed on the Internet, a [VirtualService](https://istio.io/latest/docs/reference/config/networking/virtual-service/) 75 | can be created and the host name added to the host section. Example ArgoCD 76 | ``` 77 | apiVersion: networking.istio.io/v1beta1 78 | kind: VirtualService 79 | metadata: 80 | name: argo-cd-virtualservice 81 | namespace: argo-cd 82 | spec: 83 | hosts: 84 | - "argocd.example.net" 85 | gateways: 86 | - istio-gateway/non-tls-gateway 87 | http: 88 | - match: 89 | - uri: 90 | prefix: / 91 | route: 92 | - destination: 93 | host: argo-cd-argocd-server 94 | port: 95 | number: 80 96 | ``` 97 | 98 | 99 | ## Accessing the Cluster 100 | 101 | ### kubectl 102 | So that we do not always have to connect to the RK1 modules to execute `kubectl` commands, we can copy the `kubeconfig` to the local machine. 103 | Afterwards we have to change the cluster IP address in the `kubeconfig`. 104 | 105 | ``` 106 | scp ubuntu@ip-of-rk1-module:~/.kube/config .kubeconfig 107 | ``` 108 | 109 | Open the `kubeconfig` with a text editor of your choice and replace the IP address of the cluster. 110 | ``` 111 | - cluster: 112 | certificate-authority-data: ... snip ... 113 | server: https://127.0.0.1:6443 # <- change this address to one of the control-plane nodes addresses from your hosts.yaml. e.g. https://192.168.100.231:6443 114 | name: default 115 | ``` 116 | 117 | Test the connection to the cluster from your local machine 118 | ``` 119 | $ kubectl --kubeconfig ./kubeconfig get nodes -o wide 120 | NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME 121 | turing-01 Ready control-plane,etcd,master 23h v1.28.6+k3s2 192.168.100.231 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 122 | turing-02 Ready control-plane,etcd,master 23h v1.28.6+k3s2 192.168.100.232 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 123 | turing-03 Ready control-plane,etcd,master 23h v1.28.6+k3s2 192.168.100.233 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 124 | turing-04 Ready 23h v1.28.6+k3s2 192.168.100.234 Ubuntu 22.04.4 LTS 5.10.160-rockchip containerd://1.7.11-k3s2 125 | ``` 126 | To avoid having to specify the path to your Kubeconfig each time using the `--kubeconfig` option, it can also be copied 127 | to the location `~/.kube/config`. By default, `kubectl` searches for a configuration there, if none has been specified. 128 | 129 | ### Kiali Sign in 130 | Kiali Sign in tokens have a short lifetime. Therefore, a new token must be created each time the Kiali Dashboard is needed 131 | ``` 132 | $ kubectl --namespace istio-system create token kiali 133 | 134 | eyJhbGciOiJSUzI1NiIsImtVbt_-7...snip...-aWjp3925QexVkfQuPP-qQ94AtoZGS2LQwvz7KcKKEVLUtfbKZeFie3B6EQO4iQ 135 | ``` 136 | Create a port forward to the service and access Kiali via Browser at http://localhost:8099 137 | ``` 138 | $ kubectl --kubeconfig ./kubeconfig --namespace istio-system port-forward svc/kiali 8099:20001 139 | ``` 140 | 141 | ### Argo CD 142 | Argo CD creates a random password each time it is installed. Before you can connect to the web/cli interface, this must be determined. 143 | ``` 144 | $ kubectl --kubeconfig ./kubeconfig --namespace argo-cd get secrets argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d 145 | blcElfzg7sQ-i8e7 # <- admin password 146 | ``` 147 | Create a port forward to the service and access Argo CD via Browser at http://localhost:8099 148 | ``` 149 | $ kubectl --kubeconfig ./kubeconfig --namespace argo-cd port-forward svc/argo-cd-argocd-server 8099:80 150 | ``` 151 | 152 | You should now be able to sign in to the Admin UI with the user name `admin` 153 | and the password determined from the secret. 154 | 155 | If you prefer to use the terminal, you can log in with the argocd cli tool using the following command 156 | ``` 157 | $ argocd login :443 --grpc-web-root-path argocd 158 | ``` 159 | 160 | ### Kargo.io 161 | At the moment there is no possibility to create a random password in the Kargo.io Helm Chart. Therefore, `kargo` is 162 | created as admin password during installation. 163 | 164 | Create a port forward to the service and access the dashboard at https://localhost:8080 165 | ``` 166 | $ kubectl -n kargo port-forward svc/kargo-api 8080:443 167 | ``` 168 | 169 | ### Grafana 170 | In the Prometheus Kube stack, the credentials have not been changed and are default. You can log in to the dashboard 171 | using the username `admin` and password `prom-operator`. 172 | 173 | Create a port forward to the service and access Grafana via Browser at http://localhost:8099 174 | ``` 175 | $ kubectl --kubeconfig ./kubeconfig --namespace monitoring port-forward svc/prometheus-grafana 8099:80 176 | ``` 177 | 178 | ![title](images/grafana.png) 179 | 180 | ### Longhorn CSI 181 | The Longhorn Web UI is not exposed at all via Ingress or Istio Gateway. If access to the dashboard is required, a port 182 | forward to localhost should be established via kubectl. The dashboard can then be accessed at http://localhost:8080 183 | ``` 184 | $ kubectl --kubeconfig ./kubeconfig -n longhorn port-forward svc/longhorn-frontend 8080:80 185 | ``` 186 | ![longhorn](images/longhorn.png) 187 | 188 | ### Bitnami Sealed Secrets 189 | Sealed Secrets are used to create encrypted Kubernetes Secrets. Before such a secret can be created, the 190 | public certificate must be exported from the controller 191 | ``` 192 | $ kubeseal --kubeconfig ./kubeconfig --namespace kube-system --controller-name sealed-secrets --fetch-cert > public-key-cert.pem 193 | ``` 194 | After the public certificate has been successfully obtained, Kubernetes Secrets can be encrypted. 195 | 196 | #### Create a secret 197 | ``` 198 | echo " 199 | apiVersion: v1 200 | kind: Secret 201 | metadata: 202 | name: mysecret 203 | type: Opaque 204 | stringData: 205 | password: 'v3rySekur'" > secret.yaml 206 | ``` 207 | #### Encrypt it 208 | ``` 209 | $ kubeseal --kubeconfig ./kubeconfig --scope cluster-wide --namespace kube-system --controller-name sealed-secrets --format=yaml --cert=public-key-cert.pem < secret.yaml > secret-sealed.yaml 210 | ``` 211 | #### expected output 212 | ``` 213 | cat secret-sealed.yaml 214 | --- 215 | apiVersion: bitnami.com/v1alpha1 216 | kind: SealedSecret 217 | metadata: 218 | annotations: 219 | sealedsecrets.bitnami.com/cluster-wide: "true" 220 | creationTimestamp: null 221 | name: mysecret 222 | spec: 223 | encryptedData: 224 | password: AgAiB0sFG+kBfqvR5NWfmsqbHrt2uvZMeGx3DlopOWHJjeZ3WhukBNGbAb3IdBISFXdxiprnudBk1dvP3DPKdCcvqisq8kfE2+NzNOMn40/J9JtSopIJBLF3rK3cVUhXDL2bXvDEwChKsLQyi0gOw7bqObSU3t86O1a2/XK8+iyTnJh/p6oKo6rtGNTMOUOVxYoGwfcvV+yoXEqovO2MWfgi2N9SEPIwzPmDiDXo+Tbfue2EQY4qSAyK8UoddzjMec07EDod3tLfJBT+lJ8BhAHJQJvYIC7UD6izUAi4rvZ/iLaDvt7DsPyhGVD3IT3aH48zkrg7uDxJVeVMYQyJvxLTdcQPfMYUB0eKKlL0MZioGU5jM0aoTLVFh2iZDF4d65bTEszG+NfF4lGq3pjAtA2zlJTFmdUXfoLy1fWqvFPo4TvdjEK2uBXWr2TjyvGMONOZjzoJ+GfGytLVVn4+ZIJofKDmQxogy8Tcdv5HvEahJwoy5pVRrHH8qdCFFk+hVLCdDUxP784XiQcCtCqR1A9Rb9oZhtG5CIRYqxmgEqo/aamSIwCPPjFEoEUaCyojmBGU7CTL3mjFBkEYHNu8vP37xAPCcV/X+EAVN4PpPgQwAlkGIUGv1Y9/XC5qERVaYrcBHquMHKYCYHrKeYuAlYrG8qpDG7gvXTIYjWpvZW76EpeCImouzbKP4yAdXqyh730ZBhYOgaBJfkg= 225 | template: 226 | metadata: 227 | annotations: 228 | sealedsecrets.bitnami.com/cluster-wide: "true" 229 | creationTimestamp: null 230 | name: mysecret 231 | type: Opaque 232 | 233 | ``` 234 | #### Bring your own certificates 235 | It is also possible to create your own certificates and use them to encrypt secrets, the procedure is [explained here](https://github.com/bitnami-labs/sealed-secrets/blob/main/docs/bring-your-own-certificates.md). 236 | 237 | --- 238 | 239 | ![title](images/turing.jpg) 240 | -------------------------------------------------------------------------------- /argo-cd/argo-git-repository.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: kubernetes-services 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: argo-cd 12 | project: kubernetes 13 | source: 14 | path: ./kubernetes-services 15 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 16 | targetRevision: main 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | retry: 22 | limit: 3 23 | backoff: 24 | duration: 5s 25 | factor: 2 26 | maxDuration: 3m -------------------------------------------------------------------------------- /argo-cd/argo-project.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: AppProject 3 | metadata: 4 | name: kubernetes 5 | namespace: argo-cd 6 | # Finalizer that ensures that project is not deleted until it is not referenced by any application 7 | finalizers: 8 | - resources-finalizer.argocd.argoproj.io 9 | spec: 10 | # Project description 11 | description: Additonal services for the Turring Pi v2 Cluster 12 | 13 | # Allow manifests to deploy from any Git repos 14 | sourceRepos: 15 | - '*' 16 | 17 | # Allow manifests to deploy to any Namespace in this cluster 18 | destinations: 19 | - namespace: '*' 20 | server: https://kubernetes.default.svc 21 | 22 | # Allow all cluster-scoped resources to being created 23 | clusterResourceWhitelist: 24 | - group: '*' 25 | kind: '*' 26 | 27 | # Enables namespace orphaned resource monitoring. 28 | orphanedResources: 29 | warn: false 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module test 2 | 3 | go 1.24.0 4 | 5 | toolchain go1.24.4 6 | 7 | require ( 8 | github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 9 | github.com/stretchr/testify v1.10.0 10 | k8s.io/api v0.33.1 11 | k8s.io/apimachinery v0.33.1 12 | k8s.io/client-go v0.33.1 13 | sigs.k8s.io/e2e-framework v0.6.0 14 | sigs.k8s.io/kustomize/api v0.19.0 15 | sigs.k8s.io/kustomize/kyaml v0.19.0 16 | sigs.k8s.io/yaml v1.4.0 17 | ) 18 | 19 | require ( 20 | github.com/beorn7/perks v1.0.1 // indirect 21 | github.com/blang/semver/v4 v4.0.0 // indirect 22 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 23 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 24 | github.com/emicklei/go-restful/v3 v3.12.2 // indirect 25 | github.com/evanphx/json-patch/v5 v5.9.11 // indirect 26 | github.com/fxamacker/cbor/v2 v2.8.0 // indirect 27 | github.com/go-errors/errors v1.5.1 // indirect 28 | github.com/go-logr/logr v1.4.3 // indirect 29 | github.com/go-openapi/jsonpointer v0.21.1 // indirect 30 | github.com/go-openapi/jsonreference v0.21.0 // indirect 31 | github.com/go-openapi/swag v0.23.1 // indirect 32 | github.com/gogo/protobuf v1.3.2 // indirect 33 | github.com/google/gnostic-models v0.6.9 // indirect 34 | github.com/google/go-cmp v0.7.0 // indirect 35 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect 36 | github.com/google/uuid v1.6.0 // indirect 37 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect 38 | github.com/josharian/intern v1.0.0 // indirect 39 | github.com/json-iterator/go v1.1.12 // indirect 40 | github.com/mailru/easyjson v0.9.0 // indirect 41 | github.com/moby/spdystream v0.5.0 // indirect 42 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 43 | github.com/modern-go/reflect2 v1.0.2 // indirect 44 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect 45 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 46 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect 47 | github.com/pkg/errors v0.9.1 // indirect 48 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 49 | github.com/prometheus/client_golang v1.22.0 // indirect 50 | github.com/prometheus/client_model v0.6.2 // indirect 51 | github.com/prometheus/common v0.64.0 // indirect 52 | github.com/prometheus/procfs v0.16.1 // indirect 53 | github.com/spf13/pflag v1.0.6 // indirect 54 | github.com/vladimirvivien/gexe v0.5.0 // indirect 55 | github.com/x448/float16 v0.8.4 // indirect 56 | github.com/xlab/treeprint v1.2.0 // indirect 57 | go.opentelemetry.io/otel v1.36.0 // indirect 58 | go.opentelemetry.io/otel/trace v1.36.0 // indirect 59 | golang.org/x/net v0.41.0 // indirect 60 | golang.org/x/oauth2 v0.30.0 // indirect 61 | golang.org/x/sys v0.33.0 // indirect 62 | golang.org/x/term v0.32.0 // indirect 63 | golang.org/x/text v0.26.0 // indirect 64 | golang.org/x/time v0.11.0 // indirect 65 | google.golang.org/protobuf v1.36.6 // indirect 66 | gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect 67 | gopkg.in/inf.v0 v0.9.1 // indirect 68 | gopkg.in/yaml.v3 v3.0.1 // indirect 69 | k8s.io/component-base v0.33.1 // indirect 70 | k8s.io/klog/v2 v2.130.1 // indirect 71 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect 72 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect 73 | sigs.k8s.io/controller-runtime v0.21.0 // indirect 74 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect 75 | sigs.k8s.io/randfill v1.0.0 // indirect 76 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect 77 | ) 78 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= 2 | github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= 3 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 4 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 5 | github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= 6 | github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= 7 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 8 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= 14 | github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= 15 | github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= 16 | github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= 17 | github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= 18 | github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= 19 | github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= 20 | github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= 21 | github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= 22 | github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 23 | github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= 24 | github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= 25 | github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= 26 | github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= 27 | github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= 28 | github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= 29 | github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= 30 | github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= 31 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 32 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 33 | github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= 34 | github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= 35 | github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= 36 | github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= 37 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 38 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 39 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 40 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 41 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= 42 | github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 43 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= 44 | github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= 45 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 46 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 47 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= 48 | github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= 49 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 50 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 51 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 52 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 53 | github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= 54 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 55 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 56 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 57 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 58 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 59 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 60 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 61 | github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0 h1:Q3jQ1NkFqv5o+F8dMmHd8SfEmlcwNeo1immFApntEwE= 62 | github.com/kubernetes-csi/external-snapshotter/client/v8 v8.2.0/go.mod h1:E3vdYxHj2C2q6qo8/Da4g7P+IcwqRZyy3gJBzYybV9Y= 63 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 64 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 65 | github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= 66 | github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= 67 | github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= 68 | github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= 69 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 70 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 71 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 72 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 73 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 74 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= 75 | github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= 76 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 77 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 78 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= 79 | github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= 80 | github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= 81 | github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= 82 | github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= 83 | github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= 84 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 85 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 86 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 87 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 88 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 89 | github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= 90 | github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= 91 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 92 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 93 | github.com/prometheus/common v0.64.0 h1:pdZeA+g617P7oGv1CzdTzyeShxAGrTBsolKNOLQPGO4= 94 | github.com/prometheus/common v0.64.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= 95 | github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= 96 | github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= 97 | github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= 98 | github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= 99 | github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= 100 | github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 101 | github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= 102 | github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 103 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 104 | github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= 105 | github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= 106 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 107 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 108 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 109 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 110 | github.com/vladimirvivien/gexe v0.5.0 h1:AWBVaYnrTsGYBktXvcO0DfWPeSiZxn6mnQ5nvL+A1/A= 111 | github.com/vladimirvivien/gexe v0.5.0/go.mod h1:3gjgTqE2c0VyHnU5UOIwk7gyNzZDGulPb/DJPgcw64E= 112 | github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= 113 | github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= 114 | github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= 115 | github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= 116 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 117 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 118 | go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= 119 | go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= 120 | go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w= 121 | go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA= 122 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 123 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 124 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 125 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 126 | go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= 127 | go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= 128 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 129 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 130 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 131 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 132 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 133 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 134 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 135 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 136 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= 137 | golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= 138 | golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= 139 | golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= 140 | golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= 141 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 142 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 143 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 144 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 145 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 146 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 147 | golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= 148 | golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 149 | golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= 150 | golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 151 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 152 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 153 | golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= 154 | golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= 155 | golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= 156 | golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= 157 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 158 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 159 | golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 160 | golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 161 | golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc= 162 | golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= 163 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 164 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 165 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 166 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 167 | google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= 168 | google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 169 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 170 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 171 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 172 | gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= 173 | gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= 174 | gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= 175 | gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= 176 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 177 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 178 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 179 | k8s.io/api v0.33.1 h1:tA6Cf3bHnLIrUK4IqEgb2v++/GYUtqiu9sRVk3iBXyw= 180 | k8s.io/api v0.33.1/go.mod h1:87esjTn9DRSRTD4fWMXamiXxJhpOIREjWOSjsW1kEHw= 181 | k8s.io/apiextensions-apiserver v0.33.0 h1:d2qpYL7Mngbsc1taA4IjJPRJ9ilnsXIrndH+r9IimOs= 182 | k8s.io/apiextensions-apiserver v0.33.0/go.mod h1:VeJ8u9dEEN+tbETo+lFkwaaZPg6uFKLGj5vyNEwwSzc= 183 | k8s.io/apimachinery v0.33.1 h1:mzqXWV8tW9Rw4VeW9rEkqvnxj59k1ezDUl20tFK/oM4= 184 | k8s.io/apimachinery v0.33.1/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= 185 | k8s.io/client-go v0.33.1 h1:ZZV/Ks2g92cyxWkRRnfUDsnhNn28eFpt26aGc8KbXF4= 186 | k8s.io/client-go v0.33.1/go.mod h1:JAsUrl1ArO7uRVFWfcj6kOomSlCv+JpvIsp6usAGefA= 187 | k8s.io/component-base v0.33.1 h1:EoJ0xA+wr77T+G8p6T3l4efT2oNwbqBVKR71E0tBIaI= 188 | k8s.io/component-base v0.33.1/go.mod h1:guT/w/6piyPfTgq7gfvgetyXMIh10zuXA6cRRm3rDuY= 189 | k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= 190 | k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= 191 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= 192 | k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= 193 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= 194 | k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= 195 | sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= 196 | sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= 197 | sigs.k8s.io/e2e-framework v0.6.0 h1:p7hFzHnLKO7eNsWGI2AbC1Mo2IYxidg49BiT4njxkrM= 198 | sigs.k8s.io/e2e-framework v0.6.0/go.mod h1:IREnCHnKgRCioLRmNi0hxSJ1kJ+aAdjEKK/gokcZu4k= 199 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= 200 | sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= 201 | sigs.k8s.io/kustomize/api v0.19.0 h1:F+2HB2mU1MSiR9Hp1NEgoU2q9ItNOaBJl0I4Dlus5SQ= 202 | sigs.k8s.io/kustomize/api v0.19.0/go.mod h1:/BbwnivGVcBh1r+8m3tH1VNxJmHSk1PzP5fkP6lbL1o= 203 | sigs.k8s.io/kustomize/kyaml v0.19.0 h1:RFge5qsO1uHhwJsu3ipV7RNolC7Uozc0jUBC/61XSlA= 204 | sigs.k8s.io/kustomize/kyaml v0.19.0/go.mod h1:FeKD5jEOH+FbZPpqUghBP8mrLjJ3+zD3/rf9NNu1cwY= 205 | sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 206 | sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= 207 | sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= 208 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= 209 | sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= 210 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= 211 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= 212 | -------------------------------------------------------------------------------- /hosts-sample.yml: -------------------------------------------------------------------------------- 1 | all: 2 | children: 3 | server: 4 | hosts: 5 | 192.168.100.231: 6 | 192.168.100.232: 7 | 192.168.100.233: 8 | agent: 9 | hosts: 10 | 192.168.100.234: 11 | 12 | vars: 13 | ansible_user: ubuntu 14 | hostname: turing 15 | # If all 4 NVME storage banks are used, 16 | # Ubuntu can be installed directly on the NVME storage and booted from there. 17 | #storage: 18 | # block_device: /dev/nvme0n1 19 | 20 | # if you want to expose your homelab cluster on the internet 21 | # and have a Cloudflare account, you can set up a tunnel here 22 | #cloudflare: 23 | # # create a cloudflare tunnel 24 | # # cloudflared tunnel login 25 | # # cloudflared tunnel create 26 | # # cloudflared tunnel token 27 | # tunnel_name: 28 | # tunnel_token: 29 | # hostname: 30 | 31 | k3s: 32 | # create a random token with openssl 33 | # openssl rand -hex 64 34 | token: 7a397a68cff88a10e6251fe11047fee1f5c98af7f3a69b2a0e9dafc55577ce6e33686e6b098610b5480d335c3a1521296d0b92b01bc3a303b687ccca480de93f 35 | endpoint: "{{ groups['server'][0] }}" 36 | extra_server_args: "" 37 | extra_agent_args: "" -------------------------------------------------------------------------------- /images/grafana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/procinger/turing-pi-v2-cluster/38349ca5fe114e3ea58acf92a7b8b60d0f7800b0/images/grafana.png -------------------------------------------------------------------------------- /images/longhorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/procinger/turing-pi-v2-cluster/38349ca5fe114e3ea58acf92a7b8b60d0f7800b0/images/longhorn.png -------------------------------------------------------------------------------- /images/turing.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/procinger/turing-pi-v2-cluster/38349ca5fe114e3ea58acf92a7b8b60d0f7800b0/images/turing.jpg -------------------------------------------------------------------------------- /kubernetes-services/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v2 2 | name: kubernetes 3 | version: 1.0.0 -------------------------------------------------------------------------------- /kubernetes-services/additions/cert-manager/issuer-letsencrypt-staging.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: issuer-letsencrypt-staging 5 | annotations: 6 | "helm.sh/hook": "post-install" 7 | argocd.argoproj.io/sync-wave: "100" 8 | spec: 9 | acme: 10 | privateKeySecretRef: 11 | name: letsencrypt-staging 12 | server: https://acme-staging-v02.api.letsencrypt.org/directory 13 | solvers: 14 | - http01: 15 | ingress: 16 | ingressClassName: istio 17 | -------------------------------------------------------------------------------- /kubernetes-services/additions/cert-manager/issuer-self-signed.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: ClusterIssuer 3 | metadata: 4 | name: issuer-self-signed 5 | annotations: 6 | "helm.sh/hook": "post-install" 7 | argocd.argoproj.io/sync-wave: "100" 8 | spec: 9 | selfSigned: {} 10 | -------------------------------------------------------------------------------- /kubernetes-services/additions/cloudflare-tunnel/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: cloudflare-tunnel 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: cloudflare-tunnel 10 | template: 11 | metadata: 12 | labels: 13 | app: cloudflare-tunnel 14 | spec: 15 | securityContext: 16 | runAsNonRoot: true 17 | runAsUser: 65532 18 | initContainers: 19 | - name: config-exits 20 | image: alpine:3.22.0@sha256:8a1f59ffb675680d47db6337b49d22281a139e9d709335b492be023728e11715 21 | command: 22 | - /bin/ash 23 | - -c 24 | - | 25 | if [ "${TUNNEL_TOKEN}" == "" ]; then 26 | echo "cloudflare token not found. we will hang here forever" 27 | sleep infinity 28 | fi 29 | env: 30 | - name: TUNNEL_TOKEN 31 | valueFrom: 32 | secretKeyRef: 33 | key: tunnel-token 34 | name: cloudflare 35 | optional: true 36 | containers: 37 | - name: tunnel 38 | securityContext: 39 | allowPrivilegeEscalation: false 40 | capabilities: 41 | drop: 42 | - ALL 43 | readOnlyRootFilesystem: true 44 | image: cloudflare/cloudflared:2025.5.0 45 | imagePullPolicy: IfNotPresent 46 | args: 47 | - tunnel 48 | - --config 49 | - /etc/cloudflared/config/config.yaml 50 | - run 51 | env: 52 | - name: TUNNEL_TOKEN 53 | valueFrom: 54 | secretKeyRef: 55 | key: tunnel-token 56 | name: cloudflare 57 | optional: true 58 | livenessProbe: 59 | httpGet: 60 | path: /ready 61 | port: 2000 62 | failureThreshold: 1 63 | initialDelaySeconds: 10 64 | periodSeconds: 10 65 | volumeMounts: 66 | - name: config 67 | mountPath: /etc/cloudflared/config 68 | readOnly: true 69 | volumes: 70 | - name: config 71 | configMap: 72 | name: cloudflare 73 | optional: true 74 | items: 75 | - key: config.yaml 76 | path: config.yaml 77 | -------------------------------------------------------------------------------- /kubernetes-services/additions/http-echo-server/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: http-echo-server 5 | namespace: http-echo-server 6 | labels: 7 | app: http-echo-server 8 | spec: 9 | replicas: 1 10 | selector: 11 | matchLabels: 12 | app: http-echo-server 13 | template: 14 | metadata: 15 | labels: 16 | app: http-echo-server 17 | spec: 18 | containers: 19 | - image: mendhak/http-https-echo:37 20 | name: http-echo-server 21 | ports: 22 | - containerPort: 8443 23 | protocol: TCP 24 | - containerPort: 8080 25 | protocol: TCP 26 | livenessProbe: 27 | timeoutSeconds: 10 28 | periodSeconds: 10 29 | failureThreshold: 10 30 | httpGet: 31 | path: / 32 | port: 8080 33 | restartPolicy: Always 34 | --- 35 | apiVersion: v1 36 | kind: Service 37 | metadata: 38 | name: http-echo-server 39 | namespace: http-echo-server 40 | labels: 41 | infra: http-echo-server 42 | app: http-echo-server 43 | spec: 44 | ports: 45 | - name: tcp-tls 46 | port: 443 47 | targetPort: 8443 48 | - name: tcp-non-tls 49 | port: 80 50 | targetPort: 8080 51 | selector: 52 | app: http-echo-server -------------------------------------------------------------------------------- /kubernetes-services/additions/http-echo-server/istio-pod-monitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: PodMonitor 3 | metadata: 4 | name: istio-pods 5 | spec: 6 | namespaceSelector: 7 | matchNames: 8 | - http-echo-server 9 | selector: 10 | matchLabels: 11 | istio: monitor 12 | podMetricsEndpoints: 13 | - port: http-envoy-prom 14 | path: stats/prometheus -------------------------------------------------------------------------------- /kubernetes-services/additions/http-echo-server/virtualservice.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: VirtualService 3 | metadata: 4 | name: http-echo-server 5 | spec: 6 | hosts: 7 | - "*" 8 | gateways: 9 | - istio-gateway/non-tls-gateway 10 | http: 11 | - match: 12 | - uri: 13 | prefix: / 14 | route: 15 | - destination: 16 | host: http-echo-server 17 | port: 18 | number: 80 19 | -------------------------------------------------------------------------------- /kubernetes-services/additions/istio-gateway/certificate-self-signed.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: cert-manager.io/v1 2 | kind: Certificate 3 | metadata: 4 | name: tls-self-signed 5 | spec: 6 | isCA: true 7 | subject: 8 | organizations: 9 | - Turing Pi 2 Homelab 10 | duration: 2160h # 90d 11 | renewBefore: 360h # 15d 12 | commonName: self-signed-ca 13 | secretName: tls-self-signed 14 | privateKey: 15 | algorithm: ECDSA 16 | size: 256 17 | issuerRef: 18 | name: issuer-self-signed 19 | kind: ClusterIssuer 20 | group: cert-manager.io 21 | -------------------------------------------------------------------------------- /kubernetes-services/additions/istio-gateway/gateway-metrics-service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: gateway-metrics 5 | labels: 6 | app: gateway 7 | spec: 8 | ports: 9 | - name: metrics 10 | port: 15090 11 | selector: 12 | app: istio-gateway 13 | istio: gateway -------------------------------------------------------------------------------- /kubernetes-services/additions/istio-gateway/gateway-servicemonitor.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: monitoring.coreos.com/v1 2 | kind: ServiceMonitor 3 | metadata: 4 | name: gateway 5 | spec: 6 | namespaceSelector: 7 | matchNames: 8 | - istio-gateway 9 | selector: 10 | matchLabels: 11 | app: gateway 12 | endpoints: 13 | - port: metrics 14 | path: /stats/prometheus -------------------------------------------------------------------------------- /kubernetes-services/additions/istio-gateway/istio-ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: IngressClass 3 | metadata: 4 | annotations: 5 | ingressclass.kubernetes.io/is-default-class: "true" 6 | name: istio 7 | spec: 8 | controller: istio.io/ingress-controller 9 | -------------------------------------------------------------------------------- /kubernetes-services/additions/istio-gateway/non-tls-gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1beta1 2 | kind: Gateway 3 | metadata: 4 | name: non-tls-gateway 5 | spec: 6 | selector: 7 | istio: gateway 8 | servers: 9 | - port: 10 | name: http 11 | number: 80 12 | protocol: HTTP 13 | hosts: 14 | - "*" 15 | tls: 16 | httpsRedirect: true 17 | - port: 18 | name: https 19 | number: 443 20 | protocol: HTTPS 21 | hosts: 22 | - "*" 23 | tls: 24 | mode: SIMPLE 25 | credentialName: tls-self-signed 26 | -------------------------------------------------------------------------------- /kubernetes-services/additions/k3s-upgrade-controller/kustomization.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: kustomize.config.k8s.io/v1beta1 2 | kind: Kustomization 3 | 4 | resources: 5 | # renovate: datasource=github-releases depName=rancher/system-upgrade-controller 6 | - https://github.com/rancher/system-upgrade-controller/releases/download/v0.15.2/crd.yaml 7 | - https://github.com/rancher/system-upgrade-controller?ref=v0.15.2 8 | - upgrade-plan.yaml 9 | -------------------------------------------------------------------------------- /kubernetes-services/additions/k3s-upgrade-controller/upgrade-plan.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: upgrade.cattle.io/v1 2 | kind: Plan 3 | metadata: 4 | name: k3s-upgrade 5 | namespace: system-upgrade 6 | annotations: 7 | argocd.argoproj.io/sync-wave: "100" 8 | argocd.argoproj.io/sync-options: SkipDryRunOnMissingResource=true 9 | spec: 10 | concurrency: 1 11 | cordon: true 12 | nodeSelector: 13 | matchExpressions: 14 | - key: beta.kubernetes.io/instance-type 15 | operator: In 16 | values: 17 | - 'k3s' 18 | serviceAccountName: system-upgrade 19 | upgrade: 20 | image: rancher/k3s-upgrade 21 | channel: https://update.k3s.io/v1-release/channels/stable 22 | -------------------------------------------------------------------------------- /kubernetes-services/additions/longhorn/volume-snapshot-class.yaml: -------------------------------------------------------------------------------- 1 | kind: VolumeSnapshotClass 2 | apiVersion: snapshot.storage.k8s.io/v1 3 | metadata: 4 | name: longhorn 5 | annotations: 6 | snapshot.storage.kubernetes.io/is-default-class: 'true' 7 | driver: driver.longhorn.io 8 | deletionPolicy: Delete 9 | parameters: 10 | type: snap -------------------------------------------------------------------------------- /kubernetes-services/additions/prometheus/loki-datasource.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ConfigMap 3 | metadata: 4 | name: loki-datasource 5 | labels: 6 | grafana_datasource: "1" 7 | data: 8 | loki-stack-datasource.yaml: |- 9 | apiVersion: 1 10 | datasources: 11 | - name: Loki 12 | type: loki 13 | access: proxy 14 | url: http://prometheus-loki:3100 15 | version: 1 16 | isDefault: false 17 | uuid: loki-ds 18 | jsonData: {} 19 | -------------------------------------------------------------------------------- /kubernetes-services/templates/argo-cd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: argo-cd 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: argo-cd 12 | project: default 13 | sources: 14 | - chart: argo-cd 15 | repoURL: https://argoproj.github.io/argo-helm 16 | targetRevision: 8.0.15 17 | helm: 18 | values: | 19 | redis-ha: 20 | enabled: false 21 | controller: 22 | replicas: "1" 23 | server: 24 | replicas: "1" 25 | repoServer: 26 | replicas: "1" 27 | applicationSet: 28 | replicaCount: "1" 29 | commitServer: 30 | enabled: true 31 | configs: 32 | params: 33 | "server.insecure": "true" 34 | "hydrator.enabled": "true" 35 | cm: 36 | "exec.enabled": "true" 37 | "timeout.reconciliation": "120s" 38 | "resource.customizations.ignoreDifferences.apiextensions.k8s.io_CustomResourceDefinition": | 39 | jsonPointers: 40 | - /spec/preserveUnknownFields 41 | dex: 42 | enabled: false 43 | syncPolicy: 44 | automated: 45 | prune: true 46 | selfHeal: true 47 | -------------------------------------------------------------------------------- /kubernetes-services/templates/argo-rollouts.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: argo-rollouts 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: argo-rollouts 12 | project: kubernetes 13 | source: 14 | chart: argo-rollouts 15 | repoURL: https://argoproj.github.io/argo-helm 16 | targetRevision: 2.39.5 17 | helm: 18 | values: | 19 | dashboard: 20 | enabled: true 21 | syncPolicy: 22 | automated: 23 | prune: true 24 | selfHeal: true 25 | syncOptions: 26 | - CreateNamespace=true -------------------------------------------------------------------------------- /kubernetes-services/templates/cert-manager.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: cert-manager 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: cert-manager 12 | project: kubernetes 13 | sources: 14 | - repoURL: https://charts.jetstack.io 15 | chart: cert-manager 16 | targetRevision: v1.17.2 17 | helm: 18 | values: | 19 | prometheus: 20 | enabled: true 21 | installCRDs: true 22 | # https://cert-manager.io/v1.9-docs/usage/gateway/ 23 | extraArgs: 24 | - --feature-gates=ExperimentalGatewayAPISupport=true 25 | - path: ./kubernetes-services/additions/cert-manager 26 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 27 | targetRevision: main 28 | syncPolicy: 29 | automated: 30 | prune: true 31 | selfHeal: true 32 | syncOptions: 33 | - CreateNamespace=true 34 | -------------------------------------------------------------------------------- /kubernetes-services/templates/cloudflare-tunnel.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: cloudflare-tunnel 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: cloudflare 12 | project: kubernetes 13 | sources: 14 | - path: ./kubernetes-services/additions/cloudflare-tunnel 15 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 16 | targetRevision: main 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=true 23 | -------------------------------------------------------------------------------- /kubernetes-services/templates/http-echo-server.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: http-echo-server 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: http-echo-server 12 | project: kubernetes 13 | sources: 14 | - path: ./kubernetes-services/additions/http-echo-server 15 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 16 | targetRevision: main 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=true 23 | retry: 24 | limit: 3 25 | backoff: 26 | duration: 5s 27 | factor: 2 28 | maxDuration: 3m 29 | managedNamespaceMetadata: 30 | labels: 31 | istio-injection: enabled -------------------------------------------------------------------------------- /kubernetes-services/templates/istio-gateway.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: istio-gateway 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: istio-gateway 12 | project: kubernetes 13 | sources: 14 | - repoURL: https://istio-release.storage.googleapis.com/charts 15 | chart: gateway 16 | targetRevision: 1.26.1 17 | helm: 18 | values: | 19 | platform: "" 20 | - path: ./config/crd/standard 21 | repoURL: https://github.com/kubernetes-sigs/gateway-api.git 22 | targetRevision: v1.3.0 23 | - path: ./kubernetes-services/additions/istio-gateway 24 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 25 | targetRevision: main 26 | syncPolicy: 27 | automated: 28 | prune: true 29 | selfHeal: true 30 | syncOptions: 31 | - CreateNamespace=true 32 | - RespectIgnoreDifferences=true 33 | -------------------------------------------------------------------------------- /kubernetes-services/templates/istio.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: istio-system 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: istio-system 12 | project: kubernetes 13 | ignoreDifferences: 14 | - kind: ValidatingWebhookConfiguration 15 | group: admissionregistration.k8s.io 16 | jsonPointers: 17 | - /webhooks 18 | sources: 19 | - repoURL: https://istio-release.storage.googleapis.com/charts 20 | chart: base 21 | targetRevision: 1.26.1 22 | - repoURL: https://istio-release.storage.googleapis.com/charts 23 | chart: istiod 24 | helm: 25 | values: | 26 | pilot: 27 | env: 28 | PILOT_ENABLE_ALPHA_GATEWAY_API: "true" 29 | K8S_INGRESS_NS: "istio-gateway" 30 | autoscaleEnabled: false 31 | replicaCount: 2 32 | global: 33 | proxy: 34 | tracer: "zipkin" 35 | meshConfig: 36 | ingressService: istio-gateway 37 | ingressSelector: gateway 38 | defaultConfig: 39 | tracing: 40 | zipkin: 41 | address: "istio-system-jaeger-collector.istio-system:9411" 42 | targetRevision: 1.26.1 43 | - repoURL: https://kiali.org/helm-charts 44 | chart: kiali-server 45 | targetRevision: 2.10.0 46 | helm: 47 | values: | 48 | clustering: 49 | autodetect_secrets: 50 | enabled: false 51 | kiali_feature_flags: 52 | validations: 53 | ignore: ["KIA1106"] 54 | skip_wildcard_gateway_hosts: true 55 | external_services: 56 | prometheus: 57 | url: "http://prometheus-kube-prometheus-prometheus.monitoring:9090/" 58 | grafana: 59 | enabled: true 60 | in_cluster_url: "http://prometheus-grafana.monitoring:80" 61 | tracing: 62 | in_cluster_url: "http://istio-system-jaeger-query:16685/jaeger" 63 | use_grpc: true 64 | istio: 65 | root_namespace: istio-system 66 | component_status: 67 | enabled: true 68 | components: 69 | - app_label: istiod 70 | is_core: true 71 | - app_label: istio-gateway 72 | is_core: true 73 | is_proxy: true 74 | namespace: istio-gateway 75 | - repoURL: https://jaegertracing.github.io/helm-charts 76 | chart: jaeger 77 | targetRevision: 3.4.1 78 | helm: 79 | values: | 80 | query: 81 | enabled: false 82 | provisionDataStore: 83 | cassandra: false 84 | allInOne: 85 | enabled: true 86 | storage: 87 | type: memory 88 | agent: 89 | enabled: false 90 | collector: 91 | enabled: false 92 | syncPolicy: 93 | automated: 94 | prune: true 95 | selfHeal: true 96 | syncOptions: 97 | - CreateNamespace=true 98 | - RespectIgnoreDifferences=true 99 | -------------------------------------------------------------------------------- /kubernetes-services/templates/k3s-system-upgrade-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: system-upgrade-controller 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: system-upgrade 12 | project: kubernetes 13 | sources: 14 | - path: ./kubernetes-services/additions/k3s-upgrade-controller 15 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 16 | targetRevision: main 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=false 23 | retry: 24 | limit: 3 25 | backoff: 26 | duration: 5s 27 | factor: 2 28 | maxDuration: 3m -------------------------------------------------------------------------------- /kubernetes-services/templates/kargo.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: kargo 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: kargo 12 | project: kubernetes 13 | sources: 14 | - chart: kargo 15 | repoURL: ghcr.io/akuity/kargo-charts 16 | targetRevision: 1.5.1 17 | helm: 18 | values: | 19 | api: 20 | # hardcoded login credentials 21 | # in production systems should use oidc! 22 | adminAccount: 23 | passwordHash: "$2a$12$/fHRdnXaUYBicfR0BsKh/.el6l4O/o.fEeGI7yyOjchEIfYj5Mh.K" 24 | tokenSigningKey: AbAugOWkStfbwczR8wQooceM3 25 | syncPolicy: 26 | automated: 27 | prune: true 28 | selfHeal: true 29 | syncOptions: 30 | - CreateNamespace=true -------------------------------------------------------------------------------- /kubernetes-services/templates/kured.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: kured 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: kured 12 | project: kubernetes 13 | source: 14 | chart: kured 15 | repoURL: https://kubereboot.github.io/charts 16 | targetRevision: 5.6.1 17 | helm: 18 | values: | 19 | resources: 20 | requests: 21 | memory: "32Mi" 22 | cpu: "100m" 23 | limits: 24 | memory: "64Mi" 25 | cpu: "250m" 26 | syncPolicy: 27 | automated: 28 | prune: true 29 | selfHeal: true 30 | syncOptions: 31 | - CreateNamespace=true 32 | -------------------------------------------------------------------------------- /kubernetes-services/templates/longhorn.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: longhorn 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: longhorn 12 | project: kubernetes 13 | sources: 14 | - chart: longhorn 15 | repoURL: https://charts.longhorn.io/ 16 | targetRevision: 1.9.0 17 | helm: 18 | values: | 19 | preUpgradeChecker: 20 | jobEnabled: false 21 | enablePSP: false 22 | metrics: 23 | serviceMonitor: 24 | enabled: true 25 | defaultSettings: 26 | concurrentAutomaticEngineUpgradePerNodeLimit: 3 27 | snapshotMaxCount: 5 28 | persistence: 29 | defaultClassReplicaCount: 4 30 | - path: ./kubernetes-services/additions/longhorn 31 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 32 | targetRevision: main 33 | syncPolicy: 34 | automated: 35 | prune: true 36 | selfHeal: true 37 | syncOptions: 38 | - CreateNamespace=true 39 | retry: 40 | limit: 3 41 | backoff: 42 | duration: 5s 43 | factor: 2 44 | maxDuration: 3m -------------------------------------------------------------------------------- /kubernetes-services/templates/prometheus.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: prometheus 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: monitoring 12 | project: kubernetes 13 | sources: 14 | - chart: kube-prometheus-stack 15 | repoURL: https://prometheus-community.github.io/helm-charts 16 | targetRevision: 71.1.0 17 | helm: 18 | values: | 19 | prometheusOperator: 20 | admissionWebhooks: 21 | enabled: true 22 | 23 | annotations: 24 | argocd.argoproj.io/hook: PreSync 25 | argocd.argoproj.io/hook-delete-policy: HookSucceeded 26 | 27 | patch: 28 | annotations: 29 | argocd.argoproj.io/hook: PreSync 30 | argocd.argoproj.io/hook-delete-policy: HookSucceeded 31 | 32 | mutatingWebhookConfiguration: 33 | annotations: 34 | argocd.argoproj.io/hook: PreSync 35 | 36 | validatingWebhookConfiguration: 37 | annotations: 38 | argocd.argoproj.io/hook: PreSync 39 | 40 | prometheus: 41 | prometheusSpec: 42 | storageSpec: 43 | volumeClaimTemplate: 44 | spec: 45 | storageClassName: longhorn 46 | accessModes: ["ReadWriteOnce"] 47 | resources: 48 | requests: 49 | storage: 20Gi 50 | serviceMonitorSelectorNilUsesHelmValues: false 51 | podMonitorSelectorNilUsesHelmValues: false 52 | alertmanager: 53 | alertmanagerSpec: 54 | storage: 55 | volumeClaimTemplate: 56 | spec: 57 | storageClassName: longhorn 58 | accessModes: ["ReadWriteOnce"] 59 | resources: 60 | requests: 61 | storage: 5Gi 62 | grafana: 63 | deploymentStrategy: 64 | type: Recreate 65 | grafana.ini: 66 | server: 67 | serve_from_sub_path: true 68 | persistence: 69 | enabled: true 70 | type: pvc 71 | storageClassName: longhorn 72 | accessModes: ["ReadWriteOnce"] 73 | size: 1Gi 74 | - path: ./kubernetes-services/additions/prometheus 75 | repoURL: https://github.com/procinger/turing-pi-v2-cluster.git 76 | targetRevision: main 77 | - chart: loki-stack 78 | repoURL: https://grafana.github.io/helm-charts 79 | targetRevision: 2.10.2 80 | helm: 81 | values: | 82 | grafana: 83 | enabled: false 84 | prometheus: 85 | enabled: false 86 | promtail: 87 | enabled: true 88 | config: 89 | logLevel: info 90 | serverPort: 3101 91 | clients: 92 | - url: http://prometheus-loki:3100/loki/api/v1/push 93 | syncPolicy: 94 | automated: 95 | prune: true 96 | selfHeal: true 97 | syncOptions: 98 | - CreateNamespace=true 99 | - ServerSideApply=true 100 | - PruneLast=true 101 | retry: 102 | limit: 3 103 | backoff: 104 | duration: 5s 105 | factor: 2 106 | maxDuration: 3m 107 | -------------------------------------------------------------------------------- /kubernetes-services/templates/sealed-secrets.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: sealed-secrets 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: kube-system 12 | project: kubernetes 13 | source: 14 | chart: sealed-secrets 15 | repoURL: https://bitnami-labs.github.io/sealed-secrets 16 | targetRevision: 2.17.2 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | retry: 22 | limit: 3 23 | backoff: 24 | duration: 10s 25 | maxDuration: 3m 26 | factor: 2 27 | -------------------------------------------------------------------------------- /kubernetes-services/templates/snapshot-controller.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: argoproj.io/v1alpha1 2 | kind: Application 3 | metadata: 4 | name: snapshot-controller 5 | namespace: argo-cd 6 | finalizers: 7 | - resources-finalizer.argocd.argoproj.io 8 | spec: 9 | destination: 10 | server: https://kubernetes.default.svc 11 | namespace: kube-system 12 | project: kubernetes 13 | sources: 14 | - chart: snapshot-controller 15 | repoURL: https://piraeus.io/helm-charts/ 16 | targetRevision: 4.0.2 17 | syncPolicy: 18 | automated: 19 | prune: true 20 | selfHeal: true 21 | syncOptions: 22 | - CreateNamespace=true 23 | retry: 24 | limit: 3 25 | backoff: 26 | duration: 5s 27 | factor: 2 28 | maxDuration: 3m -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ], 6 | "assignees": [ 7 | "procinger" 8 | ], 9 | "prHourlyLimit": 5, 10 | "prConcurrentLimit": 10, 11 | "enabledManagers": [ 12 | "kubernetes", 13 | "argocd", 14 | "gomod", 15 | "custom.regex", 16 | "kustomize", 17 | "github-actions" 18 | ], 19 | "postUpdateOptions": [ 20 | "gomodTidy" 21 | ], 22 | "kubernetes": { 23 | "fileMatch": [ 24 | "^kubernetes-services/.*\\.yaml$", 25 | "(^|/)kustomization\\.ya?ml$" 26 | ] 27 | }, 28 | "argocd": { 29 | "fileMatch": [ 30 | "^kubernetes-services/templates/.*\\.yaml$" 31 | ] 32 | }, 33 | "packageRules": [ 34 | { 35 | "matchManagers": [ 36 | "argocd" 37 | ], 38 | "enabled": true, 39 | "automerge": true, 40 | "labels": [ 41 | "argocd", 42 | "helm chart" 43 | ] 44 | }, 45 | { 46 | "matchManagers": [ 47 | "gomod" 48 | ], 49 | "enabled": true, 50 | "automerge": true, 51 | "labels": [ 52 | "golang" 53 | ] 54 | }, 55 | { 56 | "matchManagers": [ 57 | "kustomize" 58 | ], 59 | "enabled": true, 60 | "automerge": true, 61 | "labels": [ 62 | "kustomize" 63 | ] 64 | }, 65 | { 66 | "matchManagers": [ 67 | "custom.regex" 68 | ], 69 | "enabled": true, 70 | "automerge": true, 71 | "labels": [ 72 | "custom regex" 73 | ] 74 | }, 75 | { 76 | "matchManagers": [ 77 | "github-actions" 78 | ], 79 | "enabled": true, 80 | "automerge": true, 81 | "labels": [ 82 | "github actions" 83 | ] 84 | }, 85 | { 86 | "description": "respect weird docker versioning", 87 | "versioning": "docker", 88 | "matchDatasources": [ 89 | "docker" 90 | ], 91 | "labels": [ 92 | "container image" 93 | ] 94 | }, 95 | { 96 | "description": "grouping of istio helm charts", 97 | "groupName": "istio helm charts", 98 | "matchPackageNames": [ 99 | "istiod", 100 | "gateway", 101 | "base" 102 | ], 103 | "labels": [ 104 | "argocd", 105 | "helm chart", 106 | "istio" 107 | ] 108 | }, 109 | { 110 | "description": "grouping of monitoring helm charts", 111 | "groupName": "monitoring helm charts", 112 | "matchPackageNames": [ 113 | "kube-prometheus-stack", 114 | "loki-stack" 115 | ], 116 | "labels": [ 117 | "argocd", 118 | "helm chart", 119 | "monitoring" 120 | ] 121 | }, 122 | { 123 | "description": "grouping of k8s packages", 124 | "groupName": "go k8s apis", 125 | "matchDatasources": "go", 126 | "matchPackageNames": [ 127 | "k8s.io/**", 128 | "sigs.k8s.io/**" 129 | ], 130 | "labels": [ 131 | "golang", 132 | "k8s api", 133 | "e2e framework" 134 | ] 135 | }, 136 | { 137 | "description": "Grouping of Alpine and Cloudflare OCI artifacts", 138 | "groupName": "cloudflare", 139 | "matchDatasources": ["docker"], 140 | "matchPackageNames": [ 141 | "alpine", 142 | "cloudflare/cloudflared" 143 | ], 144 | "labels": ["oci artifacts"] 145 | } 146 | ], 147 | "customManagers": [ 148 | { 149 | "customType": "regex", 150 | "description": "K3S System Upgrade Controller CRD", 151 | "fileMatch": [ 152 | "kubernetes-services/additions/k3s-upgrade-controller/kustomization.yaml$" 153 | ], 154 | "matchStrings": [ 155 | "datasource=(?\\S+) depName=(?\\S+)\n.*?-\\s(.*?)\/(?[^/]+)\/[^/]+\n" 156 | ], 157 | "depNameTemplate": "{{{depName}}}", 158 | "datasourceTemplate": "{{#if datasource}}{{{datasource}}}{{else}}github-releases{{/if}}", 159 | "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}" 160 | } 161 | ] 162 | } 163 | -------------------------------------------------------------------------------- /roles/argo-cd/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Setup K8S tooling and add github repository 3 | when: inventory_hostname == groups['server'][0] 4 | block: 5 | - name: Install Helm 6 | block: 7 | - name: Get helm 8 | ansible.builtin.unarchive: 9 | src: https://get.helm.sh/helm-v3.14.2-linux-arm64.tar.gz 10 | dest: /tmp 11 | remote_src: yes 12 | 13 | - name: move extracted helm 14 | ansible.builtin.copy: 15 | src: /tmp/linux-arm64/helm 16 | dest: /usr/local/bin/helm 17 | remote_src: true 18 | mode: "u=rx,g=rx,o=rx" 19 | 20 | - name: Add Argo CD helm repository 21 | ansible.builtin.command: 22 | cmd: helm repo add argo https://argoproj.github.io/argo-helm 23 | 24 | - name: Install Argo CD helm chart 25 | ansible.builtin.command: 26 | cmd: helm upgrade --install --kubeconfig ~{{ ansible_user }}/.kube/config argo-cd argo/argo-cd --version 6.4.0 --namespace argo-cd --create-namespace 27 | 28 | - name: Copy Argo-CD project settings 29 | ansible.builtin.copy: 30 | src: argo-cd 31 | dest: ~{{ ansible_user }} 32 | owner: "{{ ansible_user }}" 33 | mode: '0755' 34 | 35 | - name: Add Argo CD project 36 | ansible.builtin.command: 37 | cmd: kubectl --kubeconfig ~{{ ansible_user }}/.kube/config -n argo-cd apply -f argo-cd/argo-project.yaml 38 | 39 | - name: Add Argo CD git repository 40 | ansible.builtin.command: 41 | cmd: kubectl --kubeconfig ~{{ ansible_user }}/.kube/config -n argo-cd apply -f argo-cd/argo-git-repository.yaml -------------------------------------------------------------------------------- /roles/cloudflare/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Setup Cloudflare Tunnel 3 | when: inventory_hostname == groups['server'][0] and cloudflare is defined 4 | block: 5 | - name: Create Cloudflare Namespace 6 | ansible.builtin.shell: | 7 | kubectl --kubeconfig ~{{ ansible_user }}/.kube/config delete namespace cloudflare --ignore-not-found 8 | kubectl --kubeconfig ~{{ ansible_user }}/.kube/config create namespace cloudflare 9 | 10 | - name: Create Cloudflare Secret 11 | ansible.builtin.shell: | 12 | kubectl --kubeconfig ~{{ ansible_user }}/.kube/config --namespace cloudflare \ 13 | create secret generic cloudflare \ 14 | --from-literal=tunnel-token={{ cloudflare.tunnel_token }} 15 | 16 | - name: Create Cloudflare Configmap 17 | ansible.builtin.shell: | 18 | cat <