diff --git a/.gitignore b/.gitignore index 5830af1..45f30fd 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,9 @@ inventory/inventory.yml !.gitkeep galaxy_cache galaxy_token +# terraform secret files +*.tfstate +*.tfstate.backup +*.tfvars +.terraform +.terraform.lock.hcl diff --git a/deployments/invidious/Taskfile.yml b/deployments/invidious/Taskfile.yml new file mode 100644 index 0000000..1681dba --- /dev/null +++ b/deployments/invidious/Taskfile.yml @@ -0,0 +1,16 @@ +version: '3' + +vars: + TF_BINARY: + sh: bash -c 'which tofu || which terraform || (echo "Could not find terraform compatible binary" && exit 1)' + +tasks: + deploy: + desc: deploy project using OpenTofu or Terraform + cmds: + - '{{.TF_BINARY}} init' + - '{{.TF_BINARY}} apply -auto-approve' + + default: + cmd: + task: deploy diff --git a/deployments/invidious/configs.tf b/deployments/invidious/configs.tf new file mode 100644 index 0000000..bb0992e --- /dev/null +++ b/deployments/invidious/configs.tf @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-only +resource "kubernetes_secret_v1" "app_secrets" { + metadata { + name = "${var.app_name}-secrets" + annotations = var.secret_annotations + labels = merge({ + "app.kubernetes.io/component" = "server" + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/version" = var.app_version + "app.kubernetes.io/part-of" = var.app_name + "app.kubernetes.io/managed-by" = "opentofu" + "app.kubernetes.io/instance" = var.app_name + }, var.secret_additional_labels) + } + data = var.app_configuration +} diff --git a/deployments/invidious/data.tf b/deployments/invidious/data.tf new file mode 100644 index 0000000..1ae79f2 --- /dev/null +++ b/deployments/invidious/data.tf @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only +data "kubernetes_namespace_v1" "app" { + metadata { + name = var.app_namespace + } +} diff --git a/deployments/invidious/ingresses.tf b/deployments/invidious/ingresses.tf new file mode 100644 index 0000000..0c4b20d --- /dev/null +++ b/deployments/invidious/ingresses.tf @@ -0,0 +1,36 @@ +# SPDX-License-Identifier: GPL-2.0-only +resource "kubernetes_manifest" "app_ingress_route_tcp" { + count = var.use_ingress && var.ingress_controller == "traefik" ? 1 : 0 + manifest = { + apiVersion = "traefik.io/v1alpha1" + kind = "IngressRoute" + metadata = { + name = var.app_name + namespace = data.kubernetes_namespace_v1.app.metadata[0].name + annotations = var.ingress_annotations + labels = merge({ + "app.kubernetes.io/component" = "server" + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/version" = var.app_version + "app.kubernetes.io/part-of" = var.app_name + "app.kubernetes.io/managed-by" = "opentofu" + "app.kubernetes.io/instance" = var.app_name + }, var.ingress_additional_labels) + } + spec = { + entryPoints = var.traefik_entrypoints + routes = [ + { + match = format("Host(`%s`)", var.ingress_host_url) + kind = "Rule" + services = [ + { + name = var.app_name + port = var.service_container_port + } + ] + } + ] + } + } +} diff --git a/deployments/invidious/main.tf b/deployments/invidious/main.tf new file mode 100644 index 0000000..7ccca04 --- /dev/null +++ b/deployments/invidious/main.tf @@ -0,0 +1,153 @@ +# SPDX-License-Identifier: GPL-2.0-only +terraform { + required_providers { + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.25" + } + } + required_version = ">= 1.6.2" +} + +provider "kubernetes" { + config_path = var.kubeconfig_path + config_context = var.kubeconfig_context +} + +resource "kubernetes_deployment_v1" "app" { + metadata { + name = var.app_name + namespace = data.kubernetes_namespace_v1.app.metadata[0].name + labels = merge({ + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/version" = var.app_version + "app.kubernetes.io/managed-by" = "opentofu" + "app.kubernetes.io/instance" = var.app_name + }, var.deployment_additional_labels) + annotations = var.deployment_annotations + } + spec { + selector { + match_labels = { + "app.kubernetes.io/name" = var.app_name + } + } + template { + metadata { + annotations = var.pods_annotations + labels = merge({ + "app.kubernetes.io/component" = "server" + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/version" = var.app_version + "app.kubernetes.io/part-of" = var.app_name + "app.kubernetes.io/managed-by" = "opentofu" + "app.kubernetes.io/instance" = var.app_name + }, var.pods_additional_labels) + } + spec { + service_account_name = var.service_account_name + security_context { + run_as_non_root = true + run_as_group = 1000 + run_as_user = 1000 + } + ## Web service + container { + name = var.app_name + image = var.container_invidious_image + image_pull_policy = var.container_invidious_image_pull_policy + port { + name = "http" + container_port = 3000 + protocol = "TCP" + } + security_context { + allow_privilege_escalation = false + privileged = false + capabilities { + drop = ["ALL"] + } + } + readiness_probe { + initial_delay_seconds = 60 + failure_threshold = 3 + period_seconds = 10 + success_threshold = 1 + timeout_seconds = 3 + http_get { + port = "http" + path = "/" + scheme = "HTTP" + } + } + liveness_probe { + initial_delay_seconds = 60 + failure_threshold = 3 + period_seconds = 10 + success_threshold = 1 + timeout_seconds = 5 + http_get { + port = "http" + path = "/" + scheme = "HTTP" + } + } + startup_probe { + initial_delay_seconds = 60 + failure_threshold = 30 + period_seconds = 5 + success_threshold = 1 + timeout_seconds = 1 + http_get { + port = "http" + path = "/" + scheme = "HTTP" + } + } + env_from { + secret_ref { + name = kubernetes_secret_v1.app_secrets.metadata[0].name + optional = false + } + } + # Linked to https://github.com/iv-org/invidious/issues/2970 + env { + name = "INVIDIOUS_PORT" + value = 3000 + } + resources { + requests = var.container_invidious_resources_requests + } + } + ## IV Sig helper + container { + name = "${var.app_name}-sig-helper" + image = var.container_iv_sig_helper_image + image_pull_policy = var.container_iv_sig_helper_image_pull_policy + args = ["--tcp", "127.0.0.1:12999"] + + port { + name = "http" + container_port = 12999 + protocol = "TCP" + } + security_context { + allow_privilege_escalation = false + privileged = false + read_only_root_filesystem = true + capabilities { + drop = ["ALL"] + } + } + env { + name = "RUST_LOG" + value = "info" + } + resources { + requests = var.container_iv_sig_helper_resources_requests + } + } + } + } + } +} diff --git a/deployments/invidious/outputs.tf b/deployments/invidious/outputs.tf new file mode 100644 index 0000000..56d44e9 --- /dev/null +++ b/deployments/invidious/outputs.tf @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +output "app_url" { + value = format("https://%s", var.ingress_host_url) + description = "Website URL" +} diff --git a/deployments/invidious/service_accounts.tf b/deployments/invidious/service_accounts.tf new file mode 100644 index 0000000..9e31c03 --- /dev/null +++ b/deployments/invidious/service_accounts.tf @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: GPL-2.0-only +resource "kubernetes_service_account_v1" "invidious" { + metadata { + name = var.service_account_name + annotations = merge({ + "kubernetes.io/enforce-mountable-secrets" = true + }, var.service_account_additional_annotations) + labels = merge({ + "app.kubernetes.io/component" = "server" + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/version" = var.app_version + "app.kubernetes.io/part-of" = var.app_name + "app.kubernetes.io/managed-by" = "opentofu" + "app.kubernetes.io/instance" = var.app_name + }, var.service_account_labels) + } + secret { + name = kubernetes_secret_v1.app_secrets.metadata[0].name + } +} diff --git a/deployments/invidious/services.tf b/deployments/invidious/services.tf new file mode 100644 index 0000000..0240a98 --- /dev/null +++ b/deployments/invidious/services.tf @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0-only +resource "kubernetes_service_v1" "app" { + metadata { + name = var.app_name + annotations = {} + labels = merge({ + "app.kubernetes.io/component" = "server" + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/version" = var.app_version + "app.kubernetes.io/part-of" = var.app_name + "app.kubernetes.io/managed-by" = "opentofu" + "app.kubernetes.io/instance" = var.app_name + }, var.service_additional_labels) + } + spec { + type = var.service_type + selector = { + "app.kubernetes.io/name" = var.app_name + "app.kubernetes.io/instance" = var.app_name + } + port { + name = "http" + protocol = "TCP" + port = 3000 + target_port = "http" + } + } +} diff --git a/deployments/invidious/variables.tf b/deployments/invidious/variables.tf new file mode 100644 index 0000000..246f0a6 --- /dev/null +++ b/deployments/invidious/variables.tf @@ -0,0 +1,286 @@ +# SPDX-License-Identifier: GPL-2.0-only +## Providers +variable "kubeconfig_path" { + default = "~/.kube/config" + description = "Path to the kubeconfig file" + type = string + nullable = false +} + +variable "kubeconfig_context" { + default = "default" + description = "Context to use to access the cluster" + type = string + nullable = false +} + +## Application +variable "app_name" { + default = "invidious" + description = "Application name, used by various resources such as deployment, ingress, container, ..." + type = string + nullable = false + validation { + condition = length(regexall("[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*", var.app_name)) > 0 + error_message = "Invalid value for 'app_name', must respect RFC 1123" + } +} + +variable "app_configuration" { + default = { + INVIDIOUS_CONFIG = < 0 + error_message = "Invalid value for 'service_account_name', must respect RFC 1123" + } +} + +variable "service_account_additional_annotations" { + default = {} + description = "Additional annotations for the app's service account" + type = map(any) +} + +variable "service_account_labels" { + default = {} + description = "Labels for the service account used by the app" + type = map(any) +}