diff --git a/terraform/k8sapps/collabora.tf b/terraform/k8sapps/collabora.tf new file mode 100644 index 0000000..fadbf6b --- /dev/null +++ b/terraform/k8sapps/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/k8sapps/nextcloud.tf b/terraform/k8sapps/nextcloud.tf new file mode 100644 index 0000000..e071b1f --- /dev/null +++ b/terraform/k8sapps/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 + ] + } +}