Overview
First step is to create API key in the Hetzner Cloud Console and Cloudflare API key in the Cloudflare dashboard for domain management. Import them as secrets in ./iac/terraform/nodes-infra/.auto.tfvars file:
hcloud_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
cloudflare_api_token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
cloudflare_zone_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
If you use more than one domain, as in my example, do the same thing for every Terraform directory that manages a domain.
Order of Execution
We need to use Terraform, Ansible, Helm and kubectl to deploy the infrastructure:
- We are creating SSH keys, network, and nodes in Hetzner Cloud with Terraform
- Ansible will deploy K3s on the nodes
- On K3s0, we will deploy Cilium and ArgoCD
- Apply K3s0 App of Apps to finish the setup and bootstrap
- Add K3s1 cluster to ArgoCD
- Apply k3s1 App of Apps in k3s0 to deploy the K3s1 cluster
Helm charts configuration
The Helm charts are configured in values.yaml and are imported as dependencies from a repository. They contain my personal configuration, but you can modify them to suit your needs. Do not blindly apply them - check every configuration. Each of them references my domain, and this repository is used as a production GitOps repository.
Project tree
[4.0K] ./
├── [4.0K] cluster/
│ ├── [4.0K] k3s0-ops/
│ │ └── [4.0K] helm/
│ │ ├── [4.0K] app-of-apps/
│ │ │ ├── [ 595] 100_system_cilium.yaml
│ │ │ ├── [ 734] 110_system_metallb_operator.yaml
│ │ │ ├── [ 738] 120_system_metallb_config.yaml
│ │ │ ├── [ 760] 150_system_ingress_nginx.yaml
│ │ │ ├── [ 763] 200_system_cert_manager.yaml
│ │ │ ├── [ 739] 300_system_cluster_issuer.yaml
│ │ │ ├── [ 681] 410_system_minio_operator.yaml
│ │ │ ├── [ 685] 420_system_gitlab_operator.yaml
│ │ │ ├── [ 689] 500_system_external_secrets.yaml
│ │ │ ├── [ 649] 600_system_argocd.yaml
│ │ │ ├── [ 730] 700_system_prometheus_grafana.yaml
│ │ │ ├── [ 640] 900_app_gitea.yaml
│ │ │ ├── [ 665] 910_app_uptime_kuma.yaml
│ │ │ ├── [ 661] 920_app_mailserver.yaml
│ │ │ ├── [ 683] 970_app_harbor.yaml
│ │ │ ├── [ 645] 980_app_gitlab.yaml
│ │ │ ├── [ 698] 990_app_s3_storage.yaml
│ │ │ └── [ 622] AppProject.yaml
│ │ ├── [4.0K] apps/
│ │ │ ├── [4.0K] gitea/
│ │ │ ├── [4.0K] gitlab/
│ │ │ ├── [4.0K] harbor/
│ │ │ ├── [4.0K] mailserver/
│ │ │ ├── [4.0K] s3-storage/
│ │ │ └── [4.0K] uptime-kuma/
│ │ ├── [4.0K] system/
│ │ │ ├── [4.0K] argocd/
│ │ │ ├── [4.0K] cert-manager/
│ │ │ ├── [4.0K] cilium/
│ │ │ ├── [4.0K] cluster-issuer/
│ │ │ ├── [4.0K] external-secrets/
│ │ │ ├── [4.0K] gitlab-operator/
│ │ │ ├── [4.0K] ingress-nginx/
│ │ │ ├── [4.0K] metallb-config/
│ │ │ ├── [4.0K] metallb-operator/
│ │ │ ├── [4.0K] minio-operator/
│ │ │ └── [4.0K] prometheus-grafana/
│ │ ├── [ 474] aoa.yaml
│ │ ├── [ 373] k3s1-cluster-secret-example.yaml
│ │ └── [2.0K] k3s1-cluster-secret.yaml
│ └── [4.0K] k3s1-app/
│ └── [4.0K] helm/
│ ├── [4.0K] app-of-apps/
│ │ ├── [ 551] 100_system_cilium.yaml
│ │ ├── [ 736] 110_system_metallb_operator.yaml
│ │ ├── [ 740] 120_system_metallb_config.yaml
│ │ ├── [ 762] 150_system_ingress_nginx.yaml
│ │ ├── [ 765] 200_system_cert_manager.yaml
│ │ ├── [ 741] 300_system_cluster_issuer.yaml
│ │ ├── [ 724] 400_system_postgres_operator.yaml
│ │ ├── [ 683] 410_system_minio_operator.yaml
│ │ ├── [ 691] 500_system_external_secrets.yaml
│ │ ├── [ 659] 900_app_portfolio.yaml
│ │ ├── [ 699] 910_app_github_readme_stats_api.yaml
│ │ ├── [ 671] 920_app_streamlit_wh.yaml
│ │ ├── [ 655] 925_app_probit_api.yaml
│ │ ├── [ 786] 930_app_todo_go_htmx.yaml
│ │ ├── [ 651] 935_app_fastapi.yaml
│ │ ├── [ 667] 940_app_notes_flask.yaml
│ │ ├── [ 667] 945_app_todo_django.yaml
│ │ ├── [ 926] 950_app_plausible_analytics.yaml
│ │ └── [ 668] 960_app_wordpress_ds.yaml
│ ├── [4.0K] apps/
│ │ ├── [4.0K] fastapi/
│ │ ├── [4.0K] github-readme-stats/
│ │ ├── [4.0K] notes-flask/
│ │ ├── [4.0K] plausible-analytics/
│ │ ├── [4.0K] portfolio/
│ │ ├── [4.0K] probit-api/
│ │ ├── [4.0K] streamlit-wh/
│ │ ├── [4.0K] todo-django/
│ │ ├── [4.0K] todo-go-htmx/
│ │ └── [4.0K] wordpress-ds/
│ ├── [4.0K] system/
│ │ ├── [4.0K] cert-manager/
│ │ ├── [4.0K] cilium/
│ │ ├── [4.0K] cluster-issuer/
│ │ ├── [4.0K] external-secrets/
│ │ ├── [4.0K] ingress-nginx/
│ │ ├── [4.0K] metallb-config/
│ │ ├── [4.0K] metallb-operator/
│ │ ├── [4.0K] minio-operator/
│ │ ├── [4.0K] postgres-operator/
│ │ └── [4.0K] prometheus-grafana/
│ ├── [ 474] aoa.yaml
│ └── [ 805] serviceAcc.yaml
├── [4.0K] iac/
│ ├── [4.0K] ansible/
│ │ ├── [4.0K] k3s-deploy/
│ │ │ ├── [4.0K] roles/
│ │ │ │ └── [4.0K] common/
│ │ │ ├── [ 239] ansible.cfg
│ │ │ ├── [ 459] Dockerfile
│ │ │ ├── [1.4K] k3s_deploy.yml
│ │ │ ├── [ 207] k3s_remove.yml
│ │ │ └── [ 169] requirements.yml
│ │ └── [ 827] inventory_k3s_deploy.yml
│ └── [4.0K] terraform/
│ ├── [4.0K] cloudflare-dns-astipan/
│ │ ├── [ 65] dependencies.tf
│ │ ├── [1.7K] main.tf
│ │ ├── [ 847] terrafrom.tf
│ │ └── [ 329] variables.tf
│ ├── [4.0K] cloudflare-dns-ds/
│ │ ├── [ 65] dependencies.tf
│ │ ├── [ 551] main.tf
│ │ ├── [ 842] terrafrom.tf
│ │ └── [ 329] variables.tf
│ ├── [4.0K] nodes-infra/
│ │ ├── [2.4K] cloudflare_dns.tf
│ │ ├── [1.8K] main.tf
│ │ ├── [ 142] output.tf
│ │ ├── [ 0] terraform.tfstate
│ │ ├── [ 37K] terraform.tfstate.backup
│ │ ├── [ 911] terrafrom.tf
│ │ └── [ 329] variables.tf
│ ├── [4.0K] s3-kubeconfig-backup/
│ │ ├── [ 649] main.tf
│ │ ├── [ 120] outputs.tf
│ │ ├── [ 895] terraform.tf
│ │ └── [ 159] variables.tf
│ ├── [4.0K] s3-ssh-keys-backup/
│ │ ├── [ 613] main.tf
│ │ ├── [ 120] outputs.tf
│ │ ├── [ 896] terraform.tf
│ │ ├── [ 0] terraform.tfstate
│ │ ├── [6.5K] terraform.tfstate.backup
│ │ └── [ 157] variables.tf
│ └── [4.0K] s3-tf-state/
│ ├── [ 254] main.tf
│ ├── [ 120] outputs.tf
│ ├── [ 427] terraform.tf
│ ├── [3.0K] terraform.tfstate
│ ├── [3.0K] terraform.tfstate.backup
│ └── [ 165] variables.tf
├── [ 191] docker-config.yml
├── [2.6K] docker_tag.sh*
├── [1.0K] LICENSE
├── [2.2K] Makefile
└── [1.6K] README.md