diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 77eb5ff..29e3776 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -6,7 +6,8 @@ "installTFsec": true, "installTerraformDocs": true }, - "ghcr.io/dhoeric/features/trivy:1": {} + "ghcr.io/dhoeric/features/trivy:1": {}, + "ghcr.io/devcontainers-contrib/features/argo-cd:1": {} }, "customizations": { "vscode": { diff --git a/modules/hetzner/README.md b/modules/hetzner/README.md index 785ca5e..d73943d 100644 --- a/modules/hetzner/README.md +++ b/modules/hetzner/README.md @@ -72,8 +72,10 @@ | [k3s\_cluster\_cidr](#output\_k3s\_cluster\_cidr) | CIDR used for the k3s cluster | | [kube\_api\_server](#output\_kube\_api\_server) | Kubernetes API server address | | [kubeconfig](#output\_kubeconfig) | Kubeconfig | +| [location](#output\_location) | Location to use. This is a single datacentre. | | [network\_name](#output\_network\_name) | Name of the network | | [pools](#output\_pools) | Servers created | +| [region](#output\_region) | Region to use. This covers multiple datacentres. | | [ssh\_port](#output\_ssh\_port) | SSH port for server | | [ssh\_user](#output\_ssh\_user) | SSH user for server | diff --git a/modules/hetzner/output.tf b/modules/hetzner/output.tf index 126f678..1931e98 100644 --- a/modules/hetzner/output.tf +++ b/modules/hetzner/output.tf @@ -33,6 +33,11 @@ output "k3s_cluster_cidr" { value = module.k3s.cluster_cidr } +output "location" { + description = "Location to use. This is a single datacentre." + value = var.location +} + output "network_name" { description = "Name of the network" value = hcloud_network.network.name @@ -61,6 +66,11 @@ output "pools" { ) } +output "region" { + description = "Region to use. This covers multiple datacentres." + value = var.region +} + output "ssh_port" { description = "SSH port for server" value = var.ssh_port diff --git a/modules/kubernetes/README.md b/modules/kubernetes/README.md index 3d20253..7ae8508 100644 --- a/modules/kubernetes/README.md +++ b/modules/kubernetes/README.md @@ -16,6 +16,7 @@ |------|---------| | [helm](#provider\_helm) | 2.14.1 | | [kubernetes](#provider\_kubernetes) | 2.31.0 | +| [random](#provider\_random) | 3.6.2 | ## Modules @@ -25,21 +26,30 @@ No modules. | Name | Type | |------|------| +| [helm_release.argocd](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [helm_release.hcloud_ccm](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [helm_release.hcloud_csi](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [helm_release.ingress_nginx](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | | [kubernetes_secret_v1.hcloud](https://registry.terraform.io/providers/hashicorp/kubernetes/latest/docs/resources/secret_v1) | resource | +| [random_integer.ingress_load_balancer_id](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/integer) | resource | ## Inputs | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [argocd\_version](#input\_argocd\_version) | Version of ArgoCD to use - defaults to latest | `string` | `null` | no | +| [cluster\_issuer](#input\_cluster\_issuer) | Cluster issuer to use for certificate | `string` | `"letsencrypt-staging"` | no | +| [domain](#input\_domain) | Domain to use - this may be a top-level or subdomain | `string` | n/a | yes | | [hcloud\_network\_name](#input\_hcloud\_network\_name) | Name of the network | `string` | n/a | yes | | [hcloud\_token](#input\_hcloud\_token) | Write token for the Hetzner API | `string` | n/a | yes | | [hetzner\_cloud\_config\_manager\_version](#input\_hetzner\_cloud\_config\_manager\_version) | Version of the HCloud CCM to use - defaults to latest | `string` | `null` | no | | [hetzner\_csi\_driver\_version](#input\_hetzner\_csi\_driver\_version) | Tag of the CSI driver to use - defaults to latest | `string` | `null` | no | +| [ingress\_nginx\_version](#input\_ingress\_nginx\_version) | Version of Ingress Nginx to install - defaults to latest | `string` | `null` | no | | [k3s\_cluster\_cidr](#input\_k3s\_cluster\_cidr) | CIDR used for the k3s cluster | `string` | `"10.244.0.0/16"` | no | | [kube\_context](#input\_kube\_context) | Kubernetes context to use | `string` | `"default"` | no | | [kubeconfig](#input\_kubeconfig) | Kubeconfig for the cluster | `string` | n/a | yes | +| [load\_balancer\_location](#input\_load\_balancer\_location) | Location to use for the load balancer | `string` | n/a | yes | +| [load\_balancer\_type](#input\_load\_balancer\_type) | Type of load balancer to use | `string` | `"lb11"` | no | ## Outputs diff --git a/modules/kubernetes/argocd.tf b/modules/kubernetes/argocd.tf new file mode 100644 index 0000000..6ccadf8 --- /dev/null +++ b/modules/kubernetes/argocd.tf @@ -0,0 +1,35 @@ +# Copyright 2024 Simon Emms +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "helm_release" "argocd" { + chart = "argo-cd" + name = "argocd" + atomic = true + cleanup_on_fail = true + create_namespace = true + namespace = "argocd" + repository = "https://argoproj.github.io/argo-helm" + reset_values = true + version = var.argocd_version + wait = true + + timeout = 10 * 60 # The Redis deployment can take it's sweet time + + values = [ + templatefile("${path.module}/files/argocd.yaml", { + cluster_issuer = var.cluster_issuer + domain = "argocd.${var.domain}" + }) + ] +} diff --git a/modules/kubernetes/files/argocd.yaml b/modules/kubernetes/files/argocd.yaml new file mode 100644 index 0000000..49274e7 --- /dev/null +++ b/modules/kubernetes/files/argocd.yaml @@ -0,0 +1,28 @@ +global: + domain: ${domain} +redis-ha: + enabled: true +repoServer: + autoscaling: + enabled: true + minReplicas: 2 +server: + autoscaling: + enabled: true + minReplicas: 2 + ingress: + enabled: true + ingressClassName: nginx + annotations: + nginx.ingress.kubernetes.io/force-ssl-redirect: "true" + nginx.ingress.kubernetes.io/backend-protocol: HTTP + kubernetes.io/tls-acme: "true" + cert-manager.io/cluster-issuer: ${cluster_issuer} + tls: true + extraTLS: + - hosts: + - ${domain} + secretName: argocd-tls +configs: + params: + server.insecure: true diff --git a/modules/kubernetes/files/ingress-nginx.yaml b/modules/kubernetes/files/ingress-nginx.yaml new file mode 100644 index 0000000..25e68dc --- /dev/null +++ b/modules/kubernetes/files/ingress-nginx.yaml @@ -0,0 +1,14 @@ +# The proxy protocol settings conflict with cert-manager +# @link https://github.com/kube-hetzner/terraform-hcloud-kube-hetzner/issues/354 +controller: + kind: DaemonSet + config: + use-proxy-protocol: false + service: + annotations: + load-balancer.hetzner.cloud/name: "${name}" + load-balancer.hetzner.cloud/location: "${location}" + load-balancer.hetzner.cloud/type: "${type}" + load-balancer.hetzner.cloud/disable-private-ingress: true + load-balancer.hetzner.cloud/use-private-ip: true + load-balancer.hetzner.cloud/uses-proxyprotocol: false diff --git a/modules/kubernetes/ingress-nginx.tf b/modules/kubernetes/ingress-nginx.tf new file mode 100644 index 0000000..6de3a8e --- /dev/null +++ b/modules/kubernetes/ingress-nginx.tf @@ -0,0 +1,47 @@ +# Copyright 2024 Simon Emms +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Deploy via Terraform to ensure load balancer is stopped when destroying infra + +resource "random_integer" "ingress_load_balancer_id" { + min = 1000 + max = 9999 +} + +resource "helm_release" "ingress_nginx" { + chart = "ingress-nginx" + name = "ingress-nginx" + atomic = true + cleanup_on_fail = true + create_namespace = true + namespace = "ingress-nginx" + repository = "https://kubernetes.github.io/ingress-nginx" + reset_values = true + version = var.ingress_nginx_version + wait = true + + values = [ + templatefile("${path.module}/files/ingress-nginx.yaml", { + location = var.load_balancer_location + name = "k3s-${random_integer.ingress_load_balancer_id.result}" + type = var.load_balancer_type + }) + ] + + # Depend upon the HCloud CCM to allow the load balancer to be deleted on-destroy + depends_on = [ + helm_release.hcloud_ccm, + helm_release.hcloud_csi, + ] +} diff --git a/modules/kubernetes/variables.tf b/modules/kubernetes/variables.tf index 4d1f0b2..2d97652 100644 --- a/modules/kubernetes/variables.tf +++ b/modules/kubernetes/variables.tf @@ -12,6 +12,23 @@ # See the License for the specific language governing permissions and # limitations under the License. +variable "argocd_version" { + type = string + description = "Version of ArgoCD to use - defaults to latest" + default = null +} + +variable "cluster_issuer" { + type = string + description = "Cluster issuer to use for certificate" + default = "letsencrypt-staging" +} + +variable "domain" { + type = string + description = "Domain to use - this may be a top-level or subdomain" +} + variable "hcloud_network_name" { type = string description = "Name of the network" @@ -52,3 +69,20 @@ variable "kube_context" { description = "Kubernetes context to use" default = "default" } + +variable "ingress_nginx_version" { + type = string + description = "Version of Ingress Nginx to install - defaults to latest" + default = null +} + +variable "load_balancer_location" { + type = string + description = "Location to use for the load balancer" +} + +variable "load_balancer_type" { + type = string + description = "Type of load balancer to use" + default = "lb11" +} diff --git a/stacks/dev/hetzner/terragrunt.hcl b/stacks/dev/hetzner/terragrunt.hcl index 31e3285..da0ca37 100644 --- a/stacks/dev/hetzner/terragrunt.hcl +++ b/stacks/dev/hetzner/terragrunt.hcl @@ -22,7 +22,7 @@ include { inputs = { k3s_manager_pool = { - count = 3 + count = 1 } k3s_worker_pools = [ { diff --git a/stacks/dev/kubernetes/terragrunt.hcl b/stacks/dev/kubernetes/terragrunt.hcl index 840ade4..54edecb 100644 --- a/stacks/dev/kubernetes/terragrunt.hcl +++ b/stacks/dev/kubernetes/terragrunt.hcl @@ -27,11 +27,14 @@ dependency "hetzner" { hcloud_network_name = "some-network-name" k3s_cluster_cidr = "some-cluster-cidr" kubeconfig = "some-kubeconfig" + location = "some-location" } } inputs = { - hcloud_network_name = dependency.hetzner.outputs.hcloud_network_name - k3s_cluster_cidr = dependency.hetzner.outputs.k3s_cluster_cidr - kubeconfig = dependency.hetzner.outputs.kubeconfig + domain = "dev.simonemms.com" + hcloud_network_name = dependency.hetzner.outputs.hcloud_network_name + k3s_cluster_cidr = dependency.hetzner.outputs.k3s_cluster_cidr + kubeconfig = dependency.hetzner.outputs.kubeconfig + load_balancer_location = dependency.hetzner.outputs.location } diff --git a/stacks/prod/hetzner/terragrunt.hcl b/stacks/prod/hetzner/terragrunt.hcl index e6338f7..ca03fdd 100644 --- a/stacks/prod/hetzner/terragrunt.hcl +++ b/stacks/prod/hetzner/terragrunt.hcl @@ -22,6 +22,12 @@ include { inputs = { k3s_manager_pool = { - count = 3 + count = 1 } + k3s_worker_pools = [ + { + count = 2 + name = "pool1" + }, + ] } diff --git a/stacks/prod/kubernetes/terragrunt.hcl b/stacks/prod/kubernetes/terragrunt.hcl index 840ade4..b5bf1dd 100644 --- a/stacks/prod/kubernetes/terragrunt.hcl +++ b/stacks/prod/kubernetes/terragrunt.hcl @@ -27,11 +27,15 @@ dependency "hetzner" { hcloud_network_name = "some-network-name" k3s_cluster_cidr = "some-cluster-cidr" kubeconfig = "some-kubeconfig" + location = "some-location" } } inputs = { - hcloud_network_name = dependency.hetzner.outputs.hcloud_network_name - k3s_cluster_cidr = dependency.hetzner.outputs.k3s_cluster_cidr - kubeconfig = dependency.hetzner.outputs.kubeconfig + cluster_issuer = "letsencrypt" + domain = "prod.simonemms.com" + hcloud_network_name = dependency.hetzner.outputs.hcloud_network_name + k3s_cluster_cidr = dependency.hetzner.outputs.k3s_cluster_cidr + kubeconfig = dependency.hetzner.outputs.kubeconfig + load_balancer_location = dependency.hetzner.outputs.location }