diff --git a/nginx-ssl/main.tf b/nginx-ssl/main.tf index c6c93f1..e218899 100644 --- a/nginx-ssl/main.tf +++ b/nginx-ssl/main.tf @@ -8,7 +8,6 @@ resource "nomad_job" "nginx-ssl" { vars = { dc = jsonencode([var.dc]) namespace = var.namespace - hosts = jsonencode(var.hosts) docker_image = local.docker_image docker_always_pull = jsonencode(local.docker_always_pull) service_provider = var.service_provider diff --git a/ollama/input.tf b/ollama/input.tf new file mode 100644 index 0000000..bff9048 --- /dev/null +++ b/ollama/input.tf @@ -0,0 +1,95 @@ + +/////////////////////////////////////////////////////////////////////////////// +// VARIABLES + +variable "dc" { + description = "data centers that the job is eligible to run in" + type = list(string) +} + +variable "namespace" { + description = "namespace that the job runs in" + type = string + default = "default" +} + +variable "enabled" { + type = bool + description = "If false, then no job is deployed" + default = true +} + +variable "service_provider" { + description = "Service provider, either consul or nomad" + type = string + default = "nomad" +} + +variable "service_name" { + description = "Service name" + type = string + default = "ollama" +} + +variable "service_dns" { + description = "Service discovery DNS" + type = list(string) + default = [] +} + +variable "docker_tag" { + type = string + description = "Version of the docker image to use, defaults to latest" + default = "latest" +} + +variable "docker_tag_webui" { + type = string + description = "Version of the docker image to use for webui, set to empty string to disable" + default = "main" +} + +/////////////////////////////////////////////////////////////////////////////// + +variable "hosts" { + description = "List of hosts to deploy ollama on. If empty, one allocation will be created" + type = list(string) + default = [] +} + +variable "hosts_webui" { + description = "List of hosts to deploy webui on. If empty, one allocation will be created" + type = list(string) + default = [] +} + +variable "port" { + description = "Ollama port to expose" + type = number + default = 11434 +} + +variable "port_webui" { + description = "WebUI port to expose" + type = number + default = 11435 +} + +variable "data" { + description = "Persistent data path" + type = string + default = "ollama" +} + +variable "devices" { + description = "Devices to expose" + type = list(string) + default = [] +} + +variable "openai_api_key" { + description = "OpenAI API Key" + type = string + sensitive = true + default = "" +} diff --git a/ollama/locals.tf b/ollama/locals.tf new file mode 100644 index 0000000..ff34154 --- /dev/null +++ b/ollama/locals.tf @@ -0,0 +1,6 @@ + +locals { + docker_image = "ollama/ollama:${var.docker_tag}" + docker_image_webui = var.docker_tag_webui == "" ? "" : "ghcr.io/open-webui/open-webui:${var.docker_tag_webui}" + docker_always_pull = var.docker_tag == "latest" ? true : false +} diff --git a/ollama/main.tf b/ollama/main.tf new file mode 100644 index 0000000..834b06b --- /dev/null +++ b/ollama/main.tf @@ -0,0 +1,26 @@ + +resource "nomad_job" "ollama" { + count = var.enabled ? 1 : 0 + jobspec = file("${path.module}/nomad/ollama.hcl") + + hcl2 { + allow_fs = true + vars = { + dc = jsonencode(var.dc) + namespace = var.namespace + docker_image = local.docker_image + docker_image_webui = local.docker_image_webui + docker_always_pull = jsonencode(local.docker_always_pull) + service_provider = var.service_provider + service_dns = jsonencode(var.service_dns) + service_name = var.service_name + hosts = jsonencode(var.hosts) + hosts_webui = jsonencode(var.hosts_webui) + port = var.port + port_webui = var.port_webui + data = var.data + devices = jsonencode(var.devices) + openai_api_key = var.openai_api_key + } + } +} diff --git a/ollama/nomad/ollama.hcl b/ollama/nomad/ollama.hcl new file mode 100644 index 0000000..7878465 --- /dev/null +++ b/ollama/nomad/ollama.hcl @@ -0,0 +1,222 @@ + +// ollama LLM +// Docker Image: ollama/ollama + +/////////////////////////////////////////////////////////////////////////////// +// VARIABLES + +variable "dc" { + description = "data centers that the job is eligible to run in" + type = list(string) +} + +variable "namespace" { + description = "namespace that the job runs in" + type = string + default = "default" +} + +variable "service_provider" { + description = "Service provider, either consul or nomad" + type = string + default = "nomad" +} + +variable "service_name" { + description = "Service name" + type = string + default = "ollama" +} + +variable "service_dns" { + description = "Service discovery DNS" + type = list(string) + default = [] +} + +variable "docker_image" { + description = "Docker image" + type = string + default = "ollama/ollama:latest" +} + +variable "docker_image_webui" { + description = "Docker image" + type = string + default = "ghcr.io/open-webui/open-webui:main" +} + +variable "docker_always_pull" { + description = "Pull docker image on every job restart" + type = bool + default = false +} + +/////////////////////////////////////////////////////////////////////////////// + +variable "hosts" { + description = "List of hosts to deploy on. If empty, one allocation will be created" + type = list(string) + default = [] +} + +variable "port" { + description = "Ollama port to expose" + type = number + default = 11434 +} + +variable "data" { + description = "Persistent data path" + type = string + default = "" +} + +variable "devices" { + description = "Devices to expose" + type = list(string) + default = [] +} + +variable "hosts_webui" { + description = "List of hosts to deploy webui on. If empty, one allocation will be created" + type = list(string) + default = [] +} + +variable "port_webui" { + description = "WebUI port to expose" + type = number + default = 11435 +} + +variable "openai_api_key" { + description = "OpenAI API key" + type = string + default = "" +} + +/////////////////////////////////////////////////////////////////////////////// +// LOCALS + +locals { + devices = [ + for device in var.devices : { + host_path = device + container_path = device + } + ] + volumes = compact([ + var.data != "" ? format("%s:/root/.ollama", var.data) : null, + ]) + volumes_webui = compact([ + var.data != "" ? format("%s:/app/backend/data", var.data) : null, + ]) + ollama_service = format("http://%s-http.%s.nomad.:%s", var.service_name, var.namespace, var.port) +} + +/////////////////////////////////////////////////////////////////////////////// +// JOB + +job "ollama" { + type = "service" + datacenters = var.dc + namespace = var.namespace + + update { + min_healthy_time = "10s" + healthy_deadline = "5m" + health_check = "task_states" + } + + ///////////////////////////////////////////////////////////////////////////////// + + group "ollama" { + count = var.docker_image_webui == "" ? 0 : (length(var.hosts) == 0 ? 1 : length(var.hosts)) + + dynamic "constraint" { + for_each = length(var.hosts) == 0 ? [] : [join(",", var.hosts)] + content { + attribute = node.unique.name + operator = "set_contains_any" + value = constraint.value + } + } + + network { + port "http" { + static = var.port + to = 11434 + } + } + + service { + tags = [var.service_name, "http"] + name = format("%s-%s", var.service_name, "http") + port = "http" + provider = var.service_provider + } + + task "server" { + driver = "docker" + + config { + image = var.docker_image + force_pull = var.docker_always_pull + ports = ["http"] + dns_servers = var.service_dns + volumes = local.volumes + devices = local.devices + } + + } // task "server" + } // group "ollama" + + + ///////////////////////////////////////////////////////////////////////////////// + + group "ollama-webui" { + count = length(var.hosts_webui) == 0 ? 1 : length(var.hosts_webui) + + dynamic "constraint" { + for_each = length(var.hosts_webui) == 0 ? [] : [join(",", var.hosts_webui)] + content { + attribute = node.unique.name + operator = "set_contains_any" + value = constraint.value + } + } + + network { + port "http" { + static = var.port_webui + to = 8080 + } + } + + service { + tags = [var.service_name, "webui", "http"] + name = format("%s-webui-%s", var.service_name, "http") + port = "http" + provider = var.service_provider + } + + task "server" { + driver = "docker" + + env { + OLLAMA_BASE_URL = local.ollama_service + OPENAI_API_KEYS = var.openai_api_key + OPENAI_API_BASE_URLS = var.openai_api_key == "" ? "" : "https://api.openai.com/v1" + } + + config { + image = var.docker_image_webui + force_pull = var.docker_always_pull + ports = ["http"] + dns_servers = var.service_dns + volumes = local.volumes + } + } // task "server" + } // group "ollama-webui" +} // job "ollama" diff --git a/ollama/outputs.tf b/ollama/outputs.tf new file mode 100644 index 0000000..501e872 --- /dev/null +++ b/ollama/outputs.tf @@ -0,0 +1,18 @@ + +output "enabled" { + value = var.enabled +} + +output "service-http" { + value = { + name = format("%s-%s.%s.%s.", var.service_name, "http", var.namespace, "nomad") + port = var.port + } +} + +output "service-webui-http" { + value = { + name = format("%s-webui-%s.%s.%s.", var.service_name, "http", var.namespace, "nomad") + port = var.port_webui + } +} diff --git a/ollama/providers.tf b/ollama/providers.tf new file mode 100755 index 0000000..30b081b --- /dev/null +++ b/ollama/providers.tf @@ -0,0 +1,9 @@ + +terraform { + required_providers { + nomad = { + source = "hashicorp/nomad" + version = "~> 2.0.0" + } + } +}