diff --git a/terraform/k8apps/.vault b/terraform/k8apps/.vault new file mode 100644 index 0000000..7007d14 --- /dev/null +++ b/terraform/k8apps/.vault @@ -0,0 +1 @@ +../../repository.vault \ No newline at end of file diff --git a/terraform/k8apps/backend.tf b/terraform/k8apps/backend.tf new file mode 100644 index 0000000..38df914 --- /dev/null +++ b/terraform/k8apps/backend.tf @@ -0,0 +1,24 @@ +# +# Copyright 2019 Alexandre Pires (alexandre.pires@mov.ai) +# +# 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. +# +# File: backend.tf + +terraform { + backend "s3" { + bucket = "a13labs.infra" + key = "scaleway-apps.tfstate" + region = "us-east-1" + } +} diff --git a/terraform/k8apps/buckets.tf b/terraform/k8apps/buckets.tf new file mode 100644 index 0000000..2c9b910 --- /dev/null +++ b/terraform/k8apps/buckets.tf @@ -0,0 +1,49 @@ +resource "random_string" "bucket_suffix" { + length = 8 + lower = true + upper = false + special = false +} + +resource "scaleway_object_bucket" "backup" { + name = "a13labs-backup-${random_string.bucket_suffix.result}" + project_id = var.scaleway_project_id + region = "fr-par" + + lifecycle_rule { + id = "clean" + prefix = "gitea-backup/" + enabled = true + + expiration { + days = local.cloud_backup_retention_days + } + } + + lifecycle_rule { + id = "clean" + prefix = "wordpress-backup/" + enabled = true + + expiration { + days = local.cloud_backup_retention_days + } + } + + lifecycle_rule { + id = "clean" + prefix = "nextcloud-backup/" + enabled = true + + expiration { + days = local.cloud_backup_retention_days + } + } + +} + +resource "scaleway_object_bucket" "nextcloud" { + name = "a13labs-nextcloud-${random_string.bucket_suffix.result}" + project_id = var.scaleway_project_id + region = "fr-par" +} diff --git a/terraform/k8apps/collabora.tf b/terraform/k8apps/collabora.tf new file mode 100644 index 0000000..fadbf6b --- /dev/null +++ b/terraform/k8apps/collabora.tf @@ -0,0 +1,213 @@ +# +# Namespace +# +resource "kubernetes_namespace" "collabora" { + metadata { + annotations = { + name = "collabora" + } + + labels = { + name = "collabora" + } + + name = "collabora" + } +} + +resource "kubernetes_secret" "collabora_admin_credentials" { + metadata { + name = "collabora-admin-credentials" + namespace = kubernetes_namespace.collabora.metadata[0].name + } + data = { + username = "admin" + password = var.collabora_admin_password + } + type = "Opaque" +} + +resource "kubernetes_config_map" "collabora" { + + metadata { + name = "collabora-config" + namespace = kubernetes_namespace.collabora.metadata[0].name + } + data = { + "extra_params" = "-o:ssl.enable=true" + "aliasgroup1" = "https://${local.nextcloud_fqdn}" + } +} + +resource "kubernetes_service_account" "collabora_service_account" { + + metadata { + name = "collabora-app-sa" + namespace = kubernetes_namespace.collabora.metadata[0].name + } + + automount_service_account_token = false +} + + +resource "kubernetes_service" "collabora_office" { + metadata { + name = "http" + namespace = kubernetes_namespace.collabora.metadata[0].name + } + spec { + port { + port = 80 + target_port = "http" + } + selector = { + app = "collabora" + } + cluster_ip = "None" + } +} + +resource "kubernetes_ingress_v1" "collabora_ingress" { + depends_on = [ + kubernetes_stateful_set.collabora_office + ] + metadata { + name = "collabora-ingress" + namespace = kubernetes_namespace.collabora.metadata[0].name + + annotations = { + "kubernetes.io/ingress.class" = "public" + "cert-manager.io/cluster-issuer" = "letsencrypt-prod" + "kubernetes.io/tls-acme" = "true" + "nginx.ingress.kubernetes.io/backend-protocol" = "HTTPS" + } + } + spec { + tls { + hosts = [local.collabora_fqdn] + secret_name = "collabora-tls" + } + rule { + host = local.collabora_fqdn + http { + path { + backend { + service { + name = "http" + port { + number = 80 + } + } + } + } + } + } + } +} + + +resource "kubernetes_stateful_set" "collabora_office" { + + metadata { + name = "collabora" + namespace = kubernetes_namespace.collabora.metadata[0].name + } + + spec { + service_name = "collabora" + selector { + match_labels = { + app = "collabora" + } + } + template { + metadata { + annotations = { + "cluster-autoscaler.kubernetes.io/safe-to-evict" = "true" + } + labels = { + app = "collabora" + } + } + spec { + termination_grace_period_seconds = 60 + service_account_name = kubernetes_service_account.collabora_service_account.metadata[0].name + container { + name = "collabora" + image = "collabora/code:${local.collabora_version}" + image_pull_policy = "IfNotPresent" + port { + name = "http" + container_port = 9980 + protocol = "TCP" + } + startup_probe { + tcp_socket { + # path = "/" + port = 9980 + # scheme = "HTTP" + } + failure_threshold = 30 + period_seconds = 3 + } + liveness_probe { + tcp_socket { + # path = "/" + port = 9980 + # scheme = "HTTP" + } + initial_delay_seconds = 0 + period_seconds = 30 + timeout_seconds = 30 + success_threshold = 1 + failure_threshold = 4 + } + readiness_probe { + tcp_socket { + # path = "/" + port = 9980 + # scheme = "HTTP" + } + initial_delay_seconds = 0 + period_seconds = 30 + timeout_seconds = 30 + success_threshold = 1 + failure_threshold = 2 + } + env_from { + config_map_ref { + name = kubernetes_config_map.collabora.metadata[0].name + } + } + env { + name = "username" + value_from { + secret_key_ref { + name = kubernetes_secret.collabora_admin_credentials.metadata[0].name + key = "username" + } + } + } + env { + name = "password" + value_from { + secret_key_ref { + name = kubernetes_secret.collabora_admin_credentials.metadata[0].name + key = "password" + } + } + } + volume_mount { + name = "tmp" + mount_path = "/tmp" + } + } + volume { + name = "tmp" + empty_dir { + } + } + } + } + } +} diff --git a/terraform/k8apps/config.tf b/terraform/k8apps/config.tf new file mode 100644 index 0000000..4252430 --- /dev/null +++ b/terraform/k8apps/config.tf @@ -0,0 +1,40 @@ +locals { + + # apps + wordpress_version = "6.5.2" + wordpress_fqdn = "blog.alexpires.me" + wordpress_sql_backup_cron_schedule = "0 2 * * *" + wordpress_files_backup_cron_schedule = "10 2 * * *" + + gitea_version = "1.21.3" + gitea_fqdn = "code.alexpires.me" + gitea_sql_backup_cron_schedule = "20 2 * * *" + gitea_files_backup_cron_schedule = "30 2 * * *" + + nextcloud_version = "28.0.7-apache" + nextcloud_fqdn = "cloud.alexpires.me" + nextcloud_sql_backup_cron_schedule = "40 2 * * *" + nextcloud_files_backup_cron_schedule = "50 2 * * *" + + # collabora + collabora_fqdn = "office.alexpires.me" + collabora_version = "24.04.1.4.1" + + # tvheadend + m3uproxy_version = "latest" + wireguard_version = "latest" + m3uproxy_fqdn = "tv.alexpires.me" + + # backup + local_backup_retention_days = 2 + cloud_backup_retention_days = 3 + + # notification + scaleway_smtp_host = "smtp.tem.scw.cloud" + scaleway_smtp_port = 587 +} + +data "scaleway_account_project" "default" { + project_id = var.scaleway_project_id + organization_id = var.scaleway_organization_id +} diff --git a/terraform/k8apps/gitea.tf b/terraform/k8apps/gitea.tf new file mode 100644 index 0000000..fc23a63 --- /dev/null +++ b/terraform/k8apps/gitea.tf @@ -0,0 +1,352 @@ +locals { + gitea_env_vars = { + APP_NAME = "A13Labs git repository" + HTTP_PORT = "3000" + RUN_MODE = "prod" + LFS_START_SERVER = "true" + SSH_DOMAIN = local.gitea_fqdn + DISABLE_REGISTRATION = true + INSTALL_LOCK = "true" + DB_TYPE = "mysql" + DB_NAME = "gitea" + DB_HOST = module.gitea_database.host + SSH_LISTEN_PORT = 22 + GITEA__mailer__ENABLED = true + GITEA__mailer__FROM = "gitea@alexpires.me" + GITEA__mailer__SMTP_ADDR = local.scaleway_smtp_host + GITEA__mailer__SMTP_PORT = local.scaleway_smtp_port + GITEA__mailer__PROTOCOL = "smtps" + } + + gitea_secrets = { + DB_USER = { + key = "username" + name = module.gitea_database.app_crendentials_name + } + DB_PASSWD = { + key = "password" + name = module.gitea_database.app_crendentials_name + } + GITEA__mailer__USER = { + key = "username" + name = kubernetes_secret.gitea_mail_credentials.metadata[0].name + } + GITEA__mailer__PASSWD = { + key = "password" + name = kubernetes_secret.gitea_mail_credentials.metadata[0].name + } + } + + gitea_path = "/var/lib/gitea" + gitea_mysql_path = "/var/lib/mysql.gitea" +} + +# +# Namespace +# +resource "kubernetes_namespace" "gitea" { + metadata { + annotations = { + name = "gitea" + } + + labels = { + name = "gitea" + } + + name = "gitea" + } +} + +# +# Mail credentials +# +resource "kubernetes_secret" "gitea_mail_credentials" { + metadata { + name = "gitea-mail-credentials" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + data = { + username = var.scaleway_project_id + password = scaleway_iam_api_key.mail_app_api.secret_key + } + type = "Opaque" +} + +# +# Setup MariaDB database +# +module "gitea_database" { + source = "./modules/k8s_mariadb" + + namespace = kubernetes_namespace.gitea.metadata[0].name + app_name = "gitea" + app_backup_folder = "gitea.backups" + + app_password = var.gitea_mysql_password + root_password = var.mysql_root_password + + retention_days = local.local_backup_retention_days + cron_schedule = local.gitea_sql_backup_cron_schedule + host_storage_path = local.gitea_mysql_path +} + +# +# Setup rsync backup job to scaleway bucket +# +module "gitea_backup" { + source = "./modules/scw_backup_job" + + namespace = kubernetes_namespace.gitea.metadata[0].name + app_name = "gitea" + app_files_folder = "gitea" + app_backup_folder = "mysql.gitea/gitea.backups" + + scaleway_organization_id = var.scaleway_organization_id + scaleway_project_id = var.scaleway_organization_id + access_key = scaleway_iam_api_key.backup_app_api.access_key + secret_key = scaleway_iam_api_key.backup_app_api.secret_key + bucket_name = scaleway_object_bucket.backup.name + + retention_days = local.local_backup_retention_days + cron_schedule = local.gitea_files_backup_cron_schedule +} + +# +# Gitea service, ingress, deployment and file backup job +# +resource "kubernetes_service_account" "gitea_service_account" { + + metadata { + name = "gitea-app-sa" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + + automount_service_account_token = false +} + +resource "kubernetes_service" "gitea_http" { + metadata { + name = "http" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + spec { + port { + port = 80 + target_port = "http" + } + selector = { + app = "gitea" + } + cluster_ip = "None" + } +} + +resource "kubernetes_service" "gitea_ssh" { + metadata { + name = "ssh" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + spec { + port { + port = 22 + target_port = "ssh" + node_port = 30036 + } + selector = { + app = "gitea" + } + type = "NodePort" + } +} + +resource "kubernetes_ingress_v1" "gitea_ingress" { + metadata { + name = "gitea-ingress" + namespace = kubernetes_namespace.gitea.metadata[0].name + + annotations = { + "kubernetes.io/ingress.class" = "public" + "cert-manager.io/cluster-issuer" = "letsencrypt-prod" + "nginx.ingress.kubernetes.io/ssl-redirect" = "true" + "nginx.ingress.kubernetes.io/proxy-body-size" = "8m" + "nginx.ingress.kubernetes.io/proxy-request-buffering" = "on" + "nginx.ingress.kubernetes.io/proxy-buffer-size" = "32k" + "nginx.ingress.kubernetes.io/proxy-buffers-number" = "16" + } + } + spec { + tls { + hosts = [local.gitea_fqdn] + secret_name = "gitea-tls" + } + rule { + host = local.gitea_fqdn + http { + path { + backend { + service { + name = "http" + port { + number = 80 + } + } + } + } + } + } + } +} + +resource "kubernetes_deployment" "gitea" { + + depends_on = [module.gitea_database] + + metadata { + name = "gitea-app" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + spec { + selector { + match_labels = { + app = "gitea" + } + } + strategy { + type = "Recreate" + } + template { + metadata { + labels = { + app = "gitea" + } + } + spec { + service_account_name = kubernetes_service_account.gitea_service_account.metadata[0].name + container { + name = "gitea" + image = "gitea/gitea:${local.gitea_version}" + image_pull_policy = "Always" + + resources { + requests = { + memory = "100Mi" + } + limits = { + memory = "300Mi" + } + } + + liveness_probe { + http_get { + path = "/api/healthz" + port = "http" + } + initial_delay_seconds = 200 + period_seconds = 10 + success_threshold = 1 + failure_threshold = 5 + } + + readiness_probe { + tcp_socket { + port = "http" + } + initial_delay_seconds = 5 + period_seconds = 10 + success_threshold = 1 + failure_threshold = 3 + } + + dynamic "env" { + for_each = local.gitea_env_vars + content { + name = env.key + value = env.value + } + } + + dynamic "env" { + for_each = local.gitea_secrets + content { + name = env.key + value_from { + secret_key_ref { + key = env.value.key + name = env.value.name + } + } + } + } + + port { + container_port = 3000 + name = "http" + } + port { + container_port = 22 + name = "ssh" + } + + volume_mount { + mount_path = "/data" + name = "persistent-storage" + } + } + volume { + name = "persistent-storage" + host_path { + path = local.gitea_path + } + } + } + } + } +} + +resource "kubernetes_role" "gitea_ssh_access" { + metadata { + name = "gitea-ssh-access" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + rule { + api_groups = [""] + resources = ["pods", "pods/log"] + verbs = ["get", "list"] + } + rule { + api_groups = [""] + resources = ["pods/exec"] + verbs = ["create"] + } +} + +resource "kubernetes_role_binding" "gitea_ssh_access" { + metadata { + name = "gitea-ssh-access" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "Role" + name = kubernetes_role.gitea_ssh_access.metadata[0].name + } + subject { + kind = "ServiceAccount" + name = kubernetes_service_account.gitea_service_account.metadata[0].name + namespace = kubernetes_namespace.gitea.metadata[0].name + } +} + +resource "kubernetes_secret" "gitea_access_token" { + metadata { + annotations = { + "kubernetes.io/service-account.name" = kubernetes_service_account.gitea_service_account.metadata[0].name + } + name = "git-token" + namespace = kubernetes_namespace.gitea.metadata[0].name + } + + type = "kubernetes.io/service-account-token" + wait_for_service_account_token = true +} diff --git a/terraform/k8apps/iam.tf b/terraform/k8apps/iam.tf new file mode 100644 index 0000000..7fb624e --- /dev/null +++ b/terraform/k8apps/iam.tf @@ -0,0 +1,72 @@ +resource "scaleway_iam_application" "backup_app" { + name = "k8s_backup_app" +} + +resource "scaleway_iam_application" "mail_app" { + name = "mail_app" +} + +resource "scaleway_iam_application" "nextcloud_app" { + name = "nextcloud_app" +} + +resource "scaleway_iam_policy" "object_read_write_backup" { + name = "K8s backup app policy" + description = "Gives k8s backup job access to object storage in project" + application_id = scaleway_iam_application.backup_app.id + + rule { + project_ids = [data.scaleway_account_project.default.id] + permission_set_names = [ + "ObjectStorageObjectsWrite", + "ObjectStorageObjectsRead", + "ObjectStorageObjectsDelete", + "ObjectStorageBucketsRead" + ] + } +} + +resource "scaleway_iam_policy" "email_full_access" { + name = "Email full access policy" + description = "Gives access to transactional email in project" + application_id = scaleway_iam_application.mail_app.id + + rule { + project_ids = [data.scaleway_account_project.default.id] + permission_set_names = [ + "TransactionalEmailFullAccess", + ] + } +} + +resource "scaleway_iam_policy" "object_read_write_nextcloud" { + name = "Nextcloud app policy" + description = "Gives nextcloud job access to object storage in project" + application_id = scaleway_iam_application.nextcloud_app.id + + rule { + project_ids = [data.scaleway_account_project.default.id] + permission_set_names = [ + "ObjectStorageObjectsWrite", + "ObjectStorageObjectsRead", + "ObjectStorageBucketsRead", + "ObjectStorageBucketsWrite", + "ObjectStorageObjectsDelete" + ] + } +} + +resource "scaleway_iam_api_key" "backup_app_api" { + application_id = scaleway_iam_application.backup_app.id + description = "k8s backup app API key" +} + +resource "scaleway_iam_api_key" "mail_app_api" { + application_id = scaleway_iam_application.mail_app.id + description = "mail access API key" +} + +resource "scaleway_iam_api_key" "nextcloud_app_api" { + application_id = scaleway_iam_application.nextcloud_app.id + description = "nextcloud access API key" +} diff --git a/terraform/k8apps/m3uproxy.tf b/terraform/k8apps/m3uproxy.tf new file mode 100644 index 0000000..8a1576b --- /dev/null +++ b/terraform/k8apps/m3uproxy.tf @@ -0,0 +1,446 @@ +locals { + m3uproxy_path = "/var/lib/m3uproxy" + m3uproxy_cache_path = "${local.m3uproxy_path}/cache" + squid_app_path = "${local.m3uproxy_path}/squid" + wireguard_address = "192.168.5.3/32" + wireguard_dns = "192.168.5.1" + wireguard_allowed_ips = [ + "192.168.5.1/32", + "192.168.5.3/32", + "0.0.0.0/0" + ] + wireguard_endpoint = "188.82.170.183" + wireguard_endpoint_port = 1195 + + m3uproxy_config = templatefile("${path.module}/resources/m3uproxy/m3uproxy.json", { + m3uproxy_secret = var.m3uproxy_secret + }) + +} + +resource "kubernetes_namespace" "m3uproxy" { + metadata { + annotations = { + name = "m3uproxy" + } + + labels = { + name = "m3uproxy" + } + + name = "m3uproxy" + } + +} + +resource "kubernetes_secret" "m3uproxy_admin_credentials" { + metadata { + name = "m3uproxy-credentials" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + data = { + username = "tv_admin" + password = var.m3uproxy_admin_password + } + type = "Opaque" +} + + +resource "kubernetes_service_account" "m3uproxy_service_account" { + + metadata { + name = "m3uproxy-app-sa" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + automount_service_account_token = false +} + +resource "kubernetes_config_map" "wireguard_config" { + metadata { + name = "wireguard-config" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + data = { + "wireguard.conf" = templatefile("${path.module}/resources/wireguard/wireguard.conf.tpl", { + wireguard_private_key = var.wireguard_private_key + wireguard_public_key = var.wireguard_public_key + wireguard_address = local.wireguard_address + wireguard_dns = local.wireguard_dns + wireguard_allowed_ips = join(", ", local.wireguard_allowed_ips) + wireguard_endpoint = "${local.wireguard_endpoint}:${local.wireguard_endpoint_port}" + }) + } +} + +resource "kubernetes_config_map" "squid_config" { + metadata { + name = "squid-config" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + data = { + "squid.conf" = file("${path.module}/resources/squid/squid.conf") + } + +} + +resource "kubernetes_config_map" "m3uproxy_config" { + metadata { + name = "m3uproxy-config" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + data = { + "m3uproxy.json" = local.m3uproxy_config + "playlist.json" = file("${path.module}/resources/m3uproxy/playlist.json") + } +} + +resource "kubernetes_deployment" "proxy_pt" { + + metadata { + name = "proxy-pt" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + annotations = { + "security.alpha.kubernetes.io/unsafe-sysctls" = "net.ipv4.conf.all.src_valid_mark=1" + } + } + + spec { + replicas = 1 + + selector { + match_labels = { + app = "proxy-pt" + } + } + + strategy { + type = "Recreate" + } + + template { + metadata { + labels = { + app = "proxy-pt" + } + } + + spec { + container { + name = "wireguard" + image = "linuxserver/wireguard:${local.wireguard_version}" + + resources { + requests = { + memory = "64Mi" + } + } + + liveness_probe { + exec { + command = [ + "/bin/sh", + "-c", + "wg show | grep -q transfer" + ] + } + initial_delay_seconds = 90 + period_seconds = 90 + } + + security_context { + privileged = true + capabilities { + add = ["NET_ADMIN"] + } + } + + env { + name = "PUID" + value = "1000" + } + + env { + name = "PGID" + value = "1000" + } + + env { + name = "TZ" + value = "Europe/Berlin" + } + + volume_mount { + name = "wireguard-config" + mount_path = "/config/wg_confs" + read_only = true + } + + volume_mount { + name = "lib-modules" + mount_path = "/lib/modules" + read_only = true + } + } + + container { + name = "squid" + image = "ubuntu/squid:edge" + + port { + container_port = 3128 + name = "proxy" + protocol = "TCP" + } + + volume_mount { + name = "squid-config" + mount_path = "/etc/squid" + read_only = true + } + + volume_mount { + name = "squid-data" + mount_path = "/var/spol/squid" + } + + } + + volume { + name = "wireguard-config" + config_map { + name = kubernetes_config_map.wireguard_config.metadata[0].name + } + } + + volume { + name = "lib-modules" + host_path { + path = "/lib/modules" + } + } + + volume { + name = "squid-config" + config_map { + name = kubernetes_config_map.squid_config.metadata[0].name + } + } + + volume { + name = "squid-data" + host_path { + path = local.squid_app_path + } + } + } + } + } + + lifecycle { + replace_triggered_by = [ + kubernetes_config_map.wireguard_config, + kubernetes_config_map.squid_config + ] + } +} + + +# Create the Deployment +resource "kubernetes_deployment" "m3uproxy" { + metadata { + name = "m3uproxy" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + spec { + replicas = 1 + + selector { + match_labels = { + app = "m3uproxy" + } + } + + strategy { + type = "RollingUpdate" + } + + template { + metadata { + labels = { + app = "m3uproxy" + } + } + + spec { + service_account_name = kubernetes_service_account.m3uproxy_service_account.metadata[0].name + + + container { + name = "m3uproxy" + image = "a13labs/m3uproxy:${local.m3uproxy_version}" + image_pull_policy = "Always" + + resources { + requests = { + memory = "256Mi" + } + } + + port { + container_port = 8080 + name = "http" + } + + readiness_probe { + http_get { + path = "/health" + port = "http" + } + initial_delay_seconds = 5 + period_seconds = 10 + failure_threshold = 10 + } + + env { + name = "USERNAME" + value_from { + secret_key_ref { + name = kubernetes_secret.m3uproxy_admin_credentials.metadata[0].name + key = "username" + } + } + } + + env { + name = "PASSWORD" + value_from { + secret_key_ref { + name = kubernetes_secret.m3uproxy_admin_credentials.metadata[0].name + key = "password" + } + } + } + + security_context { + run_as_user = 1000 + run_as_group = 1000 + } + + volume_mount { + name = "m3uproxy-config" + mount_path = "/app/conf" + read_only = true + } + + volume_mount { + name = "m3uproxy-app-cache" + mount_path = "/app/cache" + } + + volume_mount { + name = "geoip-db" + mount_path = "/app/GeoIP" + read_only = true + } + } + + volume { + name = "m3uproxy-config" + config_map { + name = kubernetes_config_map.m3uproxy_config.metadata[0].name + } + } + + volume { + name = "m3uproxy-app-cache" + host_path { + path = local.m3uproxy_cache_path + } + } + + volume { + name = "geoip-db" + host_path { + path = "/var/lib/GeoIP" + } + } + + } + } + } +} + +resource "kubernetes_service" "m3uproxy" { + metadata { + name = "http" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + spec { + selector = { + app = "m3uproxy" + } + port { + port = 8080 + target_port = "http" + } + cluster_ip = "None" + } +} + +resource "kubernetes_service" "proxy_pt" { + metadata { + name = "proxy-pt" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + } + + spec { + selector = { + app = "proxy-pt" + } + port { + port = 3128 + } + cluster_ip = "None" + } +} + +resource "kubernetes_ingress_v1" "m3uproxy_ingress_http" { + metadata { + name = "m3uproxy-ingress" + namespace = kubernetes_namespace.m3uproxy.metadata[0].name + + annotations = { + "kubernetes.io/ingress.class" = "public" + "cert-manager.io/cluster-issuer" = "letsencrypt-prod" + "kubernetes.io/tls-acme" = "true" + "nginx.ingress.kubernetes.io/ssl-redirect" = "true" + } + } + spec { + tls { + hosts = [local.m3uproxy_fqdn] + secret_name = "m3uproxy-tls" + } + rule { + host = local.m3uproxy_fqdn + http { + path { + backend { + service { + name = "http" + port { + number = 8080 + } + } + } + } + } + } + } +} diff --git a/terraform/k8apps/modules/k8s_mariadb/main.tf b/terraform/k8apps/modules/k8s_mariadb/main.tf new file mode 100644 index 0000000..df0fa60 --- /dev/null +++ b/terraform/k8apps/modules/k8s_mariadb/main.tf @@ -0,0 +1,241 @@ +resource "kubernetes_secret" "root_password" { + metadata { + name = "${var.app_name}-root-credentials" + namespace = var.namespace + } + data = { + username = "root" + password = var.root_password + } + type = "Opaque" +} + +resource "kubernetes_service_account" "service_account" { + + metadata { + name = "${var.app_name}-mariadb-sa" + namespace = var.namespace + } + + automount_service_account_token = false +} + +resource "kubernetes_secret" "app_password" { + metadata { + name = "${var.app_name}-app-credentials" + namespace = var.namespace + } + data = { + username = var.app_name + password = var.app_password + } + type = "Opaque" +} + +resource "kubernetes_service" "service" { + metadata { + name = "mariadb" + namespace = var.namespace + } + spec { + port { + port = 3306 + } + selector = { + app = "mariadb" + } + cluster_ip = "None" + } +} + +resource "kubernetes_deployment" "deployment" { + metadata { + name = "mariadb" + namespace = var.namespace + } + spec { + selector { + match_labels = { + app = "mariadb" + } + } + strategy { + type = "Recreate" + } + template { + metadata { + labels = { + app = "mariadb" + } + } + spec { + service_account_name = kubernetes_service_account.service_account.metadata[0].name + container { + name = "mariadb" + image = "mariadb:${var.current_version}" + + resources { + requests = { + memory = "100Mi" + } + } + + liveness_probe { + exec { + command = ["sh", "-c", "mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] + } + initial_delay_seconds = 30 + period_seconds = 10 + timeout_seconds = 1 + failure_threshold = 15 + } + + readiness_probe { + exec { + command = ["sh", "-c", "mysqladmin status -uroot -p$MARIADB_ROOT_PASSWORD"] + } + initial_delay_seconds = 120 + period_seconds = 10 + timeout_seconds = 1 + failure_threshold = 15 + } + + env { + name = "MARIADB_ROOT_PASSWORD" + value_from { + secret_key_ref { + key = "password" + name = kubernetes_secret.root_password.metadata[0].name + } + } + } + env { + name = "MARIADB_ROOT_HOST" + value = "10.%" + } + env { + name = "MARIADB_USER" + value_from { + secret_key_ref { + key = "username" + name = kubernetes_secret.app_password.metadata[0].name + } + } + } + env { + name = "MARIADB_PASSWORD" + value_from { + secret_key_ref { + key = "password" + name = kubernetes_secret.app_password.metadata[0].name + } + } + } + env { + name = "MARIADB_DATABASE" + value = var.app_name + } + port { + container_port = 3306 + name = "mariadb" + } + volume_mount { + mount_path = "/var/lib/mysql" + name = "persistent-storage" + } + volume_mount { + mount_path = "/docker-entrypoint-initdb.d" + name = "initdb-storage" + } + } + volume { + name = "persistent-storage" + host_path { + path = "${var.host_storage_path}/${var.app_name}.data" + } + } + volume { + name = "initdb-storage" + host_path { + path = "${var.host_storage_path}/${var.app_name}.initdb.d" + } + } + } + } + } +} + +# TODO: This should move out of here +resource "kubernetes_cron_job_v1" "backup_data_cron" { + metadata { + name = "mariadb-backup-job" + namespace = var.namespace + } + spec { + concurrency_policy = "Forbid" + schedule = var.cron_schedule + failed_jobs_history_limit = 2 + successful_jobs_history_limit = 2 + job_template { + metadata { + labels = { + app = "mariadb" + } + } + spec { + backoff_limit = 1 + parallelism = 1 + completions = 1 + active_deadline_seconds = 1800 + template { + metadata {} + spec { + container { + name = "sql-cron-job" + image = "mariadb:${var.current_version}" + env { + name = "MARIADB_USER" + value_from { + secret_key_ref { + key = "username" + name = kubernetes_secret.app_password.metadata[0].name + } + } + } + env { + name = "MARIADB_PASSWORD" + value_from { + secret_key_ref { + key = "password" + name = kubernetes_secret.app_password.metadata[0].name + } + } + } + env { + name = "MARIADB_DATABASE" + value = var.app_name + } + command = ["/bin/sh", "-c"] + args = [join(";", [ + "/usr/bin/mariadb-dump --single-transaction -h mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE | gzip > /backups/${var.app_name}-sql-`date +\"%Y%m%d\"`.sql.gz", + "find /backups -type f -name '${var.app_name}-sql-*.sql.gz' -mtime +${var.retention_days} | xargs --no-run-if-empty rm" + ])] + volume_mount { + mount_path = "/backups" + name = "backups-storage" + } + } + volume { + name = "backups-storage" + host_path { + path = "${var.host_storage_path}/${var.app_backup_folder}" + } + } + restart_policy = "Never" + termination_grace_period_seconds = 30 + } + } + } + } + } +} diff --git a/terraform/k8apps/modules/k8s_mariadb/outputs.tf b/terraform/k8apps/modules/k8s_mariadb/outputs.tf new file mode 100644 index 0000000..4940974 --- /dev/null +++ b/terraform/k8apps/modules/k8s_mariadb/outputs.tf @@ -0,0 +1,15 @@ +output "host" { + value = "mariadb" +} + +output "port" { + value = 3306 +} + +output "root_crendentials_name" { + value = kubernetes_secret.root_password.metadata[0].name +} + +output "app_crendentials_name" { + value = kubernetes_secret.app_password.metadata[0].name +} diff --git a/terraform/k8apps/modules/k8s_mariadb/provider.tf b/terraform/k8apps/modules/k8s_mariadb/provider.tf new file mode 100644 index 0000000..d1b0a1a --- /dev/null +++ b/terraform/k8apps/modules/k8s_mariadb/provider.tf @@ -0,0 +1,11 @@ +terraform { + required_version = "~>1.8" + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~>2.18" + } + } +} + + diff --git a/terraform/k8apps/modules/k8s_mariadb/variables.tf b/terraform/k8apps/modules/k8s_mariadb/variables.tf new file mode 100644 index 0000000..b4541ca --- /dev/null +++ b/terraform/k8apps/modules/k8s_mariadb/variables.tf @@ -0,0 +1,52 @@ +variable "namespace" { + description = "namespace" + sensitive = false + type = string +} + +variable "app_name" { + description = "database name" + sensitive = false + type = string +} + +variable "root_password" { + description = "root password" + sensitive = true + type = string +} + +variable "app_password" { + description = "user password" + sensitive = true + type = string +} + +variable "host_storage_path" { + description = "host storage path" + default = "/srv" + type = string +} + +variable "cron_schedule" { + description = "Cronjob schedule for data backups" + default = "* 2 * * *" + type = string +} + +variable "retention_days" { + description = "Backup retention days" + default = 14 + type = number +} + +variable "current_version" { + description = "version" + default = "10.7.8" + type = string +} + +variable "app_backup_folder" { + description = "Backup source location" + type = string +} diff --git a/terraform/k8apps/modules/scw_backup_job/config.tf b/terraform/k8apps/modules/scw_backup_job/config.tf new file mode 100644 index 0000000..48569eb --- /dev/null +++ b/terraform/k8apps/modules/scw_backup_job/config.tf @@ -0,0 +1,3 @@ +locals { + rclone_version = "1.61.1" +} \ No newline at end of file diff --git a/terraform/k8apps/modules/scw_backup_job/main.tf b/terraform/k8apps/modules/scw_backup_job/main.tf new file mode 100644 index 0000000..8ac4a47 --- /dev/null +++ b/terraform/k8apps/modules/scw_backup_job/main.tf @@ -0,0 +1,164 @@ +resource "kubernetes_service_account" "service_account" { + metadata { + name = "${var.app_name}-backup-sa" + namespace = var.namespace + } + + automount_service_account_token = false +} + +resource "kubernetes_secret" "scaleway_api_credentials" { + metadata { + name = "${var.app_name}-scaleway-api-credentials" + namespace = var.namespace + } + data = { + access_key = var.access_key + secret_key = var.secret_key + } + type = "Opaque" +} + +resource "kubernetes_config_map" "rclone_config" { + metadata { + name = "rclone-config" + namespace = var.namespace + } + data = { + "rclone.conf" = file("${path.module}/resources/rclone.conf") + } +} + +resource "kubernetes_cron_job_v1" "backup_job" { + metadata { + name = "${var.app_name}-backup" + namespace = var.namespace + } + spec { + concurrency_policy = "Forbid" + schedule = var.cron_schedule + failed_jobs_history_limit = 2 + successful_jobs_history_limit = 2 + job_template { + metadata { + labels = { + app = "${var.app_name}-backup" + } + } + spec { + backoff_limit = 1 + parallelism = 1 + completions = 1 + active_deadline_seconds = 1800 + template { + metadata { + labels = { + app = "${var.app_name}-backup" + } + } + spec { + service_account_name = kubernetes_service_account.service_account.metadata[0].name + security_context { + run_as_user = 0 + } + container { + + name = "${var.app_name}-backup" + image = "rclone/rclone:${local.rclone_version}" + image_pull_policy = "Always" + + resources { + requests = { + memory = "100Mi" + } + } + + env { + name = "SCW_ACCESS_KEY" + value_from { + secret_key_ref { + key = "access_key" + name = kubernetes_secret.scaleway_api_credentials.metadata[0].name + } + } + } + env { + name = "SCW_SECRET_KEY" + value_from { + secret_key_ref { + key = "secret_key" + name = kubernetes_secret.scaleway_api_credentials.metadata[0].name + } + } + } + env { + name = "SCW_DEFAULT_PROJECT_ID" + value = var.scaleway_project_id + } + env { + name = "SCW_DEFAULT_ORGANIZATION_ID" + value = var.scaleway_organization_id + } + env { + name = "SCW_DEFAULT_REGION" + value = var.scaleway_region + } + env { + name = "SCW_DEFAULT_ZONE" + value = var.scaleway_zone + } + env { + name = "BUCKET_NAME" + value = var.bucket_name + } + command = ["/bin/sh", "-c"] + args = [join(";", [ + "apk update", + "apk add gettext", + "envsubst/etc/rclone.conf", + "tar -zcvpf /backup/${var.app_name}-files-`date +\"%Y%m%d\"`.tar.gz /files", + "find /backup -type f -name '${var.app_name}-files-*.tar.gz' -mtime +${var.retention_days} | xargs --no-run-if-empty rm", + "/usr/local/bin/rclone --config=/etc/rclone.conf sync --progress /backup scaleway:$BUCKET_NAME/${var.app_name}-backup" + ])] + volume_mount { + name = "backup-storage" + mount_path = "/backup" + } + volume_mount { + name = "persistent-storage" + mount_path = "/files" + read_only = true + } + volume_mount { + name = "rclone-conf" + mount_path = "/etc/rclone.conf.tmpl" + sub_path = "rclone.conf" + read_only = true + } + } + volume { + name = "backup-storage" + host_path { + path = "${var.host_storage_path}/${var.app_backup_folder}" + } + } + volume { + name = "persistent-storage" + host_path { + path = "${var.host_storage_path}/${var.app_files_folder}" + } + } + volume { + name = "rclone-conf" + config_map { + name = kubernetes_config_map.rclone_config.metadata[0].name + } + } + restart_policy = "Never" + termination_grace_period_seconds = 30 + } + } + } + } + } +} diff --git a/terraform/k8apps/modules/scw_backup_job/provider.tf b/terraform/k8apps/modules/scw_backup_job/provider.tf new file mode 100644 index 0000000..d1b0a1a --- /dev/null +++ b/terraform/k8apps/modules/scw_backup_job/provider.tf @@ -0,0 +1,11 @@ +terraform { + required_version = "~>1.8" + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = "~>2.18" + } + } +} + + diff --git a/terraform/k8apps/modules/scw_backup_job/resources/rclone.conf b/terraform/k8apps/modules/scw_backup_job/resources/rclone.conf new file mode 100644 index 0000000..6ae9387 --- /dev/null +++ b/terraform/k8apps/modules/scw_backup_job/resources/rclone.conf @@ -0,0 +1,10 @@ +[scaleway] +type = s3 +provider = Scaleway +env_auth = false +access_key_id = $SCW_ACCESS_KEY +secret_access_key = $SCW_SECRET_KEY +region = $SCW_DEFAULT_REGION +endpoint = s3.$SCW_DEFAULT_REGION.scw.cloud +acl = private +storage_class = STANDARD \ No newline at end of file diff --git a/terraform/k8apps/modules/scw_backup_job/variables.tf b/terraform/k8apps/modules/scw_backup_job/variables.tf new file mode 100644 index 0000000..05bb10d --- /dev/null +++ b/terraform/k8apps/modules/scw_backup_job/variables.tf @@ -0,0 +1,76 @@ +variable "bucket_name" { + description = "Backup bucket name" + type = string +} + +variable "namespace" { + description = "Namespace" + type = string +} + +variable "scaleway_project_id" { + description = "Scaleway project id" + sensitive = true + type = string +} + +variable "scaleway_organization_id" { + description = "Scaleway organization id" + sensitive = true + type = string +} + +variable "scaleway_region" { + description = "Scaleway region" + default = "fr-par" + type = string +} + +variable "scaleway_zone" { + default = "fr-par-2" + type = string +} + +variable "cron_schedule" { + description = "Cronjob schedule for data backups" + default = "* 2 * * *" + type = string +} + +variable "host_storage_path" { + description = "Host path" + default = "/var/lib" + type = string +} + +variable "access_key" { + description = "Access key" + sensitive = true + type = string +} + +variable "secret_key" { + description = "Secret key" + sensitive = true + type = string +} + +variable "app_name" { + description = "Name of the app to backup" + type = string +} + +variable "app_files_folder" { + description = "Name of host folder" + type = string +} + +variable "app_backup_folder" { + description = "Backup source location" + type = string +} + +variable "retention_days" { + description = "backup retention days" + type = string +} diff --git a/terraform/k8apps/nextcloud.tf b/terraform/k8apps/nextcloud.tf new file mode 100644 index 0000000..e071b1f --- /dev/null +++ b/terraform/k8apps/nextcloud.tf @@ -0,0 +1,492 @@ +locals { + nextcloud_env_vars = { + # Database configuration + MYSQL_DATABASE = "nextcloud" + MYSQL_HOST = module.nextcloud_database.host + + # Email configuration + SMTP_HOST = local.scaleway_smtp_host + SMTP_SECURE = "ssl" + SMTP_PORT = 465 + MAIL_FROM_ADDRESS = "cloud" + MAIL_DOMAIN = "alexpires.me" + SMTP_AUTHTYPE = "PLAIN" + + # Redis configuration + REDIS_HOST = "redis" + + # Object as primary storage + OBJECTSTORE_S3_HOST = "s3.${scaleway_object_bucket.nextcloud.region}.scw.cloud" + OBJECTSTORE_S3_BUCKET = scaleway_object_bucket.nextcloud.name + OBJECTSTORE_S3_PORT = 443 + OBJECTSTORE_S3_SSL = true + OBJECTSTORE_S3_REGION = scaleway_object_bucket.nextcloud.region + OBJECTSTORE_S3_AUTOCREATE = false + + # General + NEXTCLOUD_TRUSTED_DOMAINS = local.nextcloud_fqdn + TRUSTED_PROXIES = join(" ", ["10.1.0.0/16"]) + OVERWRITEHOST = local.nextcloud_fqdn + OVERWRITEPROTOCOL = "https" + OVERWRITECLIURL = "https://${local.nextcloud_fqdn}" + } + + nextcloud_secrets = { + + OBJECTSTORE_S3_KEY = { + key = "access_key" + name = kubernetes_secret.nextcloud_object_store_credentials.metadata[0].name + } + + OBJECTSTORE_S3_SECRET = { + key = "secret_key" + name = kubernetes_secret.nextcloud_object_store_credentials.metadata[0].name + } + + SMTP_NAME = { + key = "username" + name = kubernetes_secret.nextcloud_mail_credentials.metadata[0].name + } + + SMTP_PASSWORD = { + key = "password" + name = kubernetes_secret.nextcloud_mail_credentials.metadata[0].name + } + + MYSQL_USER = { + key = "username" + name = module.nextcloud_database.app_crendentials_name + } + + MYSQL_PASSWORD = { + key = "password" + name = module.nextcloud_database.app_crendentials_name + } + + NEXTCLOUD_ADMIN_USER = { + key = "username" + name = kubernetes_secret.nextcloud_admin_credentials.metadata[0].name + } + + NEXTCLOUD_ADMIN_PASSWORD = { + key = "password" + name = kubernetes_secret.nextcloud_admin_credentials.metadata[0].name + } + } + + nextcloud_path = "/var/lib/nextcloud" + nextcloud_mysql_path = "/var/lib/mysql.nextcloud" + +} + +# +# Namespace +# +resource "kubernetes_namespace" "nextcloud" { + metadata { + annotations = { + name = "nextcloud" + } + + labels = { + name = "nextcloud" + } + + name = "nextcloud" + } +} + +# +# Nextcloud required credentials +# +resource "kubernetes_secret" "nextcloud_mail_credentials" { + metadata { + name = "nextcloud-mail-credentials" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + data = { + username = var.scaleway_project_id + password = scaleway_iam_api_key.mail_app_api.secret_key + } + type = "Opaque" +} + +resource "kubernetes_secret" "nextcloud_object_store_credentials" { + metadata { + name = "nextcloud-object-store-credentials" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + data = { + access_key = scaleway_iam_api_key.nextcloud_app_api.access_key + secret_key = scaleway_iam_api_key.nextcloud_app_api.secret_key + } + type = "Opaque" +} + +resource "kubernetes_secret" "nextcloud_admin_credentials" { + metadata { + name = "nextcloud-admin-credentials" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + data = { + username = "admin" + password = var.nextcloud_admin_password + } + type = "Opaque" +} + +# +# Setup MariaDB database +# +module "nextcloud_database" { + source = "./modules/k8s_mariadb" + + namespace = kubernetes_namespace.nextcloud.metadata[0].name + app_name = "nextcloud" + app_backup_folder = "nextcloud.backups" + + app_password = var.nextcloud_mysql_password + root_password = var.mysql_root_password + + retention_days = local.local_backup_retention_days + cron_schedule = local.nextcloud_sql_backup_cron_schedule + host_storage_path = local.nextcloud_mysql_path +} + +# +# Setup rsync backup job to scaleway bucket +# +module "nextcloud_backup" { + + source = "./modules/scw_backup_job" + + namespace = kubernetes_namespace.nextcloud.metadata[0].name + app_name = "nextcloud" + app_files_folder = "nextcloud" + app_backup_folder = "mysql.nextcloud/nextcloud.backups" + + scaleway_organization_id = var.scaleway_organization_id + scaleway_project_id = var.scaleway_organization_id + access_key = scaleway_iam_api_key.backup_app_api.access_key + secret_key = scaleway_iam_api_key.backup_app_api.secret_key + bucket_name = scaleway_object_bucket.backup.name + + retention_days = local.local_backup_retention_days + cron_schedule = local.nextcloud_files_backup_cron_schedule +} + +# +# Nextcloud service, ingress, deployment and file backup job +# +# +resource "kubernetes_service_account" "nextcloud_service_account" { + + metadata { + name = "nextcloud-app-sa" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + + automount_service_account_token = false +} + +resource "kubernetes_service" "nextcloud_redis" { + metadata { + name = "redis" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + spec { + port { + port = 6379 + name = "redis" + } + selector = { + app = "redis" + } + cluster_ip = "None" + } +} + +resource "kubernetes_service" "nextcloud_apache" { + metadata { + name = "http" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + spec { + port { + port = 80 + target_port = "http" + } + selector = { + app = "nextcloud" + } + cluster_ip = "None" + } +} + +resource "kubernetes_ingress_v1" "nextcloud_ingress" { + depends_on = [ + kubernetes_deployment.nextcloud_apache + ] + metadata { + name = "nextcloud-ingress" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + + annotations = { + "kubernetes.io/ingress.class" = "public" + "cert-manager.io/cluster-issuer" = "letsencrypt-prod" + "kubernetes.io/tls-acme" = "true" + "nginx.ingress.kubernetes.io/ssl-redirect" = "true" + "nginx.ingress.kubernetes.io/proxy-body-size" = "4G" + "nginx.ingress.kubernetes.io/proxy-request-buffering" = "on" + "nginx.ingress.kubernetes.io/proxy-buffer-size" = "32k" + "nginx.ingress.kubernetes.io/proxy-buffers-number" = "16" + "nginx.ingress.kubernetes.io/server-snippet" = file("${path.module}/resources/nextcloud-server-snippet.conf") + } + } + spec { + tls { + hosts = [local.nextcloud_fqdn] + secret_name = "nextcloud-tls" + } + rule { + host = local.nextcloud_fqdn + http { + path { + backend { + service { + name = "http" + port { + number = 80 + } + } + } + } + } + } + } +} + +resource "kubernetes_deployment" "nextcloud_redis" { + metadata { + name = "redis" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + spec { + selector { + match_labels = { + app = "redis" + } + } + strategy { + type = "RollingUpdate" + } + template { + metadata { + labels = { + app = "redis" + } + } + spec { + container { + name = "redis" + image = "redis:latest" + + resources { + requests = { + memory = "20Mi" + } + limits = { + memory = "50Mi" + } + } + + readiness_probe { + tcp_socket { + port = "redis" + } + initial_delay_seconds = 10 + period_seconds = 15 + success_threshold = 1 + failure_threshold = 3 + } + + liveness_probe { + tcp_socket { + port = "redis" + } + initial_delay_seconds = 10 + period_seconds = 15 + success_threshold = 1 + failure_threshold = 3 + } + + port { + container_port = 6379 + name = "redis" + protocol = "TCP" + } + } + } + } + } +} + +resource "kubernetes_deployment" "nextcloud_apache" { + + depends_on = [ + module.nextcloud_database, + kubernetes_deployment.nextcloud_redis + ] + + metadata { + name = "nextcloud-app" + namespace = kubernetes_namespace.nextcloud.metadata[0].name + } + spec { + selector { + match_labels = { + app = "nextcloud" + } + } + strategy { + type = "Recreate" + } + template { + metadata { + labels = { + app = "nextcloud" + } + } + spec { + service_account_name = kubernetes_service_account.nextcloud_service_account.metadata[0].name + + container { + name = "nextcloud" + image = "nextcloud:${local.nextcloud_version}" + image_pull_policy = "Always" + + resources { + requests = { + memory = "200Mi" + } + } + + readiness_probe { + http_get { + port = "http" + path = "/status.php" + } + initial_delay_seconds = 30 + period_seconds = 10 + success_threshold = 1 + failure_threshold = 3 + } + + liveness_probe { + tcp_socket { + port = "http" + } + initial_delay_seconds = 5 + period_seconds = 10 + success_threshold = 1 + failure_threshold = 3 + } + + dynamic "env" { + for_each = local.nextcloud_env_vars + content { + name = env.key + value = env.value + } + } + + dynamic "env" { + for_each = local.nextcloud_secrets + content { + name = env.key + value_from { + secret_key_ref { + key = env.value.key + name = env.value.name + } + } + } + } + + port { + container_port = 80 + name = "http" + } + + volume_mount { + mount_path = "/var/www/html" + name = "persistent-storage" + } + } + volume { + name = "persistent-storage" + host_path { + path = local.nextcloud_path + } + } + } + } + } + lifecycle { + replace_triggered_by = [ + kubernetes_deployment.nextcloud_redis + ] + } +} + +resource "kubernetes_cron_job_v1" "nextcloud_cron_apache" { + depends_on = [ + kubernetes_deployment.nextcloud_apache + ] + metadata { + namespace = kubernetes_namespace.nextcloud.metadata[0].name + name = "cronjob-trigger" + } + spec { + schedule = "*/15 * * * *" + concurrency_policy = "Forbid" + failed_jobs_history_limit = 2 + successful_jobs_history_limit = 2 + job_template { + metadata { + labels = { + app = "cronjob-trigger" + } + } + spec { + backoff_limit = 1 + parallelism = 1 + completions = 1 + active_deadline_seconds = 300 + template { + metadata { + labels = { + app = "cronjob-trigger" + } + } + spec { + container { + name = "nextcloud" + image = "nextcloud:${local.nextcloud_version}" + + command = ["curl"] + args = ["--fail", "-L", "http://http/cron.php"] + } + restart_policy = "Never" + termination_grace_period_seconds = 30 + } + } + } + } + } + lifecycle { + replace_triggered_by = [ + kubernetes_deployment.nextcloud_apache + ] + } +} diff --git a/terraform/k8apps/operator.tf b/terraform/k8apps/operator.tf new file mode 100644 index 0000000..8362673 --- /dev/null +++ b/terraform/k8apps/operator.tf @@ -0,0 +1,38 @@ +resource "kubernetes_service_account" "operator_service_account" { + metadata { + name = "operator" + namespace = "default" + } +} + +resource "kubernetes_cluster_role_binding" "operator_admin_access" { + + metadata { + name = "operator-admin-access" + } + + role_ref { + api_group = "rbac.authorization.k8s.io" + kind = "ClusterRole" + name = "cluster-admin" + } + + subject { + kind = "ServiceAccount" + namespace = "default" + name = kubernetes_service_account.operator_service_account.metadata[0].name + } +} + +resource "kubernetes_secret" "operator_access_token" { + metadata { + annotations = { + "kubernetes.io/service-account.name" = kubernetes_service_account.operator_service_account.metadata[0].name + } + name = "operator-token" + namespace = "default" + } + + type = "kubernetes.io/service-account-token" + wait_for_service_account_token = true +} diff --git a/terraform/k8apps/provider.tf b/terraform/k8apps/provider.tf new file mode 100644 index 0000000..c2618aa --- /dev/null +++ b/terraform/k8apps/provider.tf @@ -0,0 +1,46 @@ +terraform { + required_version = "~>1.8" + required_providers { + random = { + source = "hashicorp/random" + version = "~>3.4" + } + null = { + source = "hashicorp/null" + version = "~>3.2" + } + external = { + source = "hashicorp/external" + version = "~>2.2" + } + time = { + source = "hashicorp/time" + version = "~>0.9" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = "~>2.18" + } + scaleway = { + source = "scaleway/scaleway" + version = "~>2.13" + } + } +} + +provider "kubernetes" { + host = var.k8s_host + client_certificate = base64decode(var.k8s_client_certificate) + client_key = base64decode(var.k8s_client_key) + cluster_ca_certificate = base64decode(var.k8s_cluster_ca_certificate) + insecure = true +} + +provider "scaleway" { + zone = "fr-par-2" + region = "fr-par" + access_key = var.scaleway_access_key + secret_key = var.scaleway_secret_key + organization_id = var.scaleway_organization_id +} + diff --git a/terraform/k8apps/resources/apache2/000-default.conf b/terraform/k8apps/resources/apache2/000-default.conf new file mode 100644 index 0000000..36e681d --- /dev/null +++ b/terraform/k8apps/resources/apache2/000-default.conf @@ -0,0 +1,29 @@ + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + #ServerName www.example.com + + ServerAdmin webmaster@localhost + DocumentRoot /var/www/html + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/error.log + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + diff --git a/terraform/k8apps/resources/collabora/coolwsd.xml b/terraform/k8apps/resources/collabora/coolwsd.xml new file mode 100644 index 0000000..2eb441a --- /dev/null +++ b/terraform/k8apps/resources/collabora/coolwsd.xml @@ -0,0 +1,477 @@ + + + + + + + + + de_DE en_GB en_US es_ES fr_FR + it nl pt_BR pt_PT ru + + + + + + + + + + + + + false + + + + + + + + + + + + + true + + + 1 + + + 4 + 5 + + + false + 96 + 3600 + 30 + 300 + false + 0 + 8000 + 0 + 0 + 100 + 5 + 100 + 500 + 5000 + + + 10000 + 60 + 300 + 3072 + 85 + 120 + + + + + true + 120 + 900 + + + + + + true + + warning + trace + notice + fatal + + false + + -INFO-WARN + + + /var/log/coolwsd.log + never + timestamp + true + 10 days + + 10 + true + + false + + + false + 82589933 + + false + false + + + + + /var/log/coolwsd.trace.json + + + false + + + + + + + + false + + + + + + all + any + + + + + 192\.168\.[0-9]{1,3}\.[0-9]{1,3} + + ::ffff:192\.168\.[0-9]{1,3}\.[0-9]{1,3} + 127\.0\.0\.1 + ::ffff:127\.0\.0\.1 + ::1 + + 172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + + ::ffff:172\.1[6789]\.[0-9]{1,3}\.[0-9]{1,3} + + 172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + + ::ffff:172\.2[0-9]\.[0-9]{1,3}\.[0-9]{1,3} + + 172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + + ::ffff:172\.3[01]\.[0-9]{1,3}\.[0-9]{1,3} + + 10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + + ::ffff:10\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3} + + + + + + + + + + + true + + false + /etc/coolwsd/cert.pem + /etc/coolwsd/key.pem + /etc/coolwsd/ca-chain.cert.pem + + + 1000 + + + + + + + false + 31536000 + + + + + + true + true + 1800 + false + 1 + false + + + + + + + + + + + + + + default + true + + + + + + 0 + + 900 + + + + + https://cloud.alexpires.me + + + + + + + true + + + + + + + + + true + + + true + + false + admin + + M*7#VayBh7 + + + + + + + + + + + + + + + + + + + + + + + false + + + + log + + + + + + + + + true + + + \ No newline at end of file diff --git a/terraform/k8apps/resources/m3uproxy/m3uproxy.json b/terraform/k8apps/resources/m3uproxy/m3uproxy.json new file mode 100644 index 0000000..703b2ef --- /dev/null +++ b/terraform/k8apps/resources/m3uproxy/m3uproxy.json @@ -0,0 +1,30 @@ +{ + "port": 8080, + "playlist": "conf/playlist.json", + "epg": "https://m3upt.com/epg", + "no_service_image": "assets/no_service_pt.jpg", + "default_timeout": 3, + "num_workers": 10, + "scan_time": 3600, + "security": { + "geoip": { + "database": "GeoIP/GeoLite2-Country.mmdb", + "whitelist": [ + "PT", + "ST", + "BE" + ], + "internal_networks": [ + "10.1.0.0/16", + "10.152.183.0/24" + ] + } + }, + "auth": { + "provider": "file", + "secret_key": "${m3uproxy_secret}", + "settings": { + "file_path": "cache/auth.json" + } + } +} \ No newline at end of file diff --git a/terraform/k8apps/resources/m3uproxy/playlist.json b/terraform/k8apps/resources/m3uproxy/playlist.json new file mode 100644 index 0000000..eaefe26 --- /dev/null +++ b/terraform/k8apps/resources/m3uproxy/playlist.json @@ -0,0 +1,124 @@ +{ + "providers": { + "m3upt": { + "provider": "file", + "config": { + "source": "https://m3upt.com/iptv" + } + }, + "iptv_be": { + "provider": "file", + "config": { + "source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/be.m3u" + } + }, + "iptv_st": { + "provider": "file", + "config": { + "source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/st.m3u" + } + }, + "iptv_pt_samsung": { + "provider": "file", + "config": { + "source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/pt_samsung.m3u" + } + }, + "iptv_uk_rakuten": { + "provider": "file", + "config": { + "source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/uk_rakuten.m3u" + } + }, + "iptv_uk_samsung": { + "provider": "file", + "config": { + "source": "https://raw.githubusercontent.com/iptv-org/iptv/master/streams/uk_samsung.m3u" + } + } + }, + "providers_priority": [ + "m3upt", + "iptv_be", + "iptv_st", + "iptv_pt_samsung", + "iptv_uk_rakuten", + "iptv_uk_samsung" + ], + "overrides": { + "RTP1.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "RTP2.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "SIC.pt": { + "kodi": true, + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "TVI.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "RTP3.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "SICNoticias.pt": { + "headers": { + "Origin": "https://sicnoticias.pt", + "DNT": "1", + "Connection": "keep-alive" + }, + "kodi": true, + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "CNNPortugal.pt": { + "kodi": true, + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "RTPMemoria.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "RTPAcores.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "RTPMadeira.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "RTPAfrica.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "SICNovelas.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "SICReplay.pt": { + "http_proxy": "http://proxy-pt.m3uproxy.svc.cluster.local:3128" + }, + "Rádio Comercial": { + "disable_remap": true + }, + "Rádio Comercial Dance": { + "disable_remap": true + }, + "Rádio Comercial By Night": { + "disable_remap": true + }, + "Rádio Comercial One Hit Wonders": { + "disable_remap": true + }, + "Rádio Comercial Kids": { + "disable_remap": true + }, + "Rádio Comercial Portugal": { + "disable_remap": true + }, + "Rádio Comercial Brasil": { + "disable_remap": true + }, + "Rádio Comercial 90s": { + "disable_remap": true + }, + "Rádio Comercial 2000s": { + "disable_remap": true + } + } +} \ No newline at end of file diff --git a/terraform/k8apps/resources/nextcloud-server-snippet.conf b/terraform/k8apps/resources/nextcloud-server-snippet.conf new file mode 100644 index 0000000..a58c840 --- /dev/null +++ b/terraform/k8apps/resources/nextcloud-server-snippet.conf @@ -0,0 +1,27 @@ +server_tokens off; +proxy_hide_header X-Powered-By; + +rewrite ^/.well-known/webfinger /index.php/.well-known/webfinger last; +rewrite ^/.well-known/nodeinfo /index.php/.well-known/nodeinfo last; + +location = /.well-known/carddav { + return 301 $scheme://$host/remote.php/dav; +} + +location = /.well-known/caldav { + return 301 $scheme://$host/remote.php/dav; +} + +location = /robots.txt { + allow all; + log_not_found off; + access_log off; +} + +location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)/ { + deny all; +} + +location ~ ^/(?:autotest|occ|issue|indie|db_|console) { + deny all; +} \ No newline at end of file diff --git a/terraform/k8apps/resources/squid/squid.conf b/terraform/k8apps/resources/squid/squid.conf new file mode 100644 index 0000000..2dd6b07 --- /dev/null +++ b/terraform/k8apps/resources/squid/squid.conf @@ -0,0 +1,45 @@ +acl localnet src 0.0.0.1-0.255.255.255 # RFC 1122 "this" network (LAN) +acl localnet src 10.0.0.0/8 # RFC 1918 local private network (LAN) +acl localnet src 100.64.0.0/10 # RFC 6598 shared address space (CGN) +acl localnet src 169.254.0.0/16 # RFC 3927 link-local (directly plugged) machines +acl localnet src 172.16.0.0/12 # RFC 1918 local private network (LAN) +acl localnet src 192.168.0.0/16 # RFC 1918 local private network (LAN) +acl localnet src fc00::/7 # RFC 4193 local private network range +acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines +acl SSL_ports port 443 +acl Safe_ports port 80 # http +acl Safe_ports port 21 # ftp +acl Safe_ports port 443 # https +acl Safe_ports port 70 # gopher +acl Safe_ports port 210 # wais +acl Safe_ports port 1025-65535 # unregistered ports +acl Safe_ports port 280 # http-mgmt +acl Safe_ports port 488 # gss-http +acl Safe_ports port 591 # filemaker +acl Safe_ports port 777 # multiling http +acl CONNECT method CONNECT +http_access deny !Safe_ports +http_access deny CONNECT !SSL_ports +http_access allow localhost manager +http_access deny manager +http_access allow localhost +http_access allow localnet +http_access deny all +http_port 3128 +dns_v4_first on +coredump_dir /var/spool/squid +refresh_pattern ^ftp: 1440 20% 10080 +refresh_pattern ^gopher: 1440 0% 1440 +refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 +refresh_pattern \/(Packages|Sources)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims +refresh_pattern \/Release(|\.gpg)$ 0 0% 0 refresh-ims +refresh_pattern \/InRelease$ 0 0% 0 refresh-ims +refresh_pattern \/(Translation-.*)(|\.bz2|\.gz|\.xz)$ 0 0% 0 refresh-ims +refresh_pattern . 0 20% 4320 +logfile_rotate 0 +via off +forwarded_for delete +follow_x_forwarded_for deny all +pipeline_prefetch on +request_header_access From deny all +request_header_access Server deny all \ No newline at end of file diff --git a/terraform/k8apps/resources/wireguard/wireguard.conf.tpl b/terraform/k8apps/resources/wireguard/wireguard.conf.tpl new file mode 100644 index 0000000..5f5d5a1 --- /dev/null +++ b/terraform/k8apps/resources/wireguard/wireguard.conf.tpl @@ -0,0 +1,9 @@ +[Interface] +PrivateKey = ${wireguard_private_key} +Address = ${wireguard_address} +DNS = ${wireguard_dns} + +[Peer] +PublicKey = ${wireguard_public_key} +AllowedIPs = ${wireguard_allowed_ips} +Endpoint = ${wireguard_endpoint} diff --git a/terraform/k8apps/sectool.env b/terraform/k8apps/sectool.env new file mode 100644 index 0000000..b74df49 --- /dev/null +++ b/terraform/k8apps/sectool.env @@ -0,0 +1,16 @@ +AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY +AWS_SECRET_ACCESS_KEY=$AWS_SECRET_KEY +TF_VAR_scaleway_access_key=$SCALEWAY_ACCESS_KEY +TF_VAR_scaleway_secret_key=$SCALEWAY_SECRET_KEY +TF_VAR_scaleway_project_id=$SCALEWAY_PROJECT_ID +TF_VAR_scaleway_organization_id=$SCALEWAY_ORGANIZATION_ID +TF_VAR_mysql_root_password=$MYSQL_ROOT_PASSWORD +TF_VAR_wordpress_mysql_password=$MYSQL_WORDPRESS_PASSWORD +TF_VAR_gitea_mysql_password=$MYSQL_GITEA_PASSWORD +TF_VAR_nextcloud_mysql_password=$MYSQL_NEXTCLOUD_PASSWORD +TF_VAR_nextcloud_admin_password=$NEXTCLOUD_ADMIN_PASSWORD +TF_VAR_collabora_admin_password=$NEXTCLOUD_ADMIN_PASSWORD +TF_VAR_wireguard_private_key=$WIREGUARD_PRIVATE_KEY +TF_VAR_wireguard_public_key=$WIREGUARD_PUBLIC_KEY +TF_VAR_m3uproxy_admin_password=$M3UPROXY_ADMIN_PASSWORD +TF_VAR_m3uproxy_secret=$M3UPROXY_SECRET \ No newline at end of file diff --git a/terraform/k8apps/variables.tf b/terraform/k8apps/variables.tf new file mode 100644 index 0000000..427bd16 --- /dev/null +++ b/terraform/k8apps/variables.tf @@ -0,0 +1,89 @@ +variable "wordpress_mysql_password" { + description = "MySQL access password" + type = string +} + +variable "mysql_root_password" { + description = "MySQL root password" + type = string +} + +variable "gitea_mysql_password" { + description = "MySQL access password" + type = string +} + +variable "scaleway_access_key" { + description = "Scaleway Access key" + type = string +} + +variable "scaleway_secret_key" { + description = "Scaleway Secret key" + type = string +} + +variable "scaleway_project_id" { + description = "Scaleway Project Id" + type = string +} + +variable "scaleway_organization_id" { + description = "Scaleway Organization Id" + type = string +} + +variable "nextcloud_mysql_password" { + description = "MySQL access password" + type = string +} + +variable "nextcloud_admin_password" { + description = "Nextcloud access password" + type = string +} + +variable "collabora_admin_password" { + description = "Collabora access password" + type = string +} + +variable "wireguard_private_key" { + description = "Wireguard public key" + type = string +} + +variable "wireguard_public_key" { + description = "Wireguard public key" + type = string +} + +variable "m3uproxy_admin_password" { + description = "M3UProxy access password" + type = string +} + +variable "m3uproxy_secret" { + description = "M3UProxy HMAC secret" + type = string +} + +variable "k8s_host" { + description = "K8S host" + type = string +} + +variable "k8s_client_certificate" { + description = "K8S client certificate" + type = string +} + +variable "k8s_client_key" { + description = "K8S client key" + type = string +} + +variable "k8s_cluster_ca_certificate" { + description = "K8S cluster CA certificate" + type = string +} diff --git a/terraform/k8apps/wordpress.tf b/terraform/k8apps/wordpress.tf new file mode 100644 index 0000000..27b47ed --- /dev/null +++ b/terraform/k8apps/wordpress.tf @@ -0,0 +1,230 @@ +locals { + wordpress_path = "/var/lib/wordpress" + wordpress_mysql_path = "/var/lib/mysql.wordpress" +} + +resource "kubernetes_namespace" "wordpress" { + metadata { + annotations = { + name = "wordpress" + } + + labels = { + name = "wordpress" + } + + name = "wordpress" + } +} + + +# +# Setup MariaDB database +# +module "wordpress_database" { + source = "./modules/k8s_mariadb" + + namespace = kubernetes_namespace.wordpress.metadata[0].name + app_name = "wordpress" + app_backup_folder = "wordpress.backups" + + app_password = var.wordpress_mysql_password + root_password = var.mysql_root_password + + retention_days = local.local_backup_retention_days + cron_schedule = local.wordpress_sql_backup_cron_schedule + host_storage_path = local.wordpress_mysql_path +} + + +# +# Setup rsync backup job to scaleway bucket +# +module "wordpress_backup" { + source = "./modules/scw_backup_job" + + namespace = kubernetes_namespace.wordpress.metadata[0].name + app_name = "wordpress" + app_files_folder = "wordpress" + app_backup_folder = "mysql.wordpress/wordpress.backups" + + scaleway_organization_id = var.scaleway_organization_id + scaleway_project_id = var.scaleway_organization_id + access_key = scaleway_iam_api_key.backup_app_api.access_key + secret_key = scaleway_iam_api_key.backup_app_api.secret_key + bucket_name = scaleway_object_bucket.backup.name + + retention_days = local.local_backup_retention_days + cron_schedule = local.wordpress_files_backup_cron_schedule +} + +# +# Gitea service, ingress, deployment and file backup job +# +resource "kubernetes_service_account" "wordpress_service_account" { + + metadata { + name = "wordpress-app-sa" + namespace = kubernetes_namespace.wordpress.metadata[0].name + } + + automount_service_account_token = false +} + + +resource "kubernetes_service" "wordpress" { + metadata { + name = "http" + namespace = kubernetes_namespace.wordpress.metadata[0].name + } + spec { + port { + port = 80 + target_port = "http" + } + selector = { + app = "wordpress" + } + cluster_ip = "None" + } +} + +resource "kubernetes_ingress_v1" "wordpress_ingress" { + metadata { + name = "wordpress-ingress" + namespace = kubernetes_namespace.wordpress.metadata[0].name + + annotations = { + "kubernetes.io/ingress.class" = "public" + "cert-manager.io/cluster-issuer" = "letsencrypt-prod" + } + } + spec { + tls { + hosts = [local.wordpress_fqdn] + secret_name = "wordpress-le-tls" + } + rule { + host = local.wordpress_fqdn + http { + path { + backend { + service { + name = "http" + port { + number = 80 + } + } + } + } + } + } + } +} + +resource "kubernetes_deployment" "wordpress" { + + depends_on = [module.wordpress_database] + + metadata { + name = "wordpress-app" + namespace = kubernetes_namespace.wordpress.metadata[0].name + } + spec { + selector { + match_labels = { + app = "wordpress" + } + } + strategy { + type = "RollingUpdate" + } + template { + metadata { + labels = { + app = "wordpress" + } + } + spec { + service_account_name = kubernetes_service_account.wordpress_service_account.metadata[0].name + + container { + name = "wordpress" + image = "wordpress:${local.wordpress_version}" + image_pull_policy = "IfNotPresent" + + resources { + requests = { + memory = "100Mi" + } + limits = { + memory = "500Mi" + } + } + + readiness_probe { + http_get { + path = "/?nocache" + port = "http" + } + initial_delay_seconds = 20 + period_seconds = 10 + success_threshold = 1 + failure_threshold = 5 + } + + liveness_probe { + tcp_socket { + port = "http" + } + initial_delay_seconds = 5 + period_seconds = 10 + success_threshold = 1 + failure_threshold = 3 + } + + env { + name = "WORDPRESS_DB_NAME" + value = "wordpress" + } + env { + name = "WORDPRESS_DB_HOST" + value = "${module.wordpress_database.host}:${module.wordpress_database.port}" + } + env { + name = "WORDPRESS_DB_USER" + value_from { + secret_key_ref { + key = "username" + name = module.wordpress_database.app_crendentials_name + } + } + } + env { + name = "WORDPRESS_DB_PASSWORD" + value_from { + secret_key_ref { + key = "password" + name = module.wordpress_database.app_crendentials_name + } + } + } + port { + container_port = 80 + name = "http" + } + volume_mount { + mount_path = "/var/www/html" + name = "persistent-storage" + } + } + volume { + name = "persistent-storage" + host_path { + path = local.wordpress_path + } + } + } + } + } +}