feat: add terraform deployment for K8s invidious
This commit is contained in:
parent
ff7c9d8b91
commit
904b067816
10 changed files with 572 additions and 0 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -13,3 +13,9 @@ inventory/inventory.yml
|
|||
!.gitkeep
|
||||
galaxy_cache
|
||||
galaxy_token
|
||||
# terraform secret files
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
*.tfvars
|
||||
.terraform
|
||||
.terraform.lock.hcl
|
||||
|
|
16
deployments/invidious/Taskfile.yml
Normal file
16
deployments/invidious/Taskfile.yml
Normal file
|
@ -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
|
16
deployments/invidious/configs.tf
Normal file
16
deployments/invidious/configs.tf
Normal file
|
@ -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
|
||||
}
|
6
deployments/invidious/data.tf
Normal file
6
deployments/invidious/data.tf
Normal file
|
@ -0,0 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
data "kubernetes_namespace_v1" "app" {
|
||||
metadata {
|
||||
name = var.app_namespace
|
||||
}
|
||||
}
|
36
deployments/invidious/ingresses.tf
Normal file
36
deployments/invidious/ingresses.tf
Normal file
|
@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
153
deployments/invidious/main.tf
Normal file
153
deployments/invidious/main.tf
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
5
deployments/invidious/outputs.tf
Normal file
5
deployments/invidious/outputs.tf
Normal file
|
@ -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"
|
||||
}
|
20
deployments/invidious/service_accounts.tf
Normal file
20
deployments/invidious/service_accounts.tf
Normal file
|
@ -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
|
||||
}
|
||||
}
|
28
deployments/invidious/services.tf
Normal file
28
deployments/invidious/services.tf
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
286
deployments/invidious/variables.tf
Normal file
286
deployments/invidious/variables.tf
Normal file
|
@ -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 = <<EOC
|
||||
captcha_enabled: false
|
||||
channel_threads: 1
|
||||
db:
|
||||
dbname: invidious
|
||||
host: changeme
|
||||
password: 'changeme'
|
||||
port: 5432
|
||||
user: changeme
|
||||
signature_server: 127.0.0.1:12999
|
||||
visitor_data: changeme
|
||||
po_token: changeme
|
||||
hmac_key: changeme
|
||||
domain: changeme
|
||||
external_port: 443
|
||||
port: 3000
|
||||
https_only: true
|
||||
feed_threads: 1
|
||||
full_refresh: true
|
||||
popular_enabled: false
|
||||
default_user_preferences:
|
||||
autoplay: true
|
||||
captions:
|
||||
- French
|
||||
- English
|
||||
- English (auto-generated)
|
||||
continue: true
|
||||
continue_autoplay: true
|
||||
dark_mode: dark
|
||||
default_home: Subscriptions
|
||||
feed_menu:
|
||||
- Subscriptions
|
||||
- Playlists
|
||||
quality: dash
|
||||
quality_dash: best
|
||||
region: FR
|
||||
save_player_pos: true
|
||||
volume: 75
|
||||
EOC
|
||||
}
|
||||
description = "Invidious configuration passed as an environment variable called INVIDIOUS_CONFIG"
|
||||
type = object({INVIDIOUS_CONFIG=string})
|
||||
nullable = false
|
||||
validation {
|
||||
condition = !strcontains(var.app_configuration.INVIDIOUS_CONFIG, "changeme")
|
||||
error_message = "Some required variables are not correctly set; review DB configuration and values marked 'changeme'"
|
||||
}
|
||||
}
|
||||
|
||||
variable "app_version" {
|
||||
default = "latest"
|
||||
description = "Version of the application"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "app_namespace" {
|
||||
default = "default"
|
||||
description = "Namespace used to deploy app resources"
|
||||
type = string
|
||||
nullable = false
|
||||
}
|
||||
|
||||
## Deployment
|
||||
variable "deployment_annotations" {
|
||||
default = {}
|
||||
description = "Annotations for the deployment resource"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
variable "deployment_additional_labels" {
|
||||
default = {}
|
||||
description = "Additionnal labels for the deployment resource"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
## Pods
|
||||
variable "pods_annotations" {
|
||||
default = {}
|
||||
description = "Annotations for the deployment resource"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
variable "pods_additional_labels" {
|
||||
default = {}
|
||||
description = "Additionnal labels for the deployment resource"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
## Containers
|
||||
variable "container_invidious_image" {
|
||||
default = "quay.io/invidious/invidious:latest"
|
||||
description = "Image to use for the web app"
|
||||
type = string
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "container_invidious_image_pull_policy" {
|
||||
default = "IfNotPresent"
|
||||
description = "Pull policy; valid values are 'Always', 'IfNotPresent', 'Never'"
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = contains(["Always", "IfNotPresent", "Never"], var.container_invidious_image_pull_policy)
|
||||
error_message = "Invalid value for 'image_pull_policy'"
|
||||
}
|
||||
}
|
||||
|
||||
variable "container_iv_sig_helper_image" {
|
||||
default = "quay.io/invidious/inv-sig-helper:latest"
|
||||
description = "Image to use for the IV Sig helper service"
|
||||
type = string
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "container_iv_sig_helper_image_pull_policy" {
|
||||
default = "IfNotPresent"
|
||||
description = "Pull policy; valid values are 'Always', 'IfNotPresent', 'Never'"
|
||||
type = string
|
||||
|
||||
validation {
|
||||
condition = contains(["Always", "IfNotPresent", "Never"], var.container_iv_sig_helper_image_pull_policy)
|
||||
error_message = "Invalid value for 'image_pull_policy'"
|
||||
}
|
||||
}
|
||||
|
||||
variable "container_invidious_resources_requests" {
|
||||
default = {
|
||||
cpu = "1500m"
|
||||
memory = "4096Mi"
|
||||
}
|
||||
description = "Resources requests for the app container; supports 'cpu', 'memory', 'hugepages-2Mi' and 'hugepages-1Gi'"
|
||||
type = object(
|
||||
{
|
||||
cpu = optional(string)
|
||||
memory = optional(string)
|
||||
hugepages-2Mi = optional(string)
|
||||
hugepages-1Gi = optional(string)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
variable "container_iv_sig_helper_resources_requests" {
|
||||
default = {
|
||||
cpu = "500m"
|
||||
memory = "256Mi"
|
||||
}
|
||||
description = "Resources requests for the sig helper container; supports 'cpu', 'memory', 'hugepages-2Mi' and 'hugepages-1Gi'"
|
||||
type = object(
|
||||
{
|
||||
cpu = optional(string)
|
||||
memory = optional(string)
|
||||
hugepages-2Mi = optional(string)
|
||||
hugepages-1Gi = optional(string)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
## Configuration
|
||||
variable "secret_annotations" {
|
||||
default = {}
|
||||
description = "Annotations for the Secret resource"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
variable "secret_additional_labels" {
|
||||
default = {}
|
||||
description = "Additional app Secret labels"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
## Service
|
||||
variable "service_container_port" {
|
||||
default = 3000
|
||||
description = "HTTP port used by the container"
|
||||
type = number
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "service_additional_labels" {
|
||||
default = {}
|
||||
description = "Additional labels for the service resource"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
variable "service_type" {
|
||||
default = "ClusterIP"
|
||||
description = "Type of the service resource"
|
||||
type = string
|
||||
}
|
||||
|
||||
## Ingress
|
||||
variable "use_ingress" {
|
||||
default = true
|
||||
description = "Whether to use an ingress or not"
|
||||
type = bool
|
||||
}
|
||||
|
||||
variable "ingress_controller" {
|
||||
default = "traefik"
|
||||
description = "Type of ingress controller used; only traefik is supported at the moment"
|
||||
type = string
|
||||
nullable = false
|
||||
validation {
|
||||
condition = can(contains(["traefik"], var.ingress_controller))
|
||||
error_message = "Invalid value for 'ingress_controller'"
|
||||
}
|
||||
}
|
||||
|
||||
variable "ingress_annotations" {
|
||||
default = {}
|
||||
description = "Ingress resource annotations"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
variable "ingress_additional_labels" {
|
||||
default = {}
|
||||
description = "Ingress resource annotations"
|
||||
type = map(any)
|
||||
}
|
||||
|
||||
variable "ingress_host_url" {
|
||||
description = "Host used for the app, without the protocol prefix"
|
||||
type = string
|
||||
nullable = false
|
||||
}
|
||||
|
||||
variable "traefik_entrypoints" {
|
||||
default = ["websecure"]
|
||||
description = "List of entrypoints used for the IngressTCP Traefik CRD"
|
||||
type = list(string)
|
||||
nullable = false
|
||||
}
|
||||
|
||||
## Service account
|
||||
variable "service_account_name" {
|
||||
default = "invidious"
|
||||
description = "Service account used for web app"
|
||||
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.service_account_name)) > 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)
|
||||
}
|
Loading…
Add table
Reference in a new issue