From 82a179dad3b475bf0ad59f09acf65ed1626afe3f Mon Sep 17 00:00:00 2001 From: Xavier Mortelette Date: Sun, 18 Feb 2024 10:07:41 +0100 Subject: [PATCH] Optimize service port definition --- application/application.tf | 34 ++++++++++++++ application/providers.tf | 4 ++ application/variables.tf | 43 +++++++++++++++++ forward/forward.tf | 33 +------------ forward/variables.tf | 24 +--------- ingress/ingress.tf | 17 ++++--- ingress/variables.tf | 24 ++++++---- rabbitmq/outputs.tf | 23 ++++++++++ rabbitmq/providers.tf | 8 ++++ rabbitmq/rabbitmq.tf | 94 ++++++++++++++++++++++++++++++++++++++ rabbitmq/variables.tf | 28 ++++++++++++ service/outputs.tf | 16 ++++--- service/svc.tf | 27 ++++------- service/variables.tf | 55 ++++++++++------------ 14 files changed, 301 insertions(+), 129 deletions(-) create mode 100644 rabbitmq/outputs.tf create mode 100644 rabbitmq/providers.tf create mode 100644 rabbitmq/rabbitmq.tf create mode 100644 rabbitmq/variables.tf diff --git a/application/application.tf b/application/application.tf index 866833f..d970790 100644 --- a/application/application.tf +++ b/application/application.tf @@ -1,9 +1,43 @@ locals { app_slug = "${var.instance}${var.component == "" ? "" : "-"}${var.component}" + application_labels = merge(var.labels, { + "app.kubernetes.io/component" = "authentik-application" + }) app_name = var.app_name != "" ? var.app_name : var.component == var.instance ? var.instance : format("%s-%s", var.instance, var.component) main_group = format("app-%s", local.app_slug) + secret_name = var.cert_name != "" ? var.cert_name : "${local.app_slug}-cert" url_icon = startswith(var.icon, "fa-") ? "fa://${var.icon}" : format("https://%s/%s", var.dns_name, var.icon) + rules_icons = [{ + "host" = var.rule_mapper.host + "http" = { + "paths" = [for mapper in var.rule_mapper.paths : { + "path" = "/${var.icon}" + "pathType" = "Prefix" + "backend" = mapper.backend + }] + } + }] } + +resource "kubectl_manifest" "ingress_icon" { + count = startswith(var.icon, "fa-") ? 0 : 1 + force_conflicts = true + yaml_body = <<-EOF + apiVersion: "networking.k8s.io/v1" + kind: "Ingress" + metadata: + name: "${local.app_slug}-icons" + namespace: "${var.namespace}" + labels: ${jsonencode(local.application_labels)} + spec: + ingressClassName: "${var.ingress_class}" + rules: ${jsonencode(local.rules_icons)} + tls: + - hosts: ${jsonencode([var.dns_name])} + secretName: "${local.secret_name}" + EOF +} + data "authentik_group" "akadmin" { name = "authentik Admins" } diff --git a/application/providers.tf b/application/providers.tf index 77cbd53..250b1a1 100644 --- a/application/providers.tf +++ b/application/providers.tf @@ -1,5 +1,9 @@ terraform { required_providers { + kubectl = { + source = "gavinbunney/kubectl" + version = "~> 1.14.0" + } authentik = { source = "goauthentik/authentik" version = "~> 2023.5.0" diff --git a/application/variables.tf b/application/variables.tf index 63f487e..ee6a00b 100644 --- a/application/variables.tf +++ b/application/variables.tf @@ -30,3 +30,46 @@ variable "backchannel_providers" { type = list(number) default = null } + +variable "ingress_class" { + type = string + default = "" +} + +variable "rule_mapper" { + type = object({ + host = string + paths = list(object({ + path = optional(string) + type = optional(string) + backend = object({ + service = object({ + name = string + port = object({ + name = string + }) + }) + }) + })) + }) + default = { + host="not.defined" + paths= [] + } +} + +variable "cert_name" { + type = string + default = "" + description = "Give a secret name for tls, if empty will use the ingress cert_name" +} + +variable "namespace" { + type = string + default = "" +} + +variable "labels" { + type = map(string) + default = {} +} diff --git a/forward/forward.tf b/forward/forward.tf index 1219008..b3e134d 100644 --- a/forward/forward.tf +++ b/forward/forward.tf @@ -3,42 +3,11 @@ locals { forward_labels = merge(var.labels, { "app.kubernetes.io/component" = "authentik-forward" }) - main_group = format("app-%s", var.app_name) - external_url = format("https://%s", var.dns_names[0]) - rules_icons = [for v in var.dns_names : { - "host" = "${v}" - "http" = { - "paths" = [{ - "backend" = { - "service" = var.service - } - "path" = "/${var.icon}" - "pathType" = "Prefix" - }] - } - }] + external_url = format("https://%s", var.dns_name) forward_outpost_providers = jsondecode(data.http.get_forward_outpost.response_body).results[0].providers forward_outpost_pk = jsondecode(data.http.get_forward_outpost.response_body).results[0].pk } -resource "kubectl_manifest" "ingress_icon" { - force_conflicts = true - yaml_body = <<-EOF - apiVersion: "networking.k8s.io/v1" - kind: "Ingress" - metadata: - name: "${local.app_slug}-icons" - namespace: "${var.namespace}" - labels: ${jsonencode(local.forward_labels)} - spec: - ingressClassName: "${var.ingress_class}" - rules: ${jsonencode(local.rules_icons)} - tls: - - hosts: ${jsonencode(var.dns_names)} - secretName: "${var.instance}-cert" - EOF -} - data "authentik_flow" "default_authorization_flow" { slug = "default-provider-authorization-implicit-consent" } diff --git a/forward/variables.tf b/forward/variables.tf index 23cd296..d06cbaf 100644 --- a/forward/variables.tf +++ b/forward/variables.tf @@ -18,37 +18,15 @@ variable "labels" { type = map(string) } -variable "ingress_class" { +variable "dns_name" { type = string } -variable "icon" { - type = string -} - -variable "dns_names" { - type = list(string) -} - variable "access_token_validity" { type = string default = "hours=10" // ;minutes=10 } -variable "app_name" { - type = string - default = "" -} - -variable "service" { - type = object({ - name = string - port = object({ - number = number - }) - }) -} - variable "request_headers" { type = map(string) } diff --git a/ingress/ingress.tf b/ingress/ingress.tf index 19d53aa..374383a 100644 --- a/ingress/ingress.tf +++ b/ingress/ingress.tf @@ -4,23 +4,22 @@ locals { pres_labels = merge(var.labels, { "app.kubernetes.io/component" = "presentation" }) - rules = [for v in var.dns_names : { - "host" = "${v}" + rules = [for rule in var.rules_mapper : { + "host" = rule.host "http" = { - "paths" = [for mapper in var.services_mapping : { + "paths" = [for mapper in rule.paths : { "path" = "/${mapper.path}" - "pathType" = "Prefix" - "backend" = { - "service" = mapper.service - } + "pathType" = mapper.type != null && mapper.type != "" ? mapper.type : "Prefix" + "backend" = mapper.backend }] } }] + dns_names = [for rule in var.rules_mapper : rule.host] secret_name = var.cert_name != "" ? var.cert_name : "${local.app_slug}-cert" tls = var.entrypoint == "" || length(regexall(".*websecure.*", var.entrypoint)) > 0 ? [ { secretName = local.secret_name - hosts = var.dns_names + hosts = local.dns_names } ] : [] middlewares = concat( @@ -48,7 +47,7 @@ resource "kubectl_manifest" "certificate" { labels: ${jsonencode(local.pres_labels)} spec: secretName: "${local.secret_name}" - dnsNames: ${jsonencode(var.dns_names)} + dnsNames: ${jsonencode(local.dns_names)} issuerRef: kind: "ClusterIssuer" name: "${var.issuer}" diff --git a/ingress/variables.tf b/ingress/variables.tf index 0d43892..9a510eb 100644 --- a/ingress/variables.tf +++ b/ingress/variables.tf @@ -17,24 +17,28 @@ variable "ingress_class" { variable "labels" { type = map(string) } -variable "dns_names" { - type = list(string) -} variable "middlewares" { type = list(string) default = [] } -variable "services_mapping" { +variable "rules_mapper" { type = list(object({ - path = optional(string) - service = object({ - name= string - port = object({ - number = number + host = string + paths = list(object({ + path = optional(string) + type = optional(string) + backend = object({ + service = object({ + name = string + port = object({ + name = string + }) + }) }) - }) + })) })) + description = "Simplified rules mapper for ingress" } variable "entrypoint" { diff --git a/rabbitmq/outputs.tf b/rabbitmq/outputs.tf new file mode 100644 index 0000000..210cde6 --- /dev/null +++ b/rabbitmq/outputs.tf @@ -0,0 +1,23 @@ +output "conn_string" { + value = "mqtt://${urlencode(data.kubernetes_secret_v1.rabbit_secret.data["username"])}:${urlencode(data.kubernetes_secret_v1.rabbit_secret.data["password"])}@${local.app_slug}-mq.${var.namespace}.svc:1883" +} + +output "service" { + value = "${local.app_slug}-mq.${var.namespace}.svc" +} + +output "db_host" { + value = "${local.app_slug}-mq" +} + +output "db_port" { + value = "1883" +} + +output "cert_secret_name" { + value = local.secret_name +} + +output "user_secret_name" { + value = "${local.app_slug}-user" +} diff --git a/rabbitmq/providers.tf b/rabbitmq/providers.tf new file mode 100644 index 0000000..852127e --- /dev/null +++ b/rabbitmq/providers.tf @@ -0,0 +1,8 @@ +terraform { + required_providers { + kubectl = { + source = "gavinbunney/kubectl" + version = "~> 1.14.0" + } + } +} diff --git a/rabbitmq/rabbitmq.tf b/rabbitmq/rabbitmq.tf new file mode 100644 index 0000000..91108f5 --- /dev/null +++ b/rabbitmq/rabbitmq.tf @@ -0,0 +1,94 @@ +locals { + app_slug = "${var.instance}${var.component == "" ? "" : "-"}${var.component}" + rabbit_labels = merge(var.labels, { + "app.kubernetes.io/component" = "rabbitmq" + }) + secret_name = var.cert_name != "" ? var.cert_name : "${local.app_slug}-cert" +} + +resource "kubectl_manifest" "certificate" { + count = var.cert_name == "" ? 1 : 0 + yaml_body = <<-EOF + apiVersion: "cert-manager.io/v1" + kind: "Certificate" + metadata: + name: "${local.app_slug}" + namespace: "${var.namespace}" + labels: ${jsonencode(local.rabbit_labels)} + spec: + secretName: "${local.secret_name}" + dnsNames: + - "${local.app_slug}-mq.${var.namespace}.svc" + - "*.${local.app_slug}-mq-nodes.${var.namespace}.svc" + issuerRef: + kind: "ClusterIssuer" + name: "${var.issuer}" + group: "cert-manager.io" + EOF +} + +resource "kubectl_manifest" "rabbit_secret" { + yaml_body = <<-EOF + apiVersion: "secretgenerator.mittwald.de/v1alpha1" + kind: "StringSecret" + metadata: + name: "${local.app_slug}-user" + namespace: "${var.namespace}" + labels: ${jsonencode(local.rabbit_labels)} + spec: + forceRegenerate: false + data: + username: "${var.instance}" + port: "5672" + host: "${local.app_slug}-mq.${var.namespace}.svc" + fields: + - fieldName: "password" + length: "32" + EOF +} + +data "kubernetes_secret_v1" "rabbit_secret" { + depends_on = [kubectl_manifest.rabbit_secret] + metadata { + name = "${local.app_slug}-user" + namespace = var.namespace + labels = local.rabbit_labels + } +} + +# based on https://github.com/rabbitmq/cluster-operator/tree/main/docs/examples + +resource "kubectl_manifest" "rabbitmq" { + depends_on = [ + kubectl_manifest.certificate, + kubectl_manifest.rabbit_secret, + data.kubernetes_secret_v1.rabbit_secret, + ] + yaml_body = <<-EOF + apiVersion: rabbitmq.com/v1beta1 + kind: RabbitmqCluster + metadata: + name: "${local.app_slug}-mq" + namespace: "${var.namespace}" + labels: ${jsonencode(local.rabbit_labels)} + spec: + replicas: ${var.replicas} + tls: + secretName: ${local.secret_name} + rabbitmq: + erlangInetConfig: | + {inet6, true}. + envConfig: | + SERVER_ADDITIONAL_ERL_ARGS="-kernel inetrc '/etc/rabbitmq/erl_inetrc' -proto_dist inet6_tcp" + RABBITMQ_CTL_ERL_ARGS="-proto_dist inet6_tcp" + additionalConfig: | + cluster_formation.k8s.host = kubernetes.default.svc.cluster.local + default_user=${data.kubernetes_secret_v1.rabbit_secret.data["username"]} + default_pass=${data.kubernetes_secret_v1.rabbit_secret.data["password"]} + additionalPlugins: + - rabbitmq_mqtt + - rabbitmq_web_mqtt + service: + ipFamilyPolicy: "PreferDualStack" + EOF +} diff --git a/rabbitmq/variables.tf b/rabbitmq/variables.tf new file mode 100644 index 0000000..c4725fa --- /dev/null +++ b/rabbitmq/variables.tf @@ -0,0 +1,28 @@ +variable "component" { + type = string +} +variable "instance" { + type = string +} +variable "namespace" { + type = string +} +variable "labels" { + type = map(string) +} +variable "annotations" { + type = map(string) + default = {} +} +variable "issuer" { + type = string +} +variable "replicas" { + type = number + default = 1 +} +variable "cert_name" { + type = string + default = "" + description = "Give a secret name for tls, if empty and entrypointis websecure or empty, one will be created" +} diff --git a/service/outputs.tf b/service/outputs.tf index 2fae9ea..fc01c22 100644 --- a/service/outputs.tf +++ b/service/outputs.tf @@ -1,11 +1,15 @@ output "name" { value = local.app_slug } -output "default_definition" { - value = { - "name" = "${local.app_slug}" - "port" = { - "number" = var.ports[0] +output "ingress_backend_exposure" { + value = [for port_map in var.port_mapper : + { + "service" = { + "name" = "${local.app_slug}" + "port" = { + "name" = port_map.name + } + } } - } + ] } diff --git a/service/svc.tf b/service/svc.tf index 953ad9f..3186ae5 100644 --- a/service/svc.tf +++ b/service/svc.tf @@ -7,21 +7,10 @@ locals { "protocol" = var.protocols[idx] "targetPort" = target }] : [] - ext_ports = var.svc_type == "ExternalName" ? [for idx, target in var.targets : { - "name" = target - "port" = var.ports[idx] - "protocol" = var.protocols[idx] - "targetPort" = var.ports[idx] - }] : [] - lb_ports = var.svc_type == "LoadBalancer" ? [for port in var.lb_ports : { - "port" = port.port.number - "name" = port.name - "targetPort" = port.port.number - }] : [] - node_ports = var.svc_type == "NodePort" ? [for idx, port in var.ports : { - "port" = port - "targetPort" = port - "nodePort" = var.node_ports[idx] + node_ports = var.svc_type == "NodePort" ? [for port_map in var.port_mapper : { + "port" = port_map.port + "targetPort" = port_map.target + "nodePort" = port_map.port }] : [] metadata = merge( { @@ -40,9 +29,9 @@ locals { selector = local.selector }, "ExternalName" = { - type = "ExternalName" - externalName = var.target_host - ports = local.ext_ports + type = "ExternalName" + externalName = var.target_host + ports = local.default_ports }, "NodePort" = { type = "NodePort" @@ -78,6 +67,6 @@ resource "kubectl_manifest" "endpoint" { subsets: - addresses: - ip: ${var.target_host} - ports: ${jsonencode([for port in var.ports : { "port" = port }])} + ports: ${jsonencode([for port_map in var.port_mapper : { "port" = port_map.port }])} EOF } diff --git a/service/variables.tf b/service/variables.tf index c57d6c0..ec4c100 100644 --- a/service/variables.tf +++ b/service/variables.tf @@ -36,43 +36,38 @@ variable "ip_family" { } } -variable "ports" { - type = list(number) - default = [80] -} -variable "targets" { - type = list(string) - default = ["http"] -} -variable "protocols" { - type = list(any) - default = ["TCP"] +variable "port_mapper" { + type = list(object({ + name = optional(string) + port = number + protocol = string + target = string + })) + default = [{ + "name" = "80-TCP", + "port" = 80, + "protocol" = "TCP" + "target" = "80-TCP" + }] validation { - condition = alltrue([for proto in var.protocols : contains(["TCP", "UDP"], proto)]) - error_message = "Only TCP or UDP is allowed" + condition = alltrue( + [for port_map in var.port_mapper : contains(["TCP", "UDP"], port_map.protocol)] + ) + error_message = "Only numeric on containerPort and TCP or UDP on protocol is allowed" } + # validation { + # condition = (var.svc_type == "NodePort") == alltrue( + # [for port_map in var.port_mapper : port_map.port >= 30000 && port_map.port <= 32767] + # ) + # error_message = "The range of valid ports is 30000-32767 for a NodePort type" + # } } + variable "target_host" { type = string default = "" } -variable "node_ports" { - type = list(number) - default = [30080] - validation { - condition = alltrue([for port in var.node_ports : port >= 30000 && port <= 32767]) - error_message = "The range of valid ports is 30000-32767" - } -} -variable "lb_ports" { - type = list(object({ - name = string - port = object({ - number = number - }) - })) - default = [] -} + variable "lb_policy" { type = string default = "Cluster"