Getting started with COS Lite on Canonical Kubernetes¶
In this tutorial you deploy a single-node COS Lite appliance, backed by hostPath storage.
Prerequisites¶
A 4cpu8gb node or better, with at least 40Gi disk space (see Sizing guide for production deployments).
Juju v3.6 installed (doc).
Canonical Kubernetes (
k8ssnap) installed, with local-storage (doc) and load-balancer (doc) enabled.Proxy (doc) and DNS (doc) for K8s are configured (if applicable).
K8s cloud added to Juju (doc).
Deploy COS Lite using Terraform¶
To deploy the COS Lite solution, create this root module and set the model to cos-lite:
terraform {
required_version = ">= 1.5"
required_providers {
juju = {
source = "juju/juju"
version = ">= 1.0"
}
}
}
variable "model" {
type = string
}
data "juju_model" "model" {
name = var.model
owner = "admin"
}
module "cos-lite" {
source = "git::https://github.com/canonical/observability-stack//terraform/cos-lite"
model = { uuid = data.juju_model.model.uuid }
risk = "edge"
internal_tls = true
}
Customize COS Lite
You can customize the deployment of COS Lite by overriding the defaults of these variables:
# We use constraints to set AntiAffinity in K8s
# https://discourse.charmhub.io/t/pod-priority-and-affinity-in-juju-charms/4091/13?u=jose
# FIXME: Passing an empty constraints value to the Juju Terraform provider currently
# causes the operation to fail due to https://github.com/juju/terraform-provider-juju/issues/344
# Therefore, we set a default value of "arch=amd64" for all applications.
variable "risk" {
description = "Risk level that the applications are (unless overwritten by individual channels) deployed from"
type = string
default = "edge"
}
variable "model" {
description = "Model configuration. When `uuid` is set, an existing model is looked up; otherwise a new model is created with the given fields. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/model"
type = object({
uuid = optional(string)
name = optional(string, "cos-lite")
cloud = optional(object({
name = string
region = optional(string)
}))
annotations = optional(map(string))
config = optional(map(string))
constraints = optional(string)
credential = optional(string)
target_controller = optional(string)
})
default = {}
validation {
condition = var.model.uuid == null || (
var.model.annotations == null &&
var.model.cloud == null &&
var.model.config == null &&
var.model.constraints == null &&
var.model.credential == null &&
var.model.target_controller == null
)
error_message = "When `model.uuid` is set, the model already exists; do not also set `annotations`, `cloud`, `config`, `constraints`, `credential`, or `target_controller`."
}
validation {
condition = var.model.uuid != null || (var.model.name != null && length(var.model.name) > 0)
error_message = "`model.name` must be non-empty when creating a model (i.e. when `model.uuid` is null)."
}
validation {
condition = var.model.uuid == null || can(regex("^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$", var.model.uuid))
error_message = "`model.uuid` must be a valid UUID."
}
}
# -------------- # Network configurations --------------
variable "internal_tls" {
description = "Specify whether to use TLS or not for internal COS communication. By default, TLS is enabled using self-signed-certificates"
type = bool
default = true
}
variable "external_certificates_offer_url" {
description = "A Juju offer URL (e.g. admin/external-ca.certificates) of a CA providing the 'tls_certificates' integration for Traefik to supply it with server certificates."
type = string
default = null
validation {
condition = (
(var.external_certificates_offer_url == null && var.external_ca_cert_offer_url == null) ||
(var.external_certificates_offer_url != null && var.external_ca_cert_offer_url != null)
)
error_message = "external_certificates_offer_url and external_ca_cert_offer_url must be supplied together (either both set or both null)."
}
}
variable "external_ca_cert_offer_url" {
description = "A Juju offer URL (e.g. admin/external-ca.send-ca-cert) of a CA providing the 'certificate_transfer' integration for applications to trust ingress via Traefik."
type = string
default = null
}
variable "postgresql_offer_url" {
description = "A Juju offer URL (e.g. admin/postgresql.database) of a PostgreSQL service providing the 'postgresql_client' integration for applications to connect to the database."
type = string
default = null
validation {
condition = !(var.postgresql_offer_url == null && var.grafana.units > 1)
error_message = "postgresql_offer_url must be supplied when Grafana is scaled > 1 due to its database requirements."
}
}
# -------------- # Ingress configurations --------------
variable "ingress" {
description = "Per-component toggle for ingress integrations"
type = object({
alertmanager = optional(bool, true)
catalogue = optional(bool, true)
grafana = optional(bool, true)
loki = optional(bool, true)
opentelemetry_collector = optional(bool, true)
prometheus = optional(bool, true)
})
default = {}
}
# -------------- # Application configurations --------------
variable "alertmanager" {
type = object({
app_name = optional(string, "alertmanager")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for Alertmanager. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "catalogue" {
type = object({
app_name = optional(string, "catalogue")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for Catalogue. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "grafana" {
type = object({
app_name = optional(string, "grafana")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for Grafana. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "loki" {
type = object({
app_name = optional(string, "loki")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for Loki. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "opentelemetry_collector" {
type = object({
app_name = optional(string, "otelcol")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for OpenTelemetry Collector. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "prometheus" {
type = object({
app_name = optional(string, "prometheus")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for Prometheus. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "ssc" {
type = object({
app_name = optional(string, "ca")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for self-signed-certificates. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
variable "traefik" {
type = object({
app_name = optional(string, "traefik")
config = optional(map(string), {})
constraints = optional(string, "arch=amd64")
resources = optional(map(string), {})
revision = optional(number, null)
storage_directives = optional(map(string), {})
units = optional(number, 1)
})
default = {}
description = "Application configuration for Traefik. For more details: https://registry.terraform.io/providers/juju/juju/latest/docs/resources/application"
}
Then, use terraform to deploy the module:
terraform init
terraform apply -var="model=cos-lite"
You can watch the model as it settles with:
juju status --relations --watch=5s
The status of your deployment should eventually be very similar to the following:
juju status --relations --storage
Model Controller Cloud/Region Version SLA Timestamp
cos-lite kub k8s 3.6.21 unsupported 11:39:52+02:00
App Version Status Scale Charm Channel Rev Address Exposed Message
alertmanager 0.31.0 active 1 alertmanager-k8s dev/edge 202 10.152.183.170 no
ca active 1 self-signed-certificates 1/edge 637 10.152.183.71 no
catalogue active 1 catalogue-k8s dev/edge 125 10.152.183.24 no
grafana 12.4.2 active 1 grafana-k8s dev/edge 186 10.152.183.59 no
loki 3.7.1 active 1 loki-k8s dev/edge 226 10.152.183.29 no
prometheus 3.11.1 active 1 prometheus-k8s dev/edge 292 10.152.183.104 no
traefik 2.11.0 active 1 traefik-k8s latest/edge 292 10.152.183.108 no Serving at http://192.168.178.192
Unit Workload Agent Address Ports Message
alertmanager/0* active idle 10.1.0.207
ca/0* active idle 10.1.0.155
catalogue/0* active idle 10.1.0.193
grafana/0* active idle 10.1.0.229
loki/0* active idle 10.1.0.133
prometheus/0* active idle 10.1.0.83
traefik/0* active idle 10.1.0.93 Serving at http://192.168.178.192
Offer Application Charm Rev Connected Endpoint Interface Role
alertmanager-karma-dashboard alertmanager alertmanager-k8s 202 0/0 karma-dashboard karma_dashboard provider
certificates ca self-signed-certificates 637 0/0 certificates tls-certificates provider
grafana-dashboards grafana grafana-k8s 186 0/0 grafana-dashboard grafana_dashboard requirer
loki-logging loki loki-k8s 226 0/0 logging loki_push_api provider
prometheus-metrics-endpoint prometheus prometheus-k8s 292 0/0 metrics-endpoint prometheus_scrape requirer
prometheus-receive-remote-write prometheus prometheus-k8s 292 0/0 receive-remote-write prometheus_remote_write provider
send-ca-cert ca self-signed-certificates 637 0/0 send-ca-cert certificate_transfer provider
Integration provider Requirer Interface Type Message
alertmanager:alerting loki:alertmanager alertmanager_dispatch regular
alertmanager:alerting prometheus:alertmanager alertmanager_dispatch regular
alertmanager:grafana-dashboard grafana:grafana-dashboard grafana_dashboard regular
alertmanager:grafana-source grafana:grafana-source grafana_datasource regular
alertmanager:replicas alertmanager:replicas alertmanager_replica peer
alertmanager:self-metrics-endpoint prometheus:metrics-endpoint prometheus_scrape regular
ca:certificates alertmanager:certificates tls-certificates regular
ca:certificates catalogue:certificates tls-certificates regular
ca:certificates grafana:certificates tls-certificates regular
ca:certificates loki:certificates tls-certificates regular
ca:certificates prometheus:certificates tls-certificates regular
ca:send-ca-cert traefik:receive-ca-cert certificate_transfer regular
catalogue:catalogue alertmanager:catalogue catalogue regular
catalogue:catalogue grafana:catalogue catalogue regular
catalogue:catalogue prometheus:catalogue catalogue regular
catalogue:replicas catalogue:replicas catalogue_replica peer
grafana:grafana grafana:grafana grafana_peers peer
grafana:metrics-endpoint prometheus:metrics-endpoint prometheus_scrape regular
grafana:replicas grafana:replicas grafana_replicas peer
loki:grafana-dashboard grafana:grafana-dashboard grafana_dashboard regular
loki:grafana-source grafana:grafana-source grafana_datasource regular
loki:metrics-endpoint prometheus:metrics-endpoint prometheus_scrape regular
loki:replicas loki:replicas loki_replica peer
prometheus:grafana-dashboard grafana:grafana-dashboard grafana_dashboard regular
prometheus:grafana-source grafana:grafana-source grafana_datasource regular
prometheus:prometheus-peers prometheus:prometheus-peers prometheus_peers peer
traefik:ingress alertmanager:ingress ingress regular
traefik:ingress catalogue:ingress ingress regular
traefik:ingress grafana:ingress ingress regular
traefik:ingress-per-unit loki:ingress ingress_per_unit regular
traefik:ingress-per-unit prometheus:ingress ingress_per_unit regular
traefik:metrics-endpoint prometheus:metrics-endpoint prometheus_scrape regular
traefik:peers traefik:peers traefik_peers peer
Storage Unit Storage ID Type Pool Mountpoint Size Status Message
alertmanager/0 data/8 filesystem kubernetes /var/lib/juju/storage/data/0 1.0 GiB attached Successfully provisioned volume pvc-cf00986d-5428-467b-89f9-a0788e996702
grafana/0 database/6 filesystem kubernetes /var/lib/juju/storage/database/0 1.0 GiB attached Successfully provisioned volume pvc-eddf2615-6147-4e78-8224-fcf69e8080c5
loki/0 active-index-directory/4 filesystem kubernetes /var/lib/juju/storage/active-index-directory/0 1.0 GiB attached Successfully provisioned volume pvc-3d7b9b8d-1d50-469c-b5ad-94201e517d41
loki/0 loki-chunks/5 filesystem kubernetes /var/lib/juju/storage/loki-chunks/0 1.0 GiB attached Successfully provisioned volume pvc-aa88c858-aa5f-4981-8330-7eb8245ea581
prometheus/0 database/3 filesystem kubernetes /var/lib/juju/storage/database/0 1.0 GiB attached Successfully provisioned volume pvc-e8ae59b8-e046-4041-9a44-9bde5b9950ae
traefik/0 configurations/2 filesystem kubernetes /var/lib/juju/storage/configurations/0 1.0 GiB attached Successfully provisioned volume pvc-29e9fd9a-b787-4056-9cd3-5135b6a1c442
Now COS Lite is good to go: you can relate software with it to begin the monitoring!
Obtain the Grafana admin password,
juju run grafana/leader get-admin-password
then head over to the URL listed in the output and use the provided password to log in.