diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5cf880e --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +/tofu/controlplane.yaml +/tofu/kubeconfig +/tofu/talosconfig +/tofu/terraform.tfstate +/tofu/terraform.tfstate.backup +/tofu/worker.yaml +/tofu/.terraform.lock.hcl +/tofu/.terraform/ diff --git a/tofu/config/maxscale/charts/maxscale-helm/Chart.yaml b/tofu/config/maxscale/charts/maxscale-helm/Chart.yaml new file mode 100644 index 0000000..6285ebc --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/Chart.yaml @@ -0,0 +1,4 @@ +apiVersion: v2 +name: maxscale-helm +version: 0.1.9 +description: Helm chart for MaxScale related Kubernetes manifests diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/config.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/config.yaml new file mode 100644 index 0000000..f6e060e --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/config.yaml @@ -0,0 +1,157 @@ +apiVersion: k8s.mariadb.com/v1alpha1 +kind: MariaDB +metadata: + name: mariadb-repl + namespace: mariadb-operator +spec: + rootPasswordSecretKeyRef: + name: mariadb-secret + key: root-password + + username: mariadb + passwordSecretKeyRef: + name: mariadb-secret + key: password + database: mariadb + + storage: + size: 5Gi + storageClassName: longhorn + resizeInUseVolumes: true + waitForVolumeResize: true + volumeClaimTemplate: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi + storageClassName: longhorn + + replicas: 3 + replicasAllowEvenNumber: true + + podSpec: + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - "ALL" + seccompProfile: + type: RuntimeDefault + + maxScale: + enabled: true + + kubernetesService: + type: LoadBalancer + metadata: + annotations: + metallb.universe.tf/loadBalancerIPs: {{ .Values.metallb.maxscale_ip | default "" | quote }} + + connection: + secretName: mxs-repl-conn + port: 3306 + + metrics: + enabled: true + + tls: + enabled: true + + replication: + enabled: true + primary: + podIndex: 0 + automaticFailover: true + replica: + waitPoint: AfterSync + gtid: CurrentPos + replPasswordSecretKeyRef: + name: mariadb-secret + key: password + connectionTimeout: 10s + connectionRetries: 10 + syncTimeout: 10s + syncBinlog: 1 + probesEnabled: true + + service: + type: LoadBalancer + metadata: + annotations: + metallb.universe.tf/loadBalancerIPs: {{ .Values.metallb.service_ip | default "" | quote }} + connection: + secretName: mariadb-repl-conn + secretTemplate: + key: dsn + + primaryService: + type: LoadBalancer + metadata: + annotations: + metallb.universe.tf/loadBalancerIPs: {{ .Values.metallb.primary_ip | default "" | quote }} + primaryConnection: + secretName: mariadb-repl-conn-primary + secretTemplate: + key: dsn + + secondaryService: + type: LoadBalancer + metadata: + annotations: + metallb.universe.tf/loadBalancerIPs: {{ .Values.metallb.secondary_ip | default "" | quote }} + secondaryConnection: + secretName: mariadb-repl-conn-secondary + secretTemplate: + key: dsn + + affinity: + antiAffinityEnabled: true + + tolerations: + - key: "k8s.mariadb.com/ha" + operator: "Exists" + effect: "NoSchedule" + + podDisruptionBudget: + maxUnavailable: 33% + + updateStrategy: + type: ReplicasFirstPrimaryLast + + myCnf: | + [mariadb] + bind-address=* + default_storage_engine=InnoDB + binlog_format=row + innodb_autoinc_lock_mode=2 + innodb_buffer_pool_size=1024M + max_allowed_packet=256M + + #timeZone: Europe/Prague + + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + memory: 1Gi + + livenessProbe: + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 5 + + readinessProbe: + initialDelaySeconds: 20 + periodSeconds: 5 + timeoutSeconds: 5 + + metrics: + enabled: true + + tls: + enabled: true + required: true + + suspend: false \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/grant.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/grant.yaml new file mode 100644 index 0000000..6e879f3 --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/grant.yaml @@ -0,0 +1,18 @@ +apiVersion: k8s.mariadb.com/v1alpha1 +kind: Grant +metadata: + name: grant +spec: + mariaDbRef: + name: mariadb-repl + namespace: mariadb-operator + waitForIt: false + privileges: + - "ALL PRIVILEGES" + database: "*" + table: "*" + username: {{ .Values.user.name | default "user" }} + grantOption: true + host: {{ .Values.user.host | default "%" | quote }} + requeueInterval: 30s + retryInterval: 5s \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-0.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-0.yaml new file mode 100644 index 0000000..2a35453 --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-0.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: mariadb-repl-0 + namespace: mariadb-operator +spec: + selector: + app.kubernetes.io/instance: mariadb-repl + app.kubernetes.io/name: mariadb + statefulset.kubernetes.io/pod-name: mariadb-repl-0 + ports: + - name: mariadb + port: 3306 + targetPort: 3306 + protocol: TCP + type: ClusterIP \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-1.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-1.yaml new file mode 100644 index 0000000..5c28ef9 --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-1.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: mariadb-repl-1 + namespace: mariadb-operator +spec: + selector: + app.kubernetes.io/instance: mariadb-repl + app.kubernetes.io/name: mariadb + statefulset.kubernetes.io/pod-name: mariadb-repl-1 + ports: + - name: mariadb + port: 3306 + targetPort: 3306 + protocol: TCP + type: ClusterIP \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-2.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-2.yaml new file mode 100644 index 0000000..00b457b --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/mariadb-service-2.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: mariadb-repl-2 + namespace: mariadb-operator +spec: + selector: + app.kubernetes.io/instance: mariadb-repl + app.kubernetes.io/name: mariadb + statefulset.kubernetes.io/pod-name: mariadb-repl-2 + ports: + - name: mariadb + port: 3306 + targetPort: 3306 + protocol: TCP + type: ClusterIP \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-config-map.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-config-map.yaml new file mode 100644 index 0000000..a9444fc --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-config-map.yaml @@ -0,0 +1,32 @@ +{{- if (.Values.phpmyadmin.enabled | default true) }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: phpmyadmin-config + namespace: mariadb-operator +data: + hosts-init-script.sh: |- + #!/bin/bash + + echo " + /* Maximum number of databases displayed on one page */ + $cfg['MaxDbList'] = 300; + $cfg['MaxNavigationItems'] = 300; + /* Additional servers */ + $servers = [ + {{- range $i, $e := until (int (3)) }} + 'mariadb-repl-{{ $i }}', + {{- end }} + ]; + foreach ($servers as $server) { + $i++; + /* Authentication type */ + $cfg['Servers'][$i]['auth_type'] = 'cookie'; + /* Server parameters */ + $cfg['Servers'][$i]['host'] = $server; + $cfg['Servers'][$i]['port'] = '3306'; + $cfg['Servers'][$i]['compress'] = false; + $cfg['Servers'][$i]['AllowNoPassword'] = false; + } + " >> /opt/bitnami/phpmyadmin/config.inc.php +{{- end }} diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-deployment.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-deployment.yaml new file mode 100644 index 0000000..b66ee27 --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-deployment.yaml @@ -0,0 +1,76 @@ +{{- if (.Values.phpmyadmin.enabled | default true) }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: phpmyadmin + namespace: mariadb-operator + labels: + app: phpmyadmin +spec: + progressDeadlineSeconds: 600 + replicas: 1 + revisionHistoryLimit: 10 + selector: + matchLabels: + app: phpmyadmin + strategy: + rollingUpdate: + maxSurge: 25% + maxUnavailable: 25% + type: RollingUpdate + template: + metadata: + labels: + app: phpmyadmin + spec: + containers: + - env: + - name: DATABASE_ENABLE_SSL + value: "yes" + - name: DATABASE_HOST + value: "mariadb-repl" + - name: DATABASE_PORT_NUMBER + value: "3306" + - name: PHPMYADMIN_ALLOW_NO_PASSWORD + value: "false" + image: "docker.io/bitnami/phpmyadmin:5.2.2" + imagePullPolicy: IfNotPresent + livenessProbe: + failureThreshold: 3 + httpGet: + path: / + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + name: phpmyadmin + ports: + - containerPort: 8080 + name: http + protocol: TCP + - containerPort: 8443 + name: https + protocol: TCP + readinessProbe: + failureThreshold: 3 + httpGet: + path: / + port: http + scheme: HTTP + periodSeconds: 10 + successThreshold: 1 + timeoutSeconds: 1 + volumeMounts: + - mountPath: /docker-entrypoint-init.d/hosts-init-script.sh + name: config + subPath: hosts-init-script.sh + ip: 127.0.0.1 + restartPolicy: Always + volumes: + - configMap: + defaultMode: 511 + name: phpmyadmin-config + optional: false + name: config +{{- end }} \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-service.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-service.yaml new file mode 100644 index 0000000..a0b8726 --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/phpmyadmin-service.yaml @@ -0,0 +1,18 @@ +{{- if (.Values.phpmyadmin.enabled | default true) }} +apiVersion: v1 +kind: Service +metadata: + name: "phpmyadmin" + namespace: {{ .Values.namespace | default "mariadb-operator" | quote }} + labels: + app: "phpmyadmin" +spec: + clusterIP: None + ports: + - name: http + port: {{ .Values.phpmyadmin.servicePort | default 8080 }} + protocol: TCP + targetPort: {{ .Values.phpmyadmin.servicePort | default 8080 }} + selector: + app: "phpmyadmin" +{{- end }} \ No newline at end of file diff --git a/tofu/config/maxscale/charts/maxscale-helm/templates/user.yaml b/tofu/config/maxscale/charts/maxscale-helm/templates/user.yaml new file mode 100644 index 0000000..c79dd54 --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/templates/user.yaml @@ -0,0 +1,16 @@ +apiVersion: k8s.mariadb.com/v1alpha1 +kind: User +metadata: + name: mariadb-user + namespace: mariadb-operator +spec: + mariaDbRef: + name: mariadb-repl + namespace: mariadb-operator + waitForIt: false + host: {{ .Values.user.host | default "%" | quote }} + name: {{ .Values.user.name | default "user" }} + passwordPlugin: {} + passwordSecretKeyRef: + key: user-password + name: mariadb-secret diff --git a/tofu/config/maxscale/charts/maxscale-helm/values.yaml b/tofu/config/maxscale/charts/maxscale-helm/values.yaml new file mode 100644 index 0000000..dbc024d --- /dev/null +++ b/tofu/config/maxscale/charts/maxscale-helm/values.yaml @@ -0,0 +1,15 @@ +# Default values for maxscale-helm. +# This file can be used to override manifest parameters. + +user: + name: user + host: "%" + +metallb: + maxscale_ip: "" + service_ip: "" + primary_ip: "" + secondary_ip: "" + +phpmyadmin: + enabled: true diff --git a/tofu/config/maxscale/main.tf b/tofu/config/maxscale/main.tf new file mode 100644 index 0000000..d396177 --- /dev/null +++ b/tofu/config/maxscale/main.tf @@ -0,0 +1,74 @@ +terraform { + required_providers { + kubectl = { + source = "gavinbunney/kubectl" + version = "1.19.0" + } + helm = { + source = "hashicorp/helm" + version = "3.0.2" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.38.0" + } + } +} + +resource "kubernetes_namespace" "mariadb-operator" { + metadata { + name = "mariadb-operator" + } +} + +locals { + mariadb_secret_yaml = templatefile("${path.module}/mariadb-secret.yaml", { + password = var.mariadb_password + user_password = var.mariadb_user_password + root_password = var.mariadb_root_password + }) +} + +resource "kubectl_manifest" "secrets" { + yaml_body = local.mariadb_secret_yaml + depends_on = [ kubernetes_namespace.mariadb-operator ] +} + + +resource "helm_release" "mariadb-operator-crds" { + name = "mariadb-operator-crds" + repository = "https://helm.mariadb.com/mariadb-operator" + chart = "mariadb-operator-crds" + namespace = "mariadb-operator" + version = "25.8.4" + depends_on = [ kubectl_manifest.secrets ] + timeout = 3600 +} + + +resource "helm_release" "mariadb-operator" { + name = "mariadb-operator" + repository = "https://helm.mariadb.com/mariadb-operator" + chart = "mariadb-operator" + depends_on = [ helm_release.mariadb-operator-crds, kubectl_manifest.secrets ] + namespace = "mariadb-operator" + timeout = 3600 +} + +resource "helm_release" "maxscale_helm" { + name = "maxscale-helm" + chart = "${path.module}/charts/maxscale-helm" + version = "0.1.9" + depends_on = [ helm_release.mariadb-operator-crds, kubectl_manifest.secrets ] + timeout = 3600 + + set = [ + { name = "user.name", value = var.mariadb_user_name }, + { name = "user.host", value = var.mariadb_user_host }, + { name = "metallb.maxscale_ip", value = var.maxscale_ip }, + { name = "metallb.service_ip", value = var.service_ip }, + { name = "metallb.primary_ip", value = var.primary_ip }, + { name = "metallb.secondary_ip", value = var.secondary_ip }, + { name = "phpmyadmin.enabled", value = tostring(var.phpmyadmin_enabled) }, + ] +} diff --git a/tofu/config/maxscale/mariadb-secret.yaml b/tofu/config/maxscale/mariadb-secret.yaml new file mode 100644 index 0000000..b6486a0 --- /dev/null +++ b/tofu/config/maxscale/mariadb-secret.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: mariadb-secret + namespace: mariadb-operator + labels: + k8s.mariadb.com/watch: "" +stringData: + password: ${password} + user-password: ${user_password} + root-password: ${root_password} diff --git a/tofu/config/maxscale/variables.tf b/tofu/config/maxscale/variables.tf new file mode 100644 index 0000000..797cceb --- /dev/null +++ b/tofu/config/maxscale/variables.tf @@ -0,0 +1,52 @@ +variable "mariadb_password" { + description = "Password for standard MariaDB users (stored in Secret). Keep secure." + type = string + sensitive = true +} + +variable "mariadb_root_password" { + description = "Root password for MariaDB (stored in Secret). Keep secure." + type = string + sensitive = true +} + +variable "mariadb_user_name" { + description = "Application username to create and grant privileges to" + type = string +} + +variable "mariadb_user_host" { + description = "Host (wildcard or specific) for the application user" + type = string +} + +variable "maxscale_ip" { + description = "MetalLB IP for MaxScale service" + type = string +} + +variable "service_ip" { + description = "MetalLB IP for general MariaDB service" + type = string +} + +variable "primary_ip" { + description = "MetalLB IP for MariaDB primary service" + type = string +} + +variable "secondary_ip" { + description = "MetalLB IP for MariaDB secondary service" + type = string +} + +variable "phpmyadmin_enabled" { + description = "Whether to deploy phpMyAdmin auxiliary components" + type = bool +} + +variable "mariadb_user_password" { + description = "Password for the application MariaDB user" + type = string + sensitive = true +} diff --git a/tofu/config/metallb/main.tf b/tofu/config/metallb/main.tf new file mode 100644 index 0000000..1c03b66 --- /dev/null +++ b/tofu/config/metallb/main.tf @@ -0,0 +1,70 @@ +terraform { + required_providers { + kubectl = { + source = "gavinbunney/kubectl" + version = "1.19.0" + } + helm = { + source = "hashicorp/helm" + version = "3.0.2" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.38.0" + } + } +} + +resource "kubernetes_namespace" "metallb-system" { + metadata { + name = "metallb-system" + labels = { + "pod-security.kubernetes.io/enforce" = "privileged" + } + } +} + +resource "helm_release" "metallb" { + depends_on = [ kubernetes_namespace.metallb-system ] + + name = "metallb" + repository = "https://metallb.github.io/metallb" + chart = "metallb" + namespace = "metallb-system" + version = "0.14.9" + + timeout = 3600 +} + +resource "kubectl_manifest" "metallb_pool" { + depends_on = [ helm_release.metallb ] + + yaml_body = yamlencode({ + apiVersion = "metallb.io/v1beta1" + kind = "IPAddressPool" + metadata = { + name = "metallb-pool" + namespace = "metallb-system" + } + spec = { + addresses = [var.metallb_ip_range] + } + }) + +} + +resource "kubectl_manifest" "metallb_l2_advertisement" { + depends_on = [ kubectl_manifest.metallb_pool ] + + yaml_body = yamlencode({ + apiVersion = "metallb.io/v1beta1" + kind = "L2Advertisement" + metadata = { + name = "l2-advertisement" + namespace = "metallb-system" + } + spec = { + ipAddressPools = ["metallb-pool"] + } + }) +} \ No newline at end of file diff --git a/tofu/config/metallb/variables.tf b/tofu/config/metallb/variables.tf new file mode 100644 index 0000000..1bd4246 --- /dev/null +++ b/tofu/config/metallb/variables.tf @@ -0,0 +1,4 @@ +variable "metallb_ip_range" { + description = "IP address range for MetalLB address pool" + type = string +} diff --git a/tofu/config/storage/main.tf b/tofu/config/storage/main.tf new file mode 100644 index 0000000..db69b92 --- /dev/null +++ b/tofu/config/storage/main.tf @@ -0,0 +1,35 @@ +terraform { + required_providers { + kubectl = { + source = "gavinbunney/kubectl" + version = "1.19.0" + } + helm = { + source = "hashicorp/helm" + version = "3.0.2" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.38.0" + } + } +} + +resource "kubernetes_namespace" "longhorn-system" { + metadata { + name = "longhorn-system" + labels = { + "pod-security.kubernetes.io/enforce" = "privileged" + } + } +} + +resource "helm_release" "longhorn" { + depends_on = [ kubernetes_namespace.longhorn-system ] + name = "longhorn" + repository = "https://charts.longhorn.io/" + chart = "longhorn" + namespace = "longhorn-system" + version = "1.9.1" + timeout = 3600 +} diff --git a/tofu/main.tf b/tofu/main.tf new file mode 100644 index 0000000..306cc59 --- /dev/null +++ b/tofu/main.tf @@ -0,0 +1,61 @@ +terraform { + required_providers { + kubectl = { + source = "gavinbunney/kubectl" + version = "1.19.0" + } + helm = { + source = "hashicorp/helm" + version = "3.0.2" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "2.38.0" + } + } +} + +provider "kubernetes" { + config_path = "./kubeconfig" +} + +provider "kubectl" { + config_path = "./kubeconfig" +} + + +provider "helm" { + kubernetes = { + config_path = "./kubeconfig" + } +} + +module "loadbalancer" { + source = "${path.module}/config/metallb" + depends_on = [ module.storage ] + + metallb_ip_range = var.metallb_ip_range +} + + +module "database" { + source = "${path.module}/config/maxscale" + depends_on = [ module.storage, module.loadbalancer ] + + mariadb_password = var.mariadb_password + mariadb_root_password = var.mariadb_root_password + mariadb_user_name = var.mariadb_user_name + mariadb_user_host = var.mariadb_user_host + mariadb_user_password = var.mariadb_user_password + + maxscale_ip = var.metallb_maxscale_ip + service_ip = var.metallb_service_ip + primary_ip = var.metallb_primary_ip + secondary_ip = var.metallb_secondary_ip + + phpmyadmin_enabled = var.phpmyadmin_enabled +} + +module "storage" { + source = "${path.module}/config/storage" +} \ No newline at end of file diff --git a/tofu/variables.tf b/tofu/variables.tf new file mode 100644 index 0000000..d079f48 --- /dev/null +++ b/tofu/variables.tf @@ -0,0 +1,65 @@ +variable "metallb_ip_range" { + description = "IP address range for MetalLB address pool (e.g., 10.80.0.100-10.80.0.240)" + type = string +} + +variable "mariadb_password" { + description = "Password for standard MariaDB users (stored in Secret). Keep secure." + type = string + sensitive = true +} + +variable "mariadb_root_password" { + description = "Root password for MariaDB (stored in Secret). Keep secure." + type = string + sensitive = true +} + +variable "mariadb_user_name" { + description = "Application username to create and grant privileges to" + type = string + default = "user" +} + +variable "mariadb_user_host" { + description = "Host (wildcard or specific) for the application user" + type = string + default = "%" +} + +variable "metallb_maxscale_ip" { + description = "MetalLB IP for MaxScale service" + type = string + default = "" +} + +variable "metallb_service_ip" { + description = "MetalLB IP for general MariaDB service" + type = string + default = "" +} + +variable "metallb_primary_ip" { + description = "MetalLB IP for MariaDB primary service" + type = string + default = "" +} + +variable "metallb_secondary_ip" { + description = "MetalLB IP for MariaDB secondary service" + type = string + default = "" +} + +variable "phpmyadmin_enabled" { + description = "Whether to deploy phpMyAdmin auxiliary components" + type = bool + default = true +} + +variable "mariadb_user_password" { + description = "Password for the application MariaDB user" + type = string + sensitive = true + nullable = false +}