From f7e818940ce9ff19d59728782285d9ea184f65a3 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 13 Jun 2025 12:47:12 -0400 Subject: [PATCH 01/19] feat: COS TF modules --- .gitignore | 29 + terraform/modules/aws-infra/README.md | 131 ++++ terraform/modules/aws-infra/justfile | 39 ++ terraform/modules/aws-infra/main.tf | 621 ++++++++++++++++++ terraform/modules/aws-infra/outputs.tf | 29 + terraform/modules/aws-infra/variables.tf | 23 + terraform/modules/aws-infra/versions.tf | 13 + terraform/modules/cos-lite/README.md | 46 ++ terraform/modules/cos-lite/main.tf | 384 +++++++++++ terraform/modules/cos-lite/outputs.tf | 27 + terraform/modules/cos-lite/variables.tf | 16 + terraform/modules/cos-lite/versions.tf | 9 + terraform/modules/cos/README.md | 510 ++++++++++++++ terraform/modules/cos/main.tf | 607 +++++++++++++++++ terraform/modules/cos/outputs.tf | 26 + terraform/modules/cos/variables.tf | 266 ++++++++ terraform/modules/cos/versions.tf | 9 + terraform/modules/loki/README.md | 59 ++ terraform/modules/loki/main.tf | 151 +++++ terraform/modules/loki/outputs.tf | 30 + terraform/modules/loki/variables.tf | 128 ++++ terraform/modules/loki/versions.tf | 9 + terraform/modules/mimir/README.md | 60 ++ terraform/modules/mimir/main.tf | 152 +++++ terraform/modules/mimir/outputs.tf | 31 + terraform/modules/mimir/variables.tf | 129 ++++ terraform/modules/mimir/versions.tf | 9 + terraform/modules/minio/main.tf | 43 ++ terraform/modules/minio/outputs.tf | 4 + .../modules/minio/scripts/s3management.sh | 112 ++++ terraform/modules/minio/variables.tf | 47 ++ terraform/modules/minio/version.tf | 9 + terraform/modules/tempo/README.md | 63 ++ terraform/modules/tempo/main.tf | 247 +++++++ terraform/modules/tempo/outputs.tf | 33 + terraform/modules/tempo/variables.tf | 176 +++++ terraform/modules/tempo/versions.tf | 9 + .../hcl-generator/generate_integrations.py | 116 ++++ 38 files changed, 4402 insertions(+) create mode 100644 terraform/modules/aws-infra/README.md create mode 100644 terraform/modules/aws-infra/justfile create mode 100644 terraform/modules/aws-infra/main.tf create mode 100644 terraform/modules/aws-infra/outputs.tf create mode 100644 terraform/modules/aws-infra/variables.tf create mode 100644 terraform/modules/aws-infra/versions.tf create mode 100644 terraform/modules/cos-lite/README.md create mode 100644 terraform/modules/cos-lite/main.tf create mode 100644 terraform/modules/cos-lite/outputs.tf create mode 100644 terraform/modules/cos-lite/variables.tf create mode 100644 terraform/modules/cos-lite/versions.tf create mode 100644 terraform/modules/cos/README.md create mode 100644 terraform/modules/cos/main.tf create mode 100644 terraform/modules/cos/outputs.tf create mode 100644 terraform/modules/cos/variables.tf create mode 100644 terraform/modules/cos/versions.tf create mode 100644 terraform/modules/loki/README.md create mode 100644 terraform/modules/loki/main.tf create mode 100644 terraform/modules/loki/outputs.tf create mode 100644 terraform/modules/loki/variables.tf create mode 100644 terraform/modules/loki/versions.tf create mode 100644 terraform/modules/mimir/README.md create mode 100644 terraform/modules/mimir/main.tf create mode 100644 terraform/modules/mimir/outputs.tf create mode 100644 terraform/modules/mimir/variables.tf create mode 100644 terraform/modules/mimir/versions.tf create mode 100644 terraform/modules/minio/main.tf create mode 100644 terraform/modules/minio/outputs.tf create mode 100755 terraform/modules/minio/scripts/s3management.sh create mode 100644 terraform/modules/minio/variables.tf create mode 100644 terraform/modules/minio/version.tf create mode 100644 terraform/modules/tempo/README.md create mode 100644 terraform/modules/tempo/main.tf create mode 100644 terraform/modules/tempo/outputs.tf create mode 100644 terraform/modules/tempo/variables.tf create mode 100644 terraform/modules/tempo/versions.tf create mode 100644 terraform/scripts/hcl-generator/generate_integrations.py diff --git a/.gitignore b/.gitignore index f641208b..21f514e7 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,32 @@ docs/.DS_Store docs/__pycache__ docs/.idea/ docs/.vscode/ + +# Terraform +**/.terraform/* +*.tfstate +*.tfstate.* +*.tfplan +*.backup +*.bak +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Ignore CLI configuration files +.terraformrc +terraform.rc +.terraform.lock.hcl diff --git a/terraform/modules/aws-infra/README.md b/terraform/modules/aws-infra/README.md new file mode 100644 index 00000000..c36c58af --- /dev/null +++ b/terraform/modules/aws-infra/README.md @@ -0,0 +1,131 @@ +# From Zero to COS: AWS Provisioning & Deployment + +This directory contains Terraform modules for automating the process of bootstrapping a fresh AWS account to a fully running instance of COS deployed on a 3-node EKS cluster. + + +## Prerequisites + +Make sure you have the following installed: + +- [Terraform](https://developer.hashicorp.com/terraform/tutorials/aws-get-started/install-cli) >= v1.10.4 +- [AWS CLI](https://github.com/aws/aws-cli) >= 2.26.7 +- [Juju](https://snapcraft.io/juju) >= 3.0.3 +- [Just](https://github.com/casey/just) >= 1.40.0 + +### AWS Credentials Setup + +Before running any commands, ensure your AWS credentials are configured on the host: + +You can do this using one of the following methods: + +- [Environment variables](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-envvars.html) +- [Credentials file](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html) + +--- + + +## Usage + +### Bootstrap AWS infrastructure + Juju controller: +In order to provision the AWS infrastructure required for COS, create a `main.tf` file with the following content. + +```hcl +module "aws_infra" { + source = "git::https://github.com/canonical/observability//terraform/modules/aws-infra" + region = var.region + cos_cloud_name = var.cos_cloud_name + cos_controller_name = var.cos_controller_name + cos_model_name = var.cos_model_name +} + +variable "region" { + description = "The AWS region where the resources will be provisioned." + type = string +} + +variable "cos_cloud_name" { + description = "The name to assign to the Kubernetes cloud when running 'juju add-k8s'." + type = string + default = "cos-cloud" +} + +variable "cos_controller_name" { + description = "The name to assign to the Juju controller that will manage COS." + type = string + default = "cos-controller" +} + +variable "cos_model_name" { + description = "The name of the Juju model where COS will be deployed." + type = string + default = "cos" +} + +``` +Then, create a `terraform.tfvars` file with the following content: + +```hcl +region = "" +cos_cloud_name = "" +cos_controller_name = "" +cos_model_name = "" +``` +Then, use terraform to deploy the module: +```bash +terraform init +terraform apply -var-file=terraform.tfvars +``` +### Full bootstrap: go from zero to COS: + +You can fully bootstrap AWS infra and COS in one of two ways: +#### Option 1: Manual 2-Step Process +1. [Bootstrap AWS infra](#bootstrap-aws-infrastructure--juju-controller) + +Set up the necessary infrastructure and Juju controller on AWS using the `aws-infra` module. + +2. [Deploy COS on the freshly created infra](../cos/README.md#deploy-cos-on-aws-eks) + +Use the output from step 1 to deploy COS on top of your provisioned infrastructure using the `cos` module. + +#### Option 2: Automated via `just` + +Clone this repository and run the appropriate `just` command to fully automate the bootstrap process. +This command handles: + +1. Bootstrapping the AWS infrastructure +2. Piping all required input to deploy COS on top + + +Create a `terraform.tfvars` file with the following content: +```hcl +region = "" +# Add other optional variables below +cos_cloud_name = "" +cos_controller_name = "" +cos_model_name = "" +``` +Then, run `just apply` + +--- + + +## Inputs + +| Variable Name | Description | +|----------|-------------------------| +| region | AWS region to provision resources in | +| cos_cloud_name | The name to assign to the Kubernetes cloud when running 'juju add-k8s' | +| cos_controller_name | The name to assign to the Juju controller that will manage COS | +| cos_model_name | The name of the Juju model where COS will be deployed | + +--- + +## Available Commands (via `just`) + +- `just init` – Initialize Terraform for AWS infra and COS +- `just apply` – Provision AWS infrastructure, then pipe the necessary outputs to provision COS on top +- `just destroy` – Tear down everything (COS + infra) + +--- + + diff --git a/terraform/modules/aws-infra/justfile b/terraform/modules/aws-infra/justfile new file mode 100644 index 00000000..a7fe6021 --- /dev/null +++ b/terraform/modules/aws-infra/justfile @@ -0,0 +1,39 @@ +set quiet # Recipes are silent by default + +init: + terraform init + terraform -chdir=../cos init + +apply: init apply_infra apply_cos + +destroy: destroy_cos destroy_infra + +apply_infra: + terraform apply + +apply_cos: + terraform -chdir=../cos apply \ + -var="loki_bucket=$(terraform output -raw loki_bucket)" \ + -var="tempo_bucket=$(terraform output -raw tempo_bucket)" \ + -var="mimir_bucket=$(terraform output -raw mimir_bucket)" \ + -var="s3_endpoint=$(terraform output -raw s3_endpoint)" \ + -var="s3_user=$(terraform output -raw s3_user)" \ + -var="s3_password=$(terraform output -raw s3_password)" \ + -var="model_name=$(terraform output -raw cos_model)" \ + -var="ssc_channel=1/edge" \ + -var="cloud=aws" \ + +destroy_infra: + terraform destroy + +destroy_cos: + terraform -chdir=../cos destroy \ + -var="loki_bucket=$(terraform output -raw loki_bucket)" \ + -var="tempo_bucket=$(terraform output -raw tempo_bucket)" \ + -var="mimir_bucket=$(terraform output -raw mimir_bucket)" \ + -var="s3_endpoint=$(terraform output -raw s3_endpoint)" \ + -var="s3_user=$(terraform output -raw s3_user)" \ + -var="s3_password=$(terraform output -raw s3_password)" \ + -var="model_name=$(terraform output -raw cos_model)" \ + -var="ssc_channel=1/edge" \ + -var="cloud=aws" \ diff --git a/terraform/modules/aws-infra/main.tf b/terraform/modules/aws-infra/main.tf new file mode 100644 index 00000000..85350df0 --- /dev/null +++ b/terraform/modules/aws-infra/main.tf @@ -0,0 +1,621 @@ +provider "aws" { + region = var.region +} + +data "aws_availability_zones" "available" { + state = "available" +} +locals { + cos-cluster-name = "cos-cluster" +} + +## ==================================================== +## Network infra +## ==================================================== +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" + enable_dns_hostnames = true + enable_dns_support = true +} + +# To allow workloads running on ec2 nodes to access the public internet (e.g: to pull OCI images) +resource "aws_internet_gateway" "internet_gateway" { + vpc_id = aws_vpc.main.id +} + +# To deploy a 3-node eks cluster, it's recommended to spread them across at least 2 subnets in different AZs. +resource "aws_subnet" "public_subnet_1" { + vpc_id = aws_vpc.main.id + cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, 1) + availability_zone = element(data.aws_availability_zones.available.names, 0) + map_public_ip_on_launch = true + tags = { + "kubernetes.io/role/elb" = "1" + } +} + +resource "aws_subnet" "public_subnet_2" { + vpc_id = aws_vpc.main.id + cidr_block = cidrsubnet(aws_vpc.main.cidr_block, 8, 2) + availability_zone = element(data.aws_availability_zones.available.names, 1) + map_public_ip_on_launch = true + tags = { + "kubernetes.io/role/elb" = "1" + } +} + +resource "aws_route_table" "public_rt" { + vpc_id = aws_vpc.main.id + route { + cidr_block = "0.0.0.0/0" + gateway_id = aws_internet_gateway.internet_gateway.id + } +} + + +resource "aws_route_table_association" "public_rta_1" { + subnet_id = aws_subnet.public_subnet_1.id + route_table_id = aws_route_table.public_rt.id +} + +resource "aws_route_table_association" "public_rta_2" { + subnet_id = aws_subnet.public_subnet_2.id + route_table_id = aws_route_table.public_rt.id +} + +## ==================================================== +## Kubernetes infra +## ==================================================== + +# a role that eks cluster can assume +resource "aws_iam_role" "cos_cluster_role" { + name = "cos-cluster-role" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = ["eks.amazonaws.com"] + } + Action = [ + "sts:AssumeRole", + "sts:TagSession" + ] + } + ] + }) +} + +# eks role needs the below permissions +resource "aws_iam_role_policy_attachment" "cos_cluster_policy" { + role = aws_iam_role.cos_cluster_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" +} +resource "aws_iam_role_policy_attachment" "cos_cluster_network_policy" { + role = aws_iam_role.cos_cluster_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSNetworkingPolicy" +} +resource "aws_iam_role_policy_attachment" "cos_cluster_lb_policy" { + role = aws_iam_role.cos_cluster_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSLoadBalancingPolicy" +} +resource "aws_iam_role_policy_attachment" "cos_cluster_storage_policy" { + role = aws_iam_role.cos_cluster_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSBlockStoragePolicy" +} +resource "aws_iam_role_policy_attachment" "cos_cluster_compute_policy" { + role = aws_iam_role.cos_cluster_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSComputePolicy" +} + +resource "aws_eks_cluster" "cos_cluster" { + name = local.cos-cluster-name + bootstrap_self_managed_addons = false + access_config { + authentication_mode = "API" + } + role_arn = aws_iam_role.cos_cluster_role.arn + compute_config { + enabled = true + } + + kubernetes_network_config { + elastic_load_balancing { + enabled = true + } + } + + storage_config { + block_storage { + enabled = true + } + } + + vpc_config { + subnet_ids = [ + aws_subnet.public_subnet_1.id, + aws_subnet.public_subnet_2.id, + ] + } + + depends_on = [ + aws_iam_role_policy_attachment.cos_cluster_policy, + aws_iam_role_policy_attachment.cos_cluster_network_policy, + aws_iam_role_policy_attachment.cos_cluster_lb_policy, + aws_iam_role_policy_attachment.cos_cluster_storage_policy, + aws_iam_role_policy_attachment.cos_cluster_compute_policy, + aws_route_table.public_rt, + aws_internet_gateway.internet_gateway, + ] +} + +# enable network communication +resource "aws_eks_addon" "eks_kube_proxy_addon" { + cluster_name = aws_eks_cluster.cos_cluster.name + addon_name = "kube-proxy" +} + +# enable name resolution for all pods +resource "aws_eks_addon" "eks_core_dns_addon" { + cluster_name = aws_eks_cluster.cos_cluster.name + addon_name = "coredns" + depends_on = [aws_eks_node_group.cos_workers] +} + +# enables assigning a private IPv4 from your VPC to each pod. +resource "aws_eks_addon" "eks_vpc_cni_addon" { + cluster_name = aws_eks_cluster.cos_cluster.name + addon_name = "vpc-cni" +} + +# provides the ability to manage credentials for your application +# needed for EBS CSI driver +resource "aws_eks_addon" "eks_pod_identity_addon" { + cluster_name = aws_eks_cluster.cos_cluster.name + addon_name = "eks-pod-identity-agent" +} + +resource "aws_iam_role" "eks_ebs_role" { + name = "eks-ebs-csi-driver-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "pods.eks.amazonaws.com" + } + Action = ["sts:AssumeRole", "sts:TagSession"] + } + ] + }) +} +resource "aws_iam_role_policy_attachment" "eks_ebs_policy" { + role = aws_iam_role.eks_ebs_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy" +} + + +# enable Amazon EBS CSI driver. +# Give access to "ebs-csi-controller-sa" SA to provision and manage Amazon EBS volumes. +resource "aws_eks_addon" "eks_ebs_addon" { + cluster_name = aws_eks_cluster.cos_cluster.name + addon_name = "aws-ebs-csi-driver" + pod_identity_association { + role_arn = aws_iam_role.eks_ebs_role.arn + service_account = "ebs-csi-controller-sa" + } + depends_on = [aws_eks_node_group.cos_workers] +} + +# Worker nodes need the below permissions +resource "aws_iam_role" "workers_role" { + name = "workers-role" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) +} + +resource "aws_iam_role_policy_attachment" "workers_nodes_policy" { + role = aws_iam_role.workers_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy" +} + +resource "aws_iam_role_policy_attachment" "workers_cni_policy" { + role = aws_iam_role.workers_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy" +} + +resource "aws_iam_role_policy_attachment" "workers_registry_policy" { + role = aws_iam_role.workers_role.name + policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly" +} + +# create 3 AWS-managed worker nodes +resource "aws_eks_node_group" "cos_workers" { + cluster_name = aws_eks_cluster.cos_cluster.name + node_group_name = "cos-workers" + node_role_arn = aws_iam_role.workers_role.arn + subnet_ids = [ + aws_subnet.public_subnet_1.id, + aws_subnet.public_subnet_2.id, + ] + scaling_config { + min_size = 3 + max_size = 3 + desired_size = 3 + } + disk_size = 50 + instance_types = [ + "t3.xlarge" + ] + ami_type = "AL2_x86_64" + + depends_on = [ + aws_iam_role_policy_attachment.workers_nodes_policy, + aws_iam_role_policy_attachment.workers_registry_policy, + aws_iam_role_policy_attachment.workers_cni_policy, + aws_eks_cluster.cos_cluster, + ] +} + +## ==================================================== +## Bootstrap Juju +## ==================================================== + +# Authorise the current user to access the K8s cluster resources (i.e K8s RBAC) +# needed for a later step (i.e juju add-k8s) +data "aws_caller_identity" "admin" {} + +data "aws_iam_session_context" "admin_iam" { + arn = data.aws_caller_identity.admin.arn +} + +resource "aws_eks_access_policy_association" "admin_eks_admin_policy" { + cluster_name = aws_eks_cluster.cos_cluster.name + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy" + principal_arn = data.aws_iam_session_context.admin_iam.issuer_arn + access_scope { + type = "cluster" + } +} + +resource "aws_eks_access_policy_association" "admin_eks_cluster_admin_policy" { + cluster_name = aws_eks_cluster.cos_cluster.name + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + principal_arn = data.aws_iam_session_context.admin_iam.issuer_arn + access_scope { + type = "cluster" + } +} + +resource "aws_eks_access_entry" "admin_access_entry" { + cluster_name = aws_eks_cluster.cos_cluster.name + principal_arn = data.aws_iam_session_context.admin_iam.issuer_arn +} + +# create a role for the juju controller (i.e an ec2 instance) +# with the necessary permissions to manage juju resources that interact with AWS resources. +resource "aws_iam_role" "juju_controller_role" { + name = "juju-controller-role" + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = ["ec2.amazonaws.com"] + } + Action = [ + "sts:AssumeRole", + ] + } + ] + }) + +} + +# permissions needed: https://discourse.charmhub.io/t/juju-aws-permissions/5307 +resource "aws_iam_policy" "juju_controller_policy" { + name = "juju-controller-policy" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + "Sid" : "JujuEC2Actions", + Action = [ + "ec2:AssociateIamInstanceProfile", + "ec2:AttachVolume", + "ec2:AuthoriseSecurityGroupIngress", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:DeleteSecurityGroup", + "ec2:DeleteVolume", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeIamInstanceProfileAssociations", + "ec2:DescribeInstances", + "ec2:DescribeInstanceTypeOfferings", + "ec2:DescribeInstanceTypes", + "ec2:DescribeInternetGateways", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSpotPriceHistory", + "ec2:DescribeSubnets", + "ec2:DescribeVolumes", + "ec2:DescribeVpcs", + "ec2:DetachVolume", + "ec2:RevokeSecurityGroupIngress", + "ec2:RunInstances", + "ec2:TerminateInstances" + ] + Effect = "Allow" + Resource = "*" + }, + { + "Sid" : "JujuIAMActions", + "Effect" : "Allow", + "Action" : [ + "iam:AddRoleToInstanceProfile", + "iam:CreateInstanceProfile", + "iam:CreateRole", + "iam:DeleteInstanceProfile", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:GetInstanceProfile", + "iam:GetRole", + "iam:ListInstanceProfiles", + "iam:ListRolePolicies", + "iam:ListRoles", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:RemoveRoleFromInstanceProfile" + ], + "Resource" : "*" + }, + { + "Sid" : "JujuSSMActions", + "Effect" : "Allow", + "Action" : [ + "ssm:ListInstanceAssociations", + "ssm:UpdateInstanceInformation" + ], + "Resource" : "*" + } + + ] + }) +} + +resource "aws_iam_role_policy_attachment" "juju_controller_policy_attach" { + role = aws_iam_role.juju_controller_role.name + policy_arn = aws_iam_policy.juju_controller_policy.arn +} + +resource "aws_iam_instance_profile" "juju_ctrl_instance_profile" { + name = "juju-ctrl-instance-profile" + role = aws_iam_role.juju_controller_role.name +} + +# Authorise the juju controller to access the K8s cluster resources (i.e K8s RBAC) +resource "aws_eks_access_policy_association" "ctrl_access_eks_admin" { + cluster_name = aws_eks_cluster.cos_cluster.name + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSAdminPolicy" + principal_arn = aws_iam_role.juju_controller_role.arn + + access_scope { + type = "cluster" + } +} + +resource "aws_eks_access_policy_association" "ctrl_access_eks_cluster_admin" { + cluster_name = aws_eks_cluster.cos_cluster.name + policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy" + principal_arn = aws_iam_role.juju_controller_role.arn + + access_scope { + type = "cluster" + } +} + +resource "aws_eks_access_entry" "juju_controller_access_entry" { + cluster_name = aws_eks_cluster.cos_cluster.name + principal_arn = aws_iam_role.juju_controller_role.arn +} + + +# create an IAM user with permissions that can bootstrap a juju controller on aws. +# The managed role used to run this terraform doesn't work for some reason. +resource "aws_iam_user" "juju_bootstrap_user" { + name = "juju-bootstrap" +} + +resource "aws_iam_user_policy" "juju_bootstrap_policy" { + name = "juju-bootstrap-policy" + user = aws_iam_user.juju_bootstrap_user.name + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "ec2:*", + ] + Effect = "Allow" + Resource = "*" + }, + { + "Effect" : "Allow", + "Action" : [ + "iam:AddRoleToInstanceProfile", + "iam:CreateInstanceProfile", + "iam:CreateRole", + "iam:DeleteInstanceProfile", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:GetInstanceProfile", + "iam:GetRole", + "iam:ListInstanceProfiles", + "iam:ListRolePolicies", + "iam:ListRoles", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:RemoveRoleFromInstanceProfile" + ], + "Resource" : "*" + }, + ] + }) + +} + +resource "aws_iam_access_key" "juju_bootstrap_access_key" { + user = aws_iam_user.juju_bootstrap_user.name +} + +# to bootstrap a controller on aws, we need to provide the aws credentials +# through a credentials.yaml file +resource "local_sensitive_file" "aws_credentials" { + depends_on = [aws_iam_access_key.juju_bootstrap_access_key] + + content = yamlencode({ + credentials : { + aws : { + bootstrap-juju : { + auth-type : "access-key", + access-key : aws_iam_access_key.juju_bootstrap_access_key.id, + secret-key : aws_iam_access_key.juju_bootstrap_access_key.secret, + } } + } + }) + filename = "${path.root}/.terraform/tmp/credentials.yaml" +} + + +# run local juju commands to bootstrap a juju controller using aws_iam_user.juju_bootstrap_user credentials +# then, when the controller is running, it will use the aws_iam_instance_profile.juju_ctrl_instance_profile +# to give the controller access to manage AWS resources +resource "null_resource" "bootstrap_juju" { + + triggers = { + # uncomment if you need to force destroy then create + # once = timestamp() + cos-controller = var.cos_controller_name + } + + depends_on = [local_sensitive_file.aws_credentials, + aws_eks_node_group.cos_workers, + aws_eks_addon.eks_ebs_addon, + aws_eks_access_entry.juju_controller_access_entry, + aws_eks_access_entry.admin_access_entry, + aws_eks_addon.eks_vpc_cni_addon, + aws_eks_addon.eks_kube_proxy_addon, + aws_eks_addon.eks_core_dns_addon, + aws_eks_addon.eks_pod_identity_addon, + aws_iam_policy.juju_controller_policy, + aws_route_table_association.public_rta_1, + aws_route_table_association.public_rta_2, + aws_eks_access_policy_association.ctrl_access_eks_cluster_admin, + aws_eks_access_policy_association.admin_eks_admin_policy, + aws_iam_role.juju_controller_role, + aws_iam_instance_profile.juju_ctrl_instance_profile, + aws_iam_role_policy_attachment.juju_controller_policy_attach, + aws_iam_role_policy_attachment.eks_ebs_policy, + aws_eks_access_policy_association.ctrl_access_eks_admin, + aws_iam_user_policy.juju_bootstrap_policy, + aws_eks_access_policy_association.admin_eks_cluster_admin_policy, + ] + + provisioner "local-exec" { + when = create + command = <<-EOT + juju remove-credential aws bootstrap-juju --client + juju add-credential aws --client -f ${local_sensitive_file.aws_credentials.filename} + + if ! juju controllers | grep -q '^${var.cos_controller_name}'; then + juju bootstrap --bootstrap-constraints="instance-role=${aws_iam_instance_profile.juju_ctrl_instance_profile.name}" aws/${var.region} ${var.cos_controller_name} --config vpc-id=${aws_vpc.main.id} --config vpc-id-force=true --credential bootstrap-juju + else + echo "controller already exists, skipping bootstrap." + fi + aws eks --region ${var.region} update-kubeconfig --name ${local.cos-cluster-name} + /snap/juju/current/bin/juju add-k8s ${var.cos_cloud_name} --controller ${var.cos_controller_name} + juju add-model ${var.cos_model_name} ${var.cos_cloud_name}/${var.region} + EOT + } + + provisioner "local-exec" { + when = destroy + command = <<-EOT + + if juju controllers | grep -q '^${self.triggers.cos-controller}'; then + juju kill-controller ${self.triggers.cos-controller} --timeout 0 --no-prompt + else + echo "Skipping controller deletion." + fi + EOT + } +} + + +## ==================================================== +## Create S3 buckets +## ==================================================== + +resource "aws_s3_bucket" "tempo_s3" { + bucket = "cos-tempo-bucket" + force_destroy = true +} + +resource "aws_s3_bucket" "loki_s3" { + bucket = "cos-loki-bucket" + force_destroy = true +} + +resource "aws_s3_bucket" "mimir_s3" { + bucket = "cos-mimir-bucket" + force_destroy = true +} + +# currently, our charms require the existence of an S3 access key and secret key +# and we can only obtain them through an IAM user. +# create an IAM user to access the buckets. +# TODO: create 3 IAM users, one for each bucket access +resource "aws_iam_user" "s3_access" { + name = "s3-access" +} + +resource "aws_iam_user_policy" "s3_access_policy" { + name = "s3-access-policy" + user = aws_iam_user.s3_access.name + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "s3:*", + ] + Effect = "Allow" + Resource = "*" + }, + ] + }) +} + +resource "aws_iam_access_key" "s3_access_key" { + user = aws_iam_user.s3_access.name +} + diff --git a/terraform/modules/aws-infra/outputs.tf b/terraform/modules/aws-infra/outputs.tf new file mode 100644 index 00000000..c70d423d --- /dev/null +++ b/terraform/modules/aws-infra/outputs.tf @@ -0,0 +1,29 @@ + + +output "loki_bucket" { + value = aws_s3_bucket.loki_s3.bucket +} + +output "mimir_bucket" { + value = aws_s3_bucket.mimir_s3.bucket +} +output "tempo_bucket" { + value = aws_s3_bucket.tempo_s3.bucket +} + +output "s3_endpoint" { + value = "https://s3.${var.region}.amazonaws.com" +} + +output "s3_user" { + value = aws_iam_access_key.s3_access_key.id +} + +output "s3_password" { + value = aws_iam_access_key.s3_access_key.secret + sensitive = true +} + +output "cos_model" { + value = var.cos_model_name +} diff --git a/terraform/modules/aws-infra/variables.tf b/terraform/modules/aws-infra/variables.tf new file mode 100644 index 00000000..0ac2558a --- /dev/null +++ b/terraform/modules/aws-infra/variables.tf @@ -0,0 +1,23 @@ + +variable "region" { + description = "The AWS region where the resources will be provisioned." + type = string +} + +variable "cos_cloud_name" { + description = "The name to assign to the Kubernetes cloud when running 'juju add-k8s'." + type = string + default = "cos-cloud" +} + +variable "cos_controller_name" { + description = "The name to assign to the Juju controller that will manage COS." + type = string + default = "cos-controller" +} + +variable "cos_model_name" { + description = "The name of the Juju model where COS will be deployed." + type = string + default = "cos" +} diff --git a/terraform/modules/aws-infra/versions.tf b/terraform/modules/aws-infra/versions.tf new file mode 100644 index 00000000..ccb1e344 --- /dev/null +++ b/terraform/modules/aws-infra/versions.tf @@ -0,0 +1,13 @@ +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + null = { + source = "hashicorp/null" + version = "~> 3.0" + } + } +} diff --git a/terraform/modules/cos-lite/README.md b/terraform/modules/cos-lite/README.md new file mode 100644 index 00000000..c99645ae --- /dev/null +++ b/terraform/modules/cos-lite/README.md @@ -0,0 +1,46 @@ +Terraform module for COS solution + +This is a Terraform module facilitating the deployment of COS solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). + +The COS Lite solution consists of the following Terraform modules: +- [grafana-k8s](https://github.com/canonical/grafana-k8s-operator): Visualization, monitoring, and dashboards. +- [alertmanager-k8s](https://github.com/canonical/alertmanager-k8s-operator): Handles alerts sent by clients applications. +- [prometheus-k8s](https://github.com/canonical/prometheus-k8s-operator/tree/main/terraform/): Backend for metrics +- [loki-k8s](https://github.com/canonical/loki-k8s-operator/tree/main/terraform): Backend for logs +- [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator): certificates operator to secure traffic with TLS. + +## Requirements + +This module requires a `juju` model to be available. Refer to the [usage section](#usage) below for more details. + +## API + +### Inputs + +The module offers the following configurable inputs: + +| Name | Type | Description | Required | +|--------------|--------|------------------------------------------------------------------------|-------------| +| `channel` | string | Channel that the charms are deployed from | latest/edge | +| `model_name` | string | Name of the model that the charm is deployed on | | +| `use_tls` | bool | Specify whether to use TLS or not for coordinator-worker communication | | + +### Outputs + +Upon application, the module exports the following outputs: + +| Name | Description | +|------------|-----------------------------| +| `app_name` | Application name | +| `provides` | Map of `provides` endpoints | +| `requires` | Map of `requires` endpoints | + +## Usage + + +### Basic usage + +Users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. + +To deploy this module with its needed dependency, you can run `terraform apply -var="model_name=" -auto-approve`. This would deploy all COS HA solution modules in the same model. + diff --git a/terraform/modules/cos-lite/main.tf b/terraform/modules/cos-lite/main.tf new file mode 100644 index 00000000..71331838 --- /dev/null +++ b/terraform/modules/cos-lite/main.tf @@ -0,0 +1,384 @@ +# -------------- # Applications -------------- + +module "ssc" { + count = var.use_tls ? 1 : 0 + source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + model = var.model_name + channel = var.channel +} + +module "alertmanager" { + source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform" + app_name = "alertmanager" + model_name = var.model_name + channel = var.channel +} + +module "catalogue" { + source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform" + app_name = "catalogue" + model_name = var.model_name + channel = var.channel +} + +module "prometheus" { + source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform" + app_name = "prometheus" + model_name = var.model_name + channel = var.channel +} + +module "loki" { + source = "git::https://github.com/canonical/loki-k8s-operator//terraform" + app_name = "loki" + model_name = var.model_name + channel = var.channel +} + +module "grafana" { + source = "git::https://github.com/canonical/grafana-k8s-operator//terraform" + app_name = "grafana" + model_name = var.model_name + channel = var.channel +} + +module "traefik" { + source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + app_name = "traefik" + model_name = var.model_name + channel = var.channel +} + + +# -------------- # Integrations -------------- + + +# Provided by Alertmanager + +resource "juju_integration" "alertmanager_grafana_dashboards" { + model = var.model_name + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.grafana_dashboard + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_dashboard + } +} + +resource "juju_integration" "alertmanager_prometheus" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.alertmanager + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.alerting + } +} + +resource "juju_integration" "alertmanager_self_monitoring_prometheus" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.metrics_endpoint + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.self_metrics_endpoint + } +} + +resource "juju_integration" "alertmanager_loki" { + model = var.model_name + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.alertmanager + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.alerting + } +} + + +resource "juju_integration" "grafana_source_alertmanager" { + model = var.model_name + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + +# Provided by Grafana + +resource "juju_integration" "grafana_self_monitoring_prometheus" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.metrics_endpoint + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.metrics_endpoint + } +} + + +# Provided by Prometheus + +resource "juju_integration" "prometheus_grafana_dashboards_provider" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.grafana_dashboard + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_dashboard + } +} + +resource "juju_integration" "prometheus_grafana_source" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + + + + +# Provided by Loki + +resource "juju_integration" "loki_grafana_dashboards_provider" { + model = var.model_name + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.grafana_dashboard + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_dashboard + } +} + +resource "juju_integration" "loki_grafana_source" { + model = var.model_name + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + +resource "juju_integration" "loki_self_monitoring_prometheus" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.metrics_endpoint + } + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.metrics_endpoint + } +} + + +# Provided by Catalogue + +resource "juju_integration" "catalogue_alertmanager" { + model = var.model_name + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.catalogue + } +} + +resource "juju_integration" "catalogue_grafana" { + model = var.model_name + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.catalogue + } +} + +resource "juju_integration" "catalogue_prometheus" { + model = var.model_name + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.catalogue + } +} + + +# Provided by Traefik + +resource "juju_integration" "alertmanager_ingress" { + model = var.model_name + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.ingress + } +} + + +resource "juju_integration" "catalogue_ingress" { + model = var.model_name + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress + } + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.ingress + } +} + +resource "juju_integration" "grafana_ingress" { + model = var.model_name + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.traefik_route + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.ingress + } +} + +resource "juju_integration" "prometheus_ingress" { + model = var.model_name + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress_per_unit + } + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.ingress + } +} + +resource "juju_integration" "loki_ingress" { + model = var.model_name + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress_per_unit + } + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.ingress + } +} + +resource "juju_integration" "traefik_self_monitoring_prometheus" { + model = var.model_name + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.metrics_endpoint + } + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.metrics_endpoint + } +} + +# -------------- # Offers -------------- + +resource "juju_offer" "alertmanager-karma-dashboard" { + name = "alertmanager-karma-dashboard" + model = var.model_name + application_name = module.alertmanager.app_name + endpoint = "karma-dashboard" +} + +resource "juju_offer" "grafana-dashboards" { + name = "grafana-dashboards" + model = var.model_name + application_name = module.grafana.app_name + endpoint = "grafana-dashboard" +} + +resource "juju_offer" "loki-logging" { + name = "loki-logging" + model = var.model_name + application_name = module.loki.app_name + endpoint = "logging" +} + +resource "juju_offer" "prometheus-receive-remote-write" { + name = "prometheus-receive-remote-write" + model = var.model_name + application_name = module.prometheus.app_name + endpoint = "receive-remote-write" +} diff --git a/terraform/modules/cos-lite/outputs.tf b/terraform/modules/cos-lite/outputs.tf new file mode 100644 index 00000000..06e50fc7 --- /dev/null +++ b/terraform/modules/cos-lite/outputs.tf @@ -0,0 +1,27 @@ +output "app_names" { + value = merge( + { + alertmanager = module.alertmanager.app_name, + catalogue = module.catalogue.app_name, + grafana = module.grafana.app_name, + loki = module.loki.app_name, + prometheus = module.prometheus.app_name, + traefik = module.traefik.app_name, + } + ) +} + +output "grafana" { + description = "Outputs from the Grafana module" + value = module.grafana +} + +output "prometheus" { + description = "Outputs from the prometheus module" + value = module.prometheus +} + +output "loki" { + description = "Outputs from the Loki module" + value = module.loki +} diff --git a/terraform/modules/cos-lite/variables.tf b/terraform/modules/cos-lite/variables.tf new file mode 100644 index 00000000..c8f3a356 --- /dev/null +++ b/terraform/modules/cos-lite/variables.tf @@ -0,0 +1,16 @@ +variable "channel" { + description = "Charms channel" + type = string + default = "latest/edge" +} + +variable "model_name" { + description = "Model name" + type = string +} + +variable "use_tls" { + description = "Specify whether to use TLS or not for coordinator-worker communication. By default, TLS is enabled through self-signed-certificates" + type = bool + default = true +} diff --git a/terraform/modules/cos-lite/versions.tf b/terraform/modules/cos-lite/versions.tf new file mode 100644 index 00000000..77b64403 --- /dev/null +++ b/terraform/modules/cos-lite/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5" + required_providers { + juju = { + source = "juju/juju" + version = "~> 0.14" + } + } +} \ No newline at end of file diff --git a/terraform/modules/cos/README.md b/terraform/modules/cos/README.md new file mode 100644 index 00000000..e0387019 --- /dev/null +++ b/terraform/modules/cos/README.md @@ -0,0 +1,510 @@ +# Terraform module for COS solution + +This is a Terraform module facilitating the deployment of COS solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). + +The HA solution consists of the following Terraform modules: +- [alertmanager-k8s](https://github.com/canonical/alertmanager-k8s-operator/tree/main/terraform): Handles alerts sent by clients applications. +- [grafana-k8s](https://github.com/canonical/grafana-k8s-operator/tree/main/terraform): Visualization, monitoring, and dashboards. +- [grafana-agent-k8s](https://github.com/canonical/grafana-agent-k8s-operator/tree/main/terraform): Aggregate and send telemetry data. +- [loki](https://github.com/canonical/observability/tree/main/terraform/modules/loki): Backend for logs. +- [mimir](https://github.com/canonical/observability/tree/main/terraform/modules/mimir): Backend for metrics. +- [tempo](https://github.com/canonical/observability/tree/main/terraform/modules/tempo): Backend for traces. +- [s3-integrator](https://github.com/canonical/s3-integrator): facade for S3 storage configurations. +- [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator/tree/main/terraform): certificates operator to secure traffic with TLS. +- [traefik](https://github.com/canonical/traefik-k8s-operator/tree/main/terraform): ingress. + +This Terraform module deploys COS with Mimir, Tempo and Loki in their microservices modes, and other charms in monolithic mode. + +> [!NOTE] +> `s3-integrator` itself doesn't act as an S3 object storage system. For the HA solution to be functional, `s3-integrator` needs to point to an S3-like storage. See [this guide](https://discourse.charmhub.io/t/cos-lite-docs-set-up-minio/15211) to learn how to connect to an S3-like storage for traces. + +## Requirements +This module requires a `juju` model to be available. Refer to the [usage section](#usage) below for more details. + +## API + +### Inputs +The module offers the following configurable inputs: + +| Name | Type | Description | Default | +| - | - |----------------------------------------------------------------| - | +| `channel` | string | Channel that all the charms (unless overwritten) are deployed from | +| `ssc_channel` | string | Channel that the self-signed certificates charm is deployed from | latest/edge | +| `traefik_channel` | string | Channel that the traefik charm is deployed from | latest/edge | +| `model` | string | Reference to an existing model resource or data source for the model to deploy to | +| `use_tls` | bool | Specify whether to use TLS or not for in-cluster communication | +| `cloud` | string | Kubernetes cloud or environment where this COS module will be deployed | self-managed | +| `loki_coordinator_units` | number | Number of Loki coordinator units | +| `loki_backend_units` | number | Number of Loki worker units with `backend` role | +| `loki_read_units` | number | Number of Loki worker units with `read` role | +| `loki_write_units` | number | Number of Loki worker units with `write` role | +| `mimir_coordinator_units` | number | Number of Mimir coordinator units | +| `mimir_backend_units` | number | Number of Mimir worker units with `backend` role | +| `mimir_read_units` | number | Number of Mimir worker units with `read` role | +| `mimir_write_units` | number | Number of Mimir worker units with `write` role | +| `tempo_coordinator_units` | number | Number of Tempo coordinator units | +| `tempo_compactor_units` | number | Number of Tempo worker units with `compactor` role | +| `tempo_distributor_units` | number | Number of Tempo worker units with `distributor` role | +| `tempo_ingester_units` | number | Number of Tempo worker units with `ingester` role | +| `tempo_metrics_generator_units` | number | Number of Tempo worker units with `metrics_generator` role | +| `tempo_querier_units` | number | Number of Tempo worker units with `querier` role | +| `tempo_query_frontend_units` | number | Number of Tempo worker units with `query_frontend` role | +| `s3_access_key` | string | Access key credential to connect to the S3 provider | 1 | +| `s3_secret_key` | string | Secret key credential to connect to the S3 provider | 1 | +| `s3_endpoint` | string | S3 provider endpoint | 1 | +| `loki_bucket` | string | Name of the bucket in which Loki should store its logs | 1 | +| `mimir_bucket` | string | Name of the bucket in which Mimir should store its metrics | 1 | +| `tempo_bucket` | string | Name of the bucket in which Tempo should store its traces | 1 | +| `alertmanager_revision` | number | Revision number of the charm | null | +| `catalogue_revision` | number | Revision number of the charm | null | +| `grafana_revision` | number | Revision number of the charm | null | +| `grafana_agent_revision` | number | Revision number of the charm | null | +| `loki_coordinator_revision` | number | Revision number of the charm | null | +| `loki_worker_revision` | number | Revision number of the charm | null | +| `mimir_coordinator_revision` | number | Revision number of the charm | null | +| `mimir_worker_revision` | number | Revision number of the charm | null | +| `ssc_revision` | number | Revision number of the charm | null | +| `s3_integrator_revision` | number | Revision number of the charm | null | +| `tempo_coordinator_revision` | number | Revision number of the charm | null | +| `tempo_worker_revision` | number | Revision number of the charm | null | +| `traefik_revision` | number | Revision number of the charm | null | + +### Outputs +Upon application, the module exports the following outputs: + +| Name | Type | Description | +| - | - | - | +| `alertmanager`| module | Alertmanager module | +| `catalogue`| module | Catalogue module | +| `grafana`| module | Grafana module | +| `grafana_agent`| module | Grafana agent module | +| `loki`| module | Loki module | +| `mimir`| module | Mimir module | +| `ssc`| module | Self-signed certificates module | +| `tempo`| module | Tempo module | +| `traefik`| module | Traefik module | + + +## Usage + + +### Basic usage + +Users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. + +To deploy this module with its needed dependency, you can run `terraform apply -var="model=" -auto-approve`. This would deploy all COS HA solution modules in the same model. + +### High Availability + +By default, this Terraform module will deploy each worker with `3` unit. If you want to scale each Loki, Mimir or Tempo worker unit please check the variables available for that purpose in `variables.tf`. + +### Minimal sample deployment. + +In order to deploy COS with just one unit per worker charm create a `main.tf` file with the following content: + +```hcl +terraform { + required_version = ">= 1.5" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + null = { + source = "hashicorp/null" + version = "~> 3.0" + } + } +} + +# COS module that deploy the whole Canonical Observability Stack +module "cos" { + source = "git::https://github.com/canonical/observability//terraform/modules/cos" + model = "cos" + channel = "2/edge" + s3_integrator_channel = "2/edge" + ssc_channel = "1/edge" + traefik_channel = "latest/edge" + cloud = "self-managed" + use_tls = true + s3_endpoint = "http://S3_HOST_IP:8080" + s3_secret_key = "secret-key" + s3_access_key = "access-key" + loki_bucket = "loki" + mimir_bucket = "mimir" + tempo_bucket = "tempo" + loki_coordinator_units = 3 + loki_backend_units = 3 + loki_read_units = 3 + loki_write_units = 3 + mimir_coordinator_units = 3 + mimir_backend_units = 3 + mimir_read_units = 3 + mimir_write_units = 3 + tempo_coordinator_units = 3 + tempo_compactor_units = 3 + tempo_distributor_units = 3 + tempo_ingester_units = 3 + tempo_metrics_generator_units = 3 + tempo_querier_units = 3 + tempo_query_frontend_units = 3 + alertmanager_revision = null + catalogue_revision = null + grafana_revision = null + grafana_agent_revision = null + loki_coordinator_revision = null + loki_worker_revision = null + mimir_coordinator_revision = null + mimir_worker_revision = null + ssc_revision = null + s3_integrator_revision = 157 # FIXME: This is a temporary fix until the spec for the s3-integrator is stable. + tempo_coordinator_revision = null + tempo_worker_revision = null + traefik_revision = null +} +``` + +Then, use terraform to deploy the module: + +```shell +terraform init +terraform apply +``` + +Some minutes after running these two commands, we have a distributed COS deployment! + +```shell +$ juju status --relations +Model Controller Cloud/Region Version SLA Timestamp +cos microk8s microk8s/localhost 3.6.2 unsupported 20:16:42-03:00 + +App Version Status Scale Charm Channel Rev Address Exposed Message +alertmanager 0.27.0 active 1 alertmanager-k8s latest/edge 156 10.152.183.57 no +catalogue active 1 catalogue-k8s latest/edge 81 10.152.183.88 no +grafana 9.5.3 active 1 grafana-k8s latest/edge 141 10.152.183.138 no +grafana-agent 0.40.4 active 1 grafana-agent-k8s latest/edge 112 10.152.183.37 no grafana-dashboards-provider: off +loki active 3 loki-coordinator-k8s latest/edge 20 10.152.183.201 no +loki-backend 3.0.0 active 3 loki-worker-k8s latest/edge 34 10.152.183.112 no backend ready. +loki-read 3.0.0 active 3 loki-worker-k8s latest/edge 34 10.152.183.87 no read ready. +loki-s3-integrator active 1 s3-integrator latest/edge 139 10.152.183.20 no +loki-write 3.0.0 active 3 loki-worker-k8s latest/edge 34 10.152.183.167 no write ready. +mimir active 3 mimir-coordinator-k8s latest/edge 38 10.152.183.207 no +mimir-backend 2.13.0 active 3 mimir-worker-k8s latest/edge 45 10.152.183.45 no backend ready. +mimir-read 2.13.0 active 3 mimir-worker-k8s latest/edge 45 10.152.183.160 no read ready. +mimir-s3-integrator active 1 s3-integrator latest/edge 139 10.152.183.85 no +mimir-write 2.13.0 active 3 mimir-worker-k8s latest/edge 45 10.152.183.125 no write ready. +self-signed-certificates active 1 self-signed-certificates 1/edge 268 10.152.183.34 no +tempo active 3 tempo-coordinator-k8s latest/edge 70 10.152.183.72 no +tempo-compactor 2.7.1 active 3 tempo-worker-k8s latest/edge 52 10.152.183.99 no compactor ready. +tempo-distributor 2.7.1 active 3 tempo-worker-k8s latest/edge 52 10.152.183.162 no distributor ready. +tempo-ingester 2.7.1 active 3 tempo-worker-k8s latest/edge 52 10.152.183.195 no ingester ready. +tempo-metrics-generator 2.7.1 active 3 tempo-worker-k8s latest/edge 52 10.152.183.122 no metrics-generator ready. +tempo-querier 2.7.1 active 3 tempo-worker-k8s latest/edge 52 10.152.183.136 no querier ready. +tempo-query-frontend 2.7.1 active 3 tempo-worker-k8s latest/edge 52 10.152.183.105 no query-frontend ready. +tempo-s3-integrator active 1 s3-integrator latest/edge 139 10.152.183.121 no +traefik 2.11.0 active 1 traefik-k8s latest/edge 234 10.152.183.110 no Serving at 192.168.1.244 + +Unit Workload Agent Address Ports Message +alertmanager/0* active idle 10.1.167.134 +catalogue/0* active idle 10.1.167.150 +grafana-agent/0* active idle 10.1.167.149 grafana-dashboards-provider: off +grafana/0* active idle 10.1.167.173 +loki-backend/0* active idle 10.1.167.148 backend ready. +loki-backend/1 active idle 10.1.167.171 backend ready. +loki-backend/2 active idle 10.1.167.188 backend ready. +loki-read/0 active idle 10.1.167.153 read ready. +loki-read/1 active idle 10.1.167.180 read ready. +loki-read/2* active idle 10.1.167.183 read ready. +loki-s3-integrator/0* active idle 10.1.167.169 +loki-write/0* active idle 10.1.167.144 write ready. +loki-write/1 active idle 10.1.167.142 write ready. +loki-write/2 active idle 10.1.167.187 write ready. +loki/0* active idle 10.1.167.174 +mimir-backend/0* active idle 10.1.167.139 backend ready. +mimir-backend/1 active idle 10.1.167.128 backend ready. +mimir-backend/2 active idle 10.1.167.177 backend ready. +mimir-read/0* active idle 10.1.167.151 read ready. +mimir-read/1 active idle 10.1.167.163 read ready. +mimir-read/2 active idle 10.1.167.132 read ready. +mimir-s3-integrator/0* active idle 10.1.167.137 +mimir-write/0* active idle 10.1.167.152 write ready. +mimir-write/1 active idle 10.1.167.167 write ready. +mimir-write/2 active idle 10.1.167.143 write ready. +mimir/0* active idle 10.1.167.135 +self-signed-certificates/0* active idle 10.1.167.166 +tempo-compactor/0 active idle 10.1.167.181 compactor ready. +tempo-compactor/1* active idle 10.1.167.168 compactor ready. +tempo-compactor/2 active idle 10.1.167.129 compactor ready. +tempo-distributor/0* active idle 10.1.167.157 distributor ready. +tempo-distributor/1 active idle 10.1.167.131 distributor ready. +tempo-distributor/2 active idle 10.1.167.186 distributor ready. +tempo-ingester/0* active idle 10.1.167.191 ingester ready. +tempo-ingester/1 active idle 10.1.167.133 ingester ready. +tempo-ingester/2 active idle 10.1.167.179 ingester ready. +tempo-metrics-generator/0* active idle 10.1.167.147 metrics-generator ready. +tempo-metrics-generator/1 active idle 10.1.167.159 metrics-generator ready. +tempo-metrics-generator/2 active idle 10.1.167.146 metrics-generator ready. +tempo-querier/0 active idle 10.1.167.170 querier ready. +tempo-querier/1 active idle 10.1.167.140 querier ready. +tempo-querier/2* active idle 10.1.167.165 querier ready. +tempo-query-frontend/0* active idle 10.1.167.162 query-frontend ready. +tempo-query-frontend/1 active idle 10.1.167.190 query-frontend ready. +tempo-query-frontend/2 active idle 10.1.167.184 query-frontend ready. +tempo-s3-integrator/0* active idle 10.1.167.172 +tempo/0* active idle 10.1.167.189 +traefik/0* active idle 10.1.167.182 Serving at 192.168.1.244 + +Integration provider Requirer Interface Type Message +alertmanager:alerting loki:alertmanager alertmanager_dispatch regular +alertmanager:alerting mimir: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 grafana-agent:metrics-endpoint prometheus_scrape regular +catalogue:catalogue alertmanager:catalogue catalogue regular +catalogue:catalogue grafana:catalogue catalogue regular +catalogue:catalogue mimir:catalogue catalogue regular +catalogue:catalogue tempo:catalogue catalogue regular +catalogue:replicas catalogue:replicas catalogue_replica peer +grafana-agent:logging-provider loki:logging-consumer loki_push_api regular +grafana-agent:logging-provider tempo:logging loki_push_api regular +grafana-agent:peers grafana-agent:peers grafana_agent_replica peer +grafana-agent:tracing-provider grafana:charm-tracing tracing regular +grafana-agent:tracing-provider loki:charm-tracing tracing regular +grafana-agent:tracing-provider mimir:charm-tracing tracing regular +grafana:grafana grafana:grafana grafana_peers peer +grafana:replicas grafana:replicas grafana_replicas peer +loki-s3-integrator:s3-credentials loki:s3 s3 regular +loki-s3-integrator:s3-integrator-peers loki-s3-integrator:s3-integrator-peers s3-integrator-peers peer +loki:grafana-dashboards-provider grafana:grafana-dashboard grafana_dashboard regular +loki:grafana-source grafana:grafana-source grafana_datasource regular +loki:logging grafana-agent:logging-consumer loki_push_api regular +loki:loki-cluster loki-backend:loki-cluster loki_cluster regular +loki:loki-cluster loki-read:loki-cluster loki_cluster regular +loki:loki-cluster loki-write:loki-cluster loki_cluster regular +loki:self-metrics-endpoint grafana-agent:metrics-endpoint prometheus_scrape regular +mimir-s3-integrator:s3-credentials mimir:s3 s3 regular +mimir-s3-integrator:s3-integrator-peers mimir-s3-integrator:s3-integrator-peers s3-integrator-peers peer +mimir:grafana-dashboards-provider grafana:grafana-dashboard grafana_dashboard regular +mimir:grafana-source grafana:grafana-source grafana_datasource regular +mimir:mimir-cluster mimir-backend:mimir-cluster mimir_cluster regular +mimir:mimir-cluster mimir-read:mimir-cluster mimir_cluster regular +mimir:mimir-cluster mimir-write:mimir-cluster mimir_cluster regular +mimir:receive-remote-write grafana-agent:send-remote-write prometheus_remote_write regular +mimir:receive-remote-write tempo:send-remote-write prometheus_remote_write regular +mimir:self-metrics-endpoint grafana-agent:metrics-endpoint prometheus_scrape regular +tempo-s3-integrator:s3-credentials tempo:s3 s3 regular +tempo-s3-integrator:s3-integrator-peers tempo-s3-integrator:s3-integrator-peers s3-integrator-peers peer +tempo:grafana-source grafana:grafana-source grafana_datasource regular +tempo:metrics-endpoint grafana-agent:metrics-endpoint prometheus_scrape regular +tempo:peers tempo:peers tempo_peers peer +tempo:tempo-cluster tempo-compactor:tempo-cluster tempo_cluster regular +tempo:tempo-cluster tempo-distributor:tempo-cluster tempo_cluster regular +tempo:tempo-cluster tempo-ingester:tempo-cluster tempo_cluster regular +tempo:tempo-cluster tempo-metrics-generator:tempo-cluster tempo_cluster regular +tempo:tempo-cluster tempo-querier:tempo-cluster tempo_cluster regular +tempo:tempo-cluster tempo-query-frontend:tempo-cluster tempo_cluster regular +tempo:tracing grafana-agent:tracing tracing regular +traefik:ingress alertmanager:ingress ingress regular +traefik:ingress catalogue:ingress ingress regular +traefik:ingress loki:ingress ingress regular +traefik:ingress mimir:ingress ingress regular +traefik:peers traefik:peers traefik_peers peer +traefik:traefik-route grafana:ingress traefik_route regular +traefik:traefik-route tempo:ingress traefik_route regular +``` + +### Deploy COS on AWS EKS + +> **Note:** This deployment assumes that the required AWS infrastructure is already provisioned and that a Juju controller has been bootstrapped. +> Additionally, a Juju model must be ready in advance. +> +> See [provision AWS infrastructure](../aws-infra/README.md) + +In order to deploy COS on AWS, create a `main.tf` file with the following content. + +```hcl +# COS module that deploy the whole Canonical Observability Stack +module "cos" { + source = "git::https://github.com/canonical/observability//terraform/modules/cos" + model_name = var.model_name + channel = var.channel + s3_endpoint = var.s3_endpoint + s3_access_key = var.s3_access_key + s3_secret_key = var.s3_secret_key + loki_bucket = var.loki_bucket + mimir_bucket = var.mimir_bucket + tempo_bucket = var.tempo_bucket + loki_backend_units = var.loki_backend_units + loki_read_units = var.loki_read_units + loki_write_units = var.loki_write_units + mimir_backend_units = var.mimir_backend_units + mimir_read_units = var.mimir_read_units + mimir_write_units = var.mimir_write_units + tempo_compactor_units = var.tempo_compactor_units + tempo_distributor_units = var.tempo_distributor_units + tempo_ingester_units = var.tempo_ingester_units + tempo_metrics_generator_units = var.tempo_metrics_generator_units + tempo_querier_units = var.tempo_querier_units + tempo_query_frontend_units = var.tempo_query_frontend_units + cloud = var.cloud + ssc_channel = var.ssc_channel +} + +variable "channel" { + description = "Charms channel" + type = string + default = "latest/edge" +} + +variable "model_name" { + description = "Model name" + type = string +} + +variable "use_tls" { + description = "Specify whether to use TLS or not for coordinator-worker communication. By default, TLS is enabled through self-signed-certificates" + type = bool + default = true +} + +variable "s3_endpoint" { + description = "S3 endpoint" + type = string +} + +variable "s3_access_key" { + description = "S3 access key" + type = string + sensitive = true +} + +variable "s3_secret_key" { + description = "S3 secret key" + type = string + sensitive = true +} + +variable "loki_bucket" { + description = "Loki bucket name" + type = string + sensitive = true +} + +variable "mimir_bucket" { + description = "Mimir bucket name" + type = string + sensitive = true +} + +variable "tempo_bucket" { + description = "Tempo bucket name" + type = string + sensitive = true +} + +variable "loki_backend_units" { + description = "Number of Loki worker units with backend role" + type = number + default = 3 +} + +variable "loki_read_units" { + description = "Number of Loki worker units with read role" + type = number + default = 3 +} + +variable "loki_write_units" { + description = "Number of Loki worker units with write roles" + type = number + default = 3 +} + +variable "mimir_backend_units" { + description = "Number of Mimir worker units with backend role" + type = number + default = 3 +} + +variable "mimir_read_units" { + description = "Number of Mimir worker units with read role" + type = number + default = 3 +} + +variable "mimir_write_units" { + description = "Number of Mimir worker units with write role" + type = number + default = 3 +} + +variable "tempo_compactor_units" { + description = "Number of Tempo worker units with compactor role" + type = number + default = 3 +} + +variable "tempo_distributor_units" { + description = "Number of Tempo worker units with distributor role" + type = number + default = 3 +} + +variable "tempo_ingester_units" { + description = "Number of Tempo worker units with ingester role" + type = number + default = 3 +} + +variable "tempo_metrics_generator_units" { + description = "Number of Tempo worker units with metrics-generator role" + type = number + default = 3 +} + +variable "tempo_querier_units" { + description = "Number of Tempo worker units with querier role" + type = number + default = 3 +} +variable "tempo_query_frontend_units" { + description = "Number of Tempo worker units with query-frontend role" + type = number + default = 3 +} + +variable "cloud" { + description = "Kubernetes cloud or environment where this COS module will be deployed (e.g self-managed, aws)" + type = string + default = "self-managed" +} + +# ssc doesn't have a "latest" track for ubuntu@24.04 base. +variable "ssc_channel" { + description = "self-signed certificates charm channel." + type = string + default = "latest/edge" +} + +``` +Then, create a `aws.tfvars` file with the following content: + +```hcl +cloud = "aws" +# If you're deploying on an ubuntu@24.04 base +ssc_channel = "1/edge" +model = "" +s3_endpoint = "" +s3_access_key = "" +s3_secret_key = "" +loki_bucket = "" +mimir_bucket = "" +tempo_bucket = "" +``` + +Then, use terraform to deploy the module: +```bash +terraform init +terraform apply -var-file=aws.tfvars +``` diff --git a/terraform/modules/cos/main.tf b/terraform/modules/cos/main.tf new file mode 100644 index 00000000..07309b5e --- /dev/null +++ b/terraform/modules/cos/main.tf @@ -0,0 +1,607 @@ +# -------------- # Applications -------------- + +module "alertmanager" { + source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform" + app_name = "alertmanager" + model = var.model + channel = var.channel + revision = var.alertmanager_revision +} + +module "catalogue" { + source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform" + app_name = "catalogue" + model = var.model + channel = var.channel + revision = var.catalogue_revision +} + +module "grafana" { + source = "git::https://github.com/canonical/grafana-k8s-operator//terraform" + app_name = "grafana" + model = var.model + channel = var.channel + revision = var.grafana_revision +} + +module "grafana_agent" { + source = "git::https://github.com/canonical/grafana-agent-k8s-operator//terraform" + app_name = "grafana-agent" + model = var.model + channel = var.channel + revision = var.grafana_agent_revision +} + +module "loki" { + source = "git::https://github.com/canonical/observability-stack//terraform/modules/loki?ref=feat/tf-migration" + model = var.model + channel = var.channel + s3_integrator_channel = var.s3_integrator_channel + s3_integrator_revision = var.s3_integrator_revision + coordinator_revision = var.loki_coordinator_revision + worker_revision = var.loki_worker_revision + coordinator_units = var.loki_coordinator_units + backend_units = var.loki_backend_units + read_units = var.loki_read_units + write_units = var.loki_write_units + s3_bucket = var.loki_bucket + s3_endpoint = var.s3_endpoint + s3_secret_key = var.s3_secret_key + s3_access_key = var.s3_access_key + anti_affinity = var.anti_affinity +} + +module "mimir" { + source = "git::https://github.com/canonical/observability-stack//terraform/modules/mimir?ref=feat/tf-migration" + model = var.model + channel = var.channel + s3_integrator_channel = var.s3_integrator_channel + s3_integrator_revision = var.s3_integrator_revision + coordinator_revision = var.mimir_coordinator_revision + worker_revision = var.mimir_worker_revision + coordinator_units = var.mimir_coordinator_units + backend_units = var.mimir_backend_units + read_units = var.mimir_read_units + write_units = var.mimir_write_units + s3_bucket = var.mimir_bucket + s3_endpoint = var.s3_endpoint + s3_secret_key = var.s3_secret_key + s3_access_key = var.s3_access_key + anti_affinity = var.anti_affinity +} + +module "ssc" { + count = var.use_tls ? 1 : 0 + source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + model = var.model + channel = var.ssc_channel + revision = var.ssc_revision +} + +module "tempo" { + source = "git::https://github.com/canonical/observability-stack//terraform/modules/tempo?ref=feat/tf-migration" + model = var.model + channel = var.channel + s3_integrator_channel = var.s3_integrator_channel + s3_integrator_revision = var.s3_integrator_revision + coordinator_revision = var.tempo_coordinator_revision + worker_revision = var.tempo_worker_revision + coordinator_units = var.tempo_coordinator_units + compactor_units = var.tempo_compactor_units + distributor_units = var.tempo_distributor_units + ingester_units = var.tempo_ingester_units + metrics_generator_units = var.tempo_metrics_generator_units + querier_units = var.tempo_querier_units + query_frontend_units = var.tempo_query_frontend_units + s3_bucket = var.tempo_bucket + s3_endpoint = var.s3_endpoint + s3_access_key = var.s3_access_key + s3_secret_key = var.s3_secret_key + anti_affinity = var.anti_affinity +} + +module "traefik" { + source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + app_name = "traefik" + model = var.model + channel = var.traefik_channel + config = var.cloud == "aws" ? { "loadbalancer_annotations" = "service.beta.kubernetes.io/aws-load-balancer-scheme=internet-facing" } : {} + revision = var.traefik_revision +} + +# -------------- # Integrations -------------- + +# Provided by Alertmanager + +resource "juju_integration" "alertmanager_grafana_dashboards" { + model = var.model + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.grafana_dashboard + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_dashboard + } +} + +resource "juju_integration" "mimir_alertmanager" { + model = var.model + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.alertmanager + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.alerting + } +} + +resource "juju_integration" "loki_alertmanager" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.alertmanager + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.alerting + } +} + +resource "juju_integration" "agent_alertmanager_metrics" { + model = var.model + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.self_metrics_endpoint + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.metrics_endpoint + } +} + +resource "juju_integration" "grafana_source_alertmanager" { + model = var.model + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + +# Provided by Mimir + +resource "juju_integration" "mimir_grafana_dashboards_provider" { + model = var.model + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.grafana_dashboards_provider + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_dashboard + } +} + +resource "juju_integration" "mimir_grafana_source" { + model = var.model + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + +resource "juju_integration" "mimir_tracing_grafana_agent_traicing_provider" { + model = var.model + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.charm_tracing + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.tracing_provider + } +} + + +resource "juju_integration" "mimir_self_metrics_endpoint_grafana_agent_metrics_endpoint" { + model = var.model + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.self_metrics_endpoint + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.metrics_endpoint + } +} + + +# Provided by Loki + +resource "juju_integration" "loki_grafana_dashboards_provider" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.grafana_dashboards_provider + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_dashboard + } +} + +resource "juju_integration" "loki_grafana_source" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + +resource "juju_integration" "loki_logging_consumer_grafana_agent_logging_provider" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.logging_consumer + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.logging_provider + } +} + +resource "juju_integration" "loki_logging_grafana_agent_logging_consumer" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.logging + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.logging_consumer + } +} + +resource "juju_integration" "loki_tracing_grafana_agent_traicing_provider" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.charm_tracing + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.tracing_provider + } +} + +# Provided by Tempo +resource "juju_integration" "tempo_grafana_source" { + model = var.model + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.grafana_source + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.grafana_source + } +} + +resource "juju_integration" "tempo_tracing_grafana_agent_tracing" { + model = var.model + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.tracing + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.tracing + } +} + +resource "juju_integration" "tempo_metrics_endpoint_grafana_agent_metrics_endpoint" { + model = var.model + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.metrics_endpoint + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.metrics_endpoint + } +} + +resource "juju_integration" "tempo_logging_grafana_agent_logging_provider" { + model = var.model + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.logging + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.logging_provider + } +} + +resource "juju_integration" "tempo_send_remote_write_mimir_receive_remote_write" { + model = var.model + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.send-remote-write + } + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.receive_remote_write + } +} + +# Provided by Catalogue + +resource "juju_integration" "alertmanager_catalogue" { + model = var.model + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.catalogue + } +} + +resource "juju_integration" "grafana_catalogue" { + model = var.model + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.catalogue + } +} + +resource "juju_integration" "tempo_catalogue" { + model = var.model + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.catalogue + } +} + +resource "juju_integration" "mimir_catalogue" { + model = var.model + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.catalogue + } + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.catalogue + } +} + +# Provided by Traefik + +resource "juju_integration" "alertmanager_ingress" { + model = var.model + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.ingress + } +} + +resource "juju_integration" "catalogue_ingress" { + model = var.model + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress + } + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.ingress + } +} + +resource "juju_integration" "grafana_ingress" { + model = var.model + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.traefik_route + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.ingress + } +} + +resource "juju_integration" "mimir_ingress" { + model = var.model + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress + } + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.ingress + } +} + +resource "juju_integration" "loki_ingress" { + model = var.model + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.ingress + } + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.ingress + } +} + +resource "juju_integration" "tempo_ingress" { + model = var.model + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.traefik_route + } + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.ingress + } +} + +# Grafana agent + +resource "juju_integration" "agent_loki_metrics" { + model = var.model + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.self_metrics_endpoint + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.metrics_endpoint + } +} + +resource "juju_integration" "agent_mimir_metrics" { + model = var.model + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.receive_remote_write + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.send_remote_write + } +} + +# Provided by Grafana + +resource "juju_integration" "grafana_tracing_grafana_agent_traicing_provider" { + model = var.model + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.charm_tracing + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.tracing_provider + } +} + +# -------------- # Offers -------------- + +resource "juju_offer" "alertmanager-karma-dashboard" { + name = "alertmanager-karma-dashboard" + model = var.model + application_name = module.alertmanager.app_name + endpoint = "karma-dashboard" +} + +resource "juju_offer" "grafana-dashboards" { + name = "grafana-dashboards" + model = var.model + application_name = module.grafana.app_name + endpoint = "grafana-dashboard" +} + +resource "juju_offer" "loki-logging" { + name = "loki-logging" + model = var.model + application_name = module.loki.app_names.loki_coordinator + endpoint = "logging" +} + +resource "juju_offer" "mimir-receive-remote-write" { + name = "mimir-receive-remote-write" + model = var.model + application_name = module.mimir.app_names.mimir_coordinator + endpoint = "receive-remote-write" +} diff --git a/terraform/modules/cos/outputs.tf b/terraform/modules/cos/outputs.tf new file mode 100644 index 00000000..0de44f9b --- /dev/null +++ b/terraform/modules/cos/outputs.tf @@ -0,0 +1,26 @@ +# -------------- # Integration offers -------------- # + +output "offers" { + value = { + alertmanager_karma_dashboard = juju_offer.alertmanager-karma-dashboard + grafana_dashboards = juju_offer.grafana-dashboards + loki_logging = juju_offer.loki-logging + mimir_receive_remote_write = juju_offer.mimir-receive-remote-write + } +} + +# -------------- # Sub-modules -------------- # + +output "components" { + value = { + alertmanager = module.alertmanager + catalogue = module.catalogue + grafana = module.grafana + grafana_agent = module.grafana_agent + loki = module.loki + mimir = module.mimir + ssc = module.ssc + tempo = module.tempo + traefik = module.traefik + } +} diff --git a/terraform/modules/cos/variables.tf b/terraform/modules/cos/variables.tf new file mode 100644 index 00000000..a100b5d7 --- /dev/null +++ b/terraform/modules/cos/variables.tf @@ -0,0 +1,266 @@ + +# the list of kubernetes clouds where this COS module can be deployed. +locals { + clouds = ["aws", "self-managed"] +} + +variable "channel" { + description = "Channel that the charms are (unless overwritten by external_channels) deployed from" + type = string +} + +variable "model" { + description = "Reference to an existing model resource or data source for the model to deploy to" + type = string +} + +variable "use_tls" { + description = "Specify whether to use TLS or not for coordinator-worker communication. By default, TLS is enabled through self-signed-certificates" + type = bool + default = true +} + +variable "cloud" { + description = "Kubernetes cloud or environment where this COS module will be deployed (e.g self-managed, aws)" + type = string + default = "self-managed" + validation { + condition = contains(local.clouds, var.cloud) + error_message = "Allowed values are: ${join(", ", local.clouds)}." + } +} + +variable "anti_affinity" { + description = "Enable anti-affinity constraints across all HA modules (Mimir, Loki, Tempo)" + type = bool + default = true +} + +# -------------- # External channels -------------- +# O11y does not own these charms, so we allow users to specify their channels directly. + +variable "ssc_channel" { + description = "Channel that the self-signed certificates charm is deployed from" + type = string + default = "1/stable" +} + +variable "s3_integrator_channel" { + description = "Channel that the s3-integrator charm is deployed from" + type = string + default = "2/edge" +} + +variable "traefik_channel" { + description = "Channel that the Traefik charm is deployed from" + type = string + default = "latest/stable" +} + +# -------------- # S3 storage configuration -------------- + +variable "s3_endpoint" { + description = "S3 endpoint" + type = string +} + +variable "s3_access_key" { + description = "S3 access-key credential" + type = string +} + +variable "s3_secret_key" { + description = "S3 secret-key credential" + type = string + sensitive = true +} + +variable "loki_bucket" { + description = "Loki bucket name" + type = string + sensitive = true +} + +variable "mimir_bucket" { + description = "Mimir bucket name" + type = string + sensitive = true +} + +variable "tempo_bucket" { + description = "Tempo bucket name" + type = string + sensitive = true +} + +# -------------- # Charm revisions -------------- + +variable "alertmanager_revision" { + description = "Revision number of the Alertmanager charm" + type = number + default = null +} + +variable "catalogue_revision" { + description = "Revision number of the Catalogue charm" + type = number + default = null +} + +variable "grafana_revision" { + description = "Revision number of the Grafana charm" + type = number + default = null +} + +variable "grafana_agent_revision" { + description = "Revision number of the Grafana agent charm" + type = number + default = null +} + +variable "loki_coordinator_revision" { + description = "Revision number of the Loki coordinator charm" + type = number + default = null +} + +variable "loki_worker_revision" { + description = "Revision number of the Loki worker charm" + type = number + default = null +} + +variable "mimir_coordinator_revision" { + description = "Revision number of the Mimir coordinator charm" + type = number + default = null +} + +variable "mimir_worker_revision" { + description = "Revision number of the Mimir worker charm" + type = number + default = null +} + +variable "ssc_revision" { + description = "Revision number of the self-signed certificates charm" + type = number + default = null +} + +variable "s3_integrator_revision" { + description = "Revision number of the s3-integrator charm" + type = number + default = 157 # FIXME: https://github.com/canonical/observability/issues/342 +} + +variable "tempo_coordinator_revision" { + description = "Revision number of the Tempo coordinator charm" + type = number + default = null +} + +variable "tempo_worker_revision" { + description = "Revision number of the Tempo worker charm" + type = number + default = null +} + +variable "traefik_revision" { + description = "Revision number of the Traefik charm" + type = number + default = null +} + +# -------------- # Charm unit counts -------------- + +variable "loki_backend_units" { + description = "Number of Loki worker units with backend role" + type = number + default = 3 +} + +variable "loki_read_units" { + description = "Number of Loki worker units with read role" + type = number + default = 3 +} + +variable "loki_write_units" { + description = "Number of Loki worker units with write roles" + type = number + default = 3 +} + +variable "loki_coordinator_units" { + description = "Number of Loki coordinator units" + type = number + default = 3 +} + +variable "mimir_backend_units" { + description = "Number of Mimir worker units with backend role" + type = number + default = 3 +} + +variable "mimir_read_units" { + description = "Number of Mimir worker units with read role" + type = number + default = 3 +} + +variable "mimir_write_units" { + description = "Number of Mimir worker units with write role" + type = number + default = 3 +} + +variable "mimir_coordinator_units" { + description = "Number of Mimir coordinator units" + type = number + default = 3 +} + +variable "tempo_compactor_units" { + description = "Number of Tempo worker units with compactor role" + type = number + default = 3 +} + +variable "tempo_distributor_units" { + description = "Number of Tempo worker units with distributor role" + type = number + default = 3 +} + +variable "tempo_ingester_units" { + description = "Number of Tempo worker units with ingester role" + type = number + default = 3 +} + +variable "tempo_metrics_generator_units" { + description = "Number of Tempo worker units with metrics-generator role" + type = number + default = 3 +} + +variable "tempo_querier_units" { + description = "Number of Tempo worker units with querier role" + type = number + default = 3 +} + +variable "tempo_query_frontend_units" { + description = "Number of Tempo worker units with query-frontend role" + type = number + default = 3 +} + +variable "tempo_coordinator_units" { + description = "Number of Tempo coordinator units" + type = number + default = 3 +} diff --git a/terraform/modules/cos/versions.tf b/terraform/modules/cos/versions.tf new file mode 100644 index 00000000..cde98c1a --- /dev/null +++ b/terraform/modules/cos/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5" + required_providers { + juju = { + source = "juju/juju" + version = ">= 0.14.0" + } + } +} \ No newline at end of file diff --git a/terraform/modules/loki/README.md b/terraform/modules/loki/README.md new file mode 100644 index 00000000..d13e1b0b --- /dev/null +++ b/terraform/modules/loki/README.md @@ -0,0 +1,59 @@ +# Terraform module for Loki solution + +This is a Terraform module facilitating the deployment of Loki solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). + +The solution consists of the following Terraform modules: +- [loki-coordinator-k8s](https://github.com/canonical/loki-coordinator-k8s-operator): ingress, cluster coordination, single integration facade. +- [loki-worker-k8s](https://github.com/canonical/loki-worker-k8s-operator): run one or more Loki application components. +- [s3-integrator](https://github.com/canonical/s3-integrator): facade for S3 storage configurations. +- [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator): certificates operator to secure traffic with TLS. + +This Terraform module deploys Loki in its [microservices mode](https://grafana.com/docs/enterprise-logs/latest/get-started/deployment-modes/#microservices-mode), which runs each one of the required roles in distinct processes. + + +> [!NOTE] +> `s3-integrator` itself doesn't act as an S3 object storage system. For the HA solution to be functional, `s3-integrator` needs to point to an S3-like storage. See [this guide](https://discourse.charmhub.io/t/cos-lite-docs-set-up-minio/15211) to learn how to connect to an S3-like storage for traces. + +## Requirements +This module requires a `juju` model to be available. Refer to the [usage section](#usage) below for more details. + +## API + +### Inputs +The module offers the following configurable inputs: + +| Name | Type | Description | Default | +| - | - | - | - | +| `backend_units`| number | Number of Loki worker units with the backend role | 1 | +| `channel`| string | Channel that the charms are deployed from | | +| `model`| string | Name of the model that the charm is deployed on | | +| `read_units`| number | Number of Loki worker units with the read role | 1 | +| `write_units`| number | Number of Loki worker units with the write role | 1 | +| `coordinator_units`| number | Number of Loki coordinator units | 1 | +| `s3_integrator_name` | string | Name of the s3-integrator app | 1 | +| `s3_bucket` | string | Name of the bucke in which Loki stores logs | 1 | +| `s3_access_key` | string | Access key credential to connect to the S3 provider | 1 | +| `s3_secret_key` | string | Secret key credential to connect to the S3 provider | 1 | +| `s3_endpoint` | string | Endpoint of the S3 provider | 1 | + +### Outputs +Upon application, the module exports the following outputs: + +| Name | Type | Description | +| - | - | - | +| `app_names`| map(string) | Names of the deployed applications | +| `endpoints`| map(string) | Map of all `provides` and `requires` endpoints | + +## Usage + + +### Basic usage + +Users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. + +To deploy this module with its needed dependency, you can run `terraform apply -var="model_name=" -auto-approve`. This would deploy all Loki HA solution modules in the same model. + +### Microservice deployment + +By default, this Terraform module will deploy each Loki worker with `1` unit. To configure the module to run `x` units of any worker role, you can run `terraform apply -var="model_name=" -var="_units=" -auto-approve`. +See [Loki worker roles](https://discourse.charmhub.io/t/loki-worker-roles/15484) for the recommended scale for each role. diff --git a/terraform/modules/loki/main.tf b/terraform/modules/loki/main.tf new file mode 100644 index 00000000..9977eafe --- /dev/null +++ b/terraform/modules/loki/main.tf @@ -0,0 +1,151 @@ +resource "juju_secret" "loki_s3_credentials_secret" { + model = var.model + name = "loki_s3_credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "Credentials for the S3 endpoint" +} + +resource "juju_access_secret" "loki_s3_secret_access" { + model = var.model + applications = [ + juju_application.s3_integrator.name + ] + secret_id = juju_secret.loki_s3_credentials_secret.secret_id +} + +# TODO: Replace s3_integrator resource to use its remote terraform module once available +resource "juju_application" "s3_integrator" { + name = var.s3_integrator_name + model = var.model + trust = true + + charm { + name = "s3-integrator" + channel = var.s3_integrator_channel + revision = var.s3_integrator_revision + } + config = { + endpoint = var.s3_endpoint + bucket = var.s3_bucket + credentials = "secret:${juju_secret.loki_s3_credentials_secret.secret_id}" + } + units = 1 +} + +module "loki_coordinator" { + source = "git::https://github.com/canonical/loki-coordinator-k8s-operator//terraform" + app_name = "loki" + model = var.model + channel = var.channel + revision = var.coordinator_revision + units = var.coordinator_units + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=loki,anti-pod.topology-key=kubernetes.io/hostname" : null +} + +module "loki_backend" { + source = "git::https://github.com/canonical/loki-worker-k8s-operator//terraform" + app_name = var.backend_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.backend_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-backend = true + } + revision = var.worker_revision + units = var.backend_units + depends_on = [ + module.loki_coordinator + ] +} + +module "loki_read" { + source = "git::https://github.com/canonical/loki-worker-k8s-operator//terraform" + app_name = var.read_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.read_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-read = true + } + revision = var.worker_revision + units = var.read_units + depends_on = [ + module.loki_coordinator + ] +} + +module "loki_write" { + source = "git::https://github.com/canonical/loki-worker-k8s-operator//terraform" + app_name = var.write_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.write_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-write = true + } + revision = var.worker_revision + units = var.write_units + depends_on = [ + module.loki_coordinator + ] +} + +# -------------- # Integrations -------------- + +resource "juju_integration" "coordinator_to_s3_integrator" { + model = var.model + application { + name = juju_application.s3_integrator.name + endpoint = "s3-credentials" + } + + application { + name = module.loki_coordinator.app_name + endpoint = "s3" + } +} + +resource "juju_integration" "coordinator_to_backend" { + model = var.model + + application { + name = module.loki_coordinator.app_name + endpoint = "loki-cluster" + } + + application { + name = module.loki_backend.app_name + endpoint = "loki-cluster" + } +} + +resource "juju_integration" "coordinator_to_read" { + model = var.model + + application { + name = module.loki_coordinator.app_name + endpoint = "loki-cluster" + } + + application { + name = module.loki_read.app_name + endpoint = "loki-cluster" + } +} + +resource "juju_integration" "coordinator_to_write" { + model = var.model + + application { + name = module.loki_coordinator.app_name + endpoint = "loki-cluster" + } + + application { + name = module.loki_write.app_name + endpoint = "loki-cluster" + } +} diff --git a/terraform/modules/loki/outputs.tf b/terraform/modules/loki/outputs.tf new file mode 100644 index 00000000..27921bed --- /dev/null +++ b/terraform/modules/loki/outputs.tf @@ -0,0 +1,30 @@ +output "app_names" { + value = merge( + { + loki_s3_integrator = juju_application.s3_integrator.name, + loki_coordinator = module.loki_coordinator.app_name, + loki_backend = module.loki_backend.app_name, + loki_read = module.loki_read.app_name, + loki_write = module.loki_write.app_name, + } + ) +} + +output "endpoints" { + value = { + # Requires + alertmanager = "alertmanager", + certificates = "certificates", + ingress = "ingress", + logging_consumer = "logging-consumer", + s3 = "s3", + charm_tracing = "charm-tracing", + # Provides + grafana_dashboards_provider = "grafana-dashboards-provider", + grafana_source = "grafana-source", + logging = "logging", + loki_cluster = "loki-cluster", + receive_remote_write = "receive-remote-write", + self_metrics_endpoint = "self-metrics-endpoint", + } +} diff --git a/terraform/modules/loki/variables.tf b/terraform/modules/loki/variables.tf new file mode 100644 index 00000000..5a55e11f --- /dev/null +++ b/terraform/modules/loki/variables.tf @@ -0,0 +1,128 @@ +variable "model" { + description = "Reference to an existing model resource or data source for the model to deploy to" + type = string +} + +variable "channel" { + description = "Channel that the charms are deployed from" + type = string +} + +variable "s3_integrator_channel" { + description = "Channel that the s3-integrator charm is deployed from" + type = string + default = "2/edge" +} + +variable "coordinator_revision" { + description = "Revision number of the coordinator charm" + type = number + default = null +} + +variable "worker_revision" { + description = "Revision number of the worker charm" + type = number + default = null +} + +variable "s3_integrator_revision" { + description = "Revision number of the s3-integrator charm" + type = number + default = 157 # FIXME: https://github.com/canonical/observability/issues/342 +} + +variable "s3_bucket" { + description = "Bucket name" + type = string + default = "loki" +} + +variable "s3_access_key" { + description = "S3 access-key credential" + type = string +} + +variable "s3_secret_key" { + description = "S3 secret-key credential" + type = string + sensitive = true +} + +variable "s3_endpoint" { + description = "S3 endpoint" + type = string +} + +variable "anti_affinity" { + description = "Enable anti-affinity constraints." + type = bool + default = true +} + +# -------------- # App Names -------------- + +variable "backend_name" { + description = "Name of the Loki app with the backend role" + type = string + default = "loki-backend" +} + +variable "read_name" { + description = "Name of the Loki app with the read role" + type = string + default = "loki-read" +} + +variable "write_name" { + description = "Name of the Loki app with the write role" + type = string + default = "loki-write" +} + +variable "s3_integrator_name" { + description = "Name of the s3-integrator app" + type = string + default = "loki-s3-integrator" +} +# -------------- # Units Per App -------------- + +variable "backend_units" { + description = "Number of Loki worker units with the backend role" + type = number + default = 1 + validation { + condition = var.backend_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "read_units" { + description = "Number of Loki worker units with the read role" + type = number + default = 1 + validation { + condition = var.read_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "write_units" { + description = "Number of Loki worker units with the write role" + type = number + default = 1 + validation { + condition = var.write_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "coordinator_units" { + description = "Number of Loki coordinator units" + type = number + default = 1 + validation { + condition = var.coordinator_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} diff --git a/terraform/modules/loki/versions.tf b/terraform/modules/loki/versions.tf new file mode 100644 index 00000000..cde98c1a --- /dev/null +++ b/terraform/modules/loki/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5" + required_providers { + juju = { + source = "juju/juju" + version = ">= 0.14.0" + } + } +} \ No newline at end of file diff --git a/terraform/modules/mimir/README.md b/terraform/modules/mimir/README.md new file mode 100644 index 00000000..99390bcf --- /dev/null +++ b/terraform/modules/mimir/README.md @@ -0,0 +1,60 @@ +# Terraform module for Mimir solution + +This is a Terraform module facilitating the deployment of Mimir solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). + +The solution consists of the following Terraform modules: +- [mimir-coordinator-k8s](https://github.com/canonical/mimir-coordinator-k8s-operator): ingress, cluster coordination, single integration facade. +- [mimir-worker-k8s](https://github.com/canonical/mimir-worker-k8s-operator): run one or more mimir application components. +- [s3-integrator](https://github.com/canonical/s3-integrator): facade for S3 storage configurations. +- [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator): certificates operator to secure traffic with TLS. + +This Terraform module deploys Mimir in its [microservices mode](https://grafana.com/docs/mimir/latest/references/architecture/deployment-modes/#microservices-mode), which runs each one of the required roles in distinct processes. + + +> [!NOTE] +> `s3-integrator` itself doesn't act as an S3 object storage system. For the HA solution to be functional, `s3-integrator` needs to point to an S3-like storage. See [this guide](https://discourse.charmhub.io/t/cos-lite-docs-set-up-minio/15211) to learn how to connect to an S3-like storage for traces. + +## Requirements +This module requires a `juju` model to be available. Refer to the [usage section](#usage) below for more details. + +## API + +### Inputs +The module offers the following configurable inputs: + +| Name | Type | Description | Default | +| - | - | - | - | +| `channel`| string | Channel that the charms are deployed from | | +| `model`| string | Name of the model that the charm is deployed on | | +| `coordinator_units`| number | Number of Mimir coordinator units | 1 | +| `read_units`| number | Number of Mimir worker units with the read role | 1 | +| `write_units`| number | Number of Mimir worker units with the write role | 1 | +| `backend_units`| number | Number of Mimir worker units with the backend role | 1 | +| `s3_integrator_name` | string | Name of the s3-integrator app | 1 | +| `s3_bucket` | string | Name of the bucke in which Mimir stores metrics | 1 | +| `s3_access_key` | string | Access key credential to connect to the S3 provider | 1 | +| `s3_secret_key` | string | Secret key credential to connect to the S3 provider | 1 | +| `s3_endpoint` | string | Endpoint of the S3 provider | 1 | + + +### Outputs +Upon application, the module exports the following outputs: + +| Name | Type | Description | +| - | - | - | +| `app_names`| map(string) | Names of the deployed applications | +| `endpoints`| map(string) | Map of all `provides` and `requires` endpoints | + +## Usage + + +### Basic usage + +Users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. + +To deploy this module with its needed dependency, you can run `terraform apply -var="model_name=" -auto-approve`. This would deploy all Mimir HA solution modules in the same model. + +### Microservice deployment + +By default, this Terraform module will deploy each Mimir worker with `1` unit. To configure the module to run `x` units of any worker role, you can run `terraform apply -var="model_name=" -var="_units=" -auto-approve`. +See [Mimir worker roles](https://discourse.charmhub.io/t/mimir-worker-roles/15484) for the recommended scale for each role. diff --git a/terraform/modules/mimir/main.tf b/terraform/modules/mimir/main.tf new file mode 100644 index 00000000..f1284846 --- /dev/null +++ b/terraform/modules/mimir/main.tf @@ -0,0 +1,152 @@ +resource "juju_secret" "mimir_s3_credentials_secret" { + model = var.model + name = "mimir_s3_credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "Credentials for the S3 endpoint" +} + +resource "juju_access_secret" "mimir_s3_secret_access" { + model = var.model + applications = [ + juju_application.s3_integrator.name + ] + secret_id = juju_secret.mimir_s3_credentials_secret.secret_id +} + +# TODO: Replace s3_integrator resource to use its remote terraform module once available +resource "juju_application" "s3_integrator" { + name = var.s3_integrator_name + model = var.model + trust = true + + charm { + name = "s3-integrator" + channel = var.s3_integrator_channel + revision = var.s3_integrator_revision + } + config = { + endpoint = var.s3_endpoint + bucket = var.s3_bucket + credentials = "secret:${juju_secret.mimir_s3_credentials_secret.secret_id}" + } + units = 1 + +} + +module "mimir_coordinator" { + source = "git::https://github.com/canonical/mimir-coordinator-k8s-operator//terraform" + app_name = "mimir" + model = var.model + channel = var.channel + revision = var.coordinator_revision + units = var.coordinator_units + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=mimir,anti-pod.topology-key=kubernetes.io/hostname" : null +} + +module "mimir_read" { + source = "git::https://github.com/canonical/mimir-worker-k8s-operator//terraform" + app_name = var.read_name + model = var.model + channel = var.channel + revision = var.worker_revision + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.read_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-read = true + } + units = var.read_units + depends_on = [ + module.mimir_coordinator + ] +} + +module "mimir_write" { + source = "git::https://github.com/canonical/mimir-worker-k8s-operator//terraform" + app_name = var.write_name + model = var.model + channel = var.channel + revision = var.worker_revision + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.write_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-write = true + } + units = var.write_units + depends_on = [ + module.mimir_coordinator + ] +} + +module "mimir_backend" { + source = "git::https://github.com/canonical/mimir-worker-k8s-operator//terraform" + app_name = var.backend_name + model = var.model + channel = var.channel + revision = var.worker_revision + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.backend_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-backend = true + } + units = var.backend_units + depends_on = [ + module.mimir_coordinator + ] +} + +# -------------- # Integrations -------------- + +resource "juju_integration" "coordinator_to_s3_integrator" { + model = var.model + application { + name = juju_application.s3_integrator.name + endpoint = "s3-credentials" + } + + application { + name = module.mimir_coordinator.app_name + endpoint = "s3" + } +} + +resource "juju_integration" "coordinator_to_read" { + model = var.model + + application { + name = module.mimir_coordinator.app_name + endpoint = "mimir-cluster" + } + + application { + name = module.mimir_read.app_name + endpoint = "mimir-cluster" + } +} + +resource "juju_integration" "coordinator_to_write" { + model = var.model + + application { + name = module.mimir_coordinator.app_name + endpoint = "mimir-cluster" + } + + application { + name = module.mimir_write.app_name + endpoint = "mimir-cluster" + } +} + +resource "juju_integration" "coordinator_to_backend" { + model = var.model + + application { + name = module.mimir_coordinator.app_name + endpoint = "mimir-cluster" + } + + application { + name = module.mimir_backend.app_name + endpoint = "mimir-cluster" + } +} diff --git a/terraform/modules/mimir/outputs.tf b/terraform/modules/mimir/outputs.tf new file mode 100644 index 00000000..d3603364 --- /dev/null +++ b/terraform/modules/mimir/outputs.tf @@ -0,0 +1,31 @@ +output "app_names" { + value = merge( + { + mimir_s3_integrator = juju_application.s3_integrator.name, + mimir_coordinator = module.mimir_coordinator.app_name, + mimir_read = module.mimir_read.app_name, + mimir_write = module.mimir_write.app_name, + mimir_backend = module.mimir_backend.app_name, + } + ) +} + +output "endpoints" { + value = { + # Requires + alertmanager = "alertmanager", + certificates = "certificates", + ingress = "ingress", + logging_consumer = "logging-consumer", + s3 = "s3", + charm_tracing = "charm-tracing", + catalogue = "catalogue", + + # Provides + grafana_dashboards_provider = "grafana-dashboards-provider", + grafana_source = "grafana-source", + mimir_cluster = "mimir-cluster", + receive_remote_write = "receive-remote-write", + self_metrics_endpoint = "self-metrics-endpoint", + } +} diff --git a/terraform/modules/mimir/variables.tf b/terraform/modules/mimir/variables.tf new file mode 100644 index 00000000..c6d35842 --- /dev/null +++ b/terraform/modules/mimir/variables.tf @@ -0,0 +1,129 @@ +variable "model" { + description = "Reference to an existing model resource or data source for the model to deploy to" + type = string +} + +variable "channel" { + description = "Channel that the charms are deployed from" + type = string +} + +variable "s3_integrator_channel" { + description = "Channel that the s3-integrator charm is deployed from" + type = string + default = "2/edge" +} + +variable "coordinator_revision" { + description = "Revision number of the coordinator charm" + type = number + default = null +} + +variable "worker_revision" { + description = "Revision number of the worker charm" + type = number + default = null +} + +variable "s3_integrator_revision" { + description = "Revision number of the s3-integrator charm" + type = number + default = 157 # FIXME: https://github.com/canonical/observability/issues/342 +} + +variable "s3_bucket" { + description = "Bucket name" + type = string + default = "mimir" +} + +variable "s3_access_key" { + description = "S3 access-key credential" + type = string +} + +variable "s3_secret_key" { + description = "S3 secret-key credential" + type = string + sensitive = true +} + +variable "s3_endpoint" { + description = "S3 endpoint" + type = string +} + +variable "anti_affinity" { + description = "Enable anti-affinity constraints." + type = bool + default = true +} + +# -------------- # App Names -------------- + +variable "read_name" { + description = "Name of the Mimir read (meta role) app" + type = string + default = "mimir-read" +} + +variable "write_name" { + description = "Name of the Mimir write (meta role) app" + type = string + default = "mimir-write" +} + +variable "backend_name" { + description = "Name of the Mimir backend (meta role) app" + type = string + default = "mimir-backend" +} + +variable "s3_integrator_name" { + description = "Name of the s3-integrator app" + type = string + default = "mimir-s3-integrator" +} + +# -------------- # Units Per App -------------- + +variable "read_units" { + description = "Number of Mimir worker units with the read meta role" + type = number + default = 1 + validation { + condition = var.read_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "write_units" { + description = "Number of Mimir worker units with the write meta role" + type = number + default = 1 + validation { + condition = var.write_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "backend_units" { + description = "Number of Mimir worker units with the backend meta role" + type = number + default = 1 + validation { + condition = var.backend_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "coordinator_units" { + description = "Number of Mimir coordinator units" + type = number + default = 1 + validation { + condition = var.coordinator_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} diff --git a/terraform/modules/mimir/versions.tf b/terraform/modules/mimir/versions.tf new file mode 100644 index 00000000..cde98c1a --- /dev/null +++ b/terraform/modules/mimir/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5" + required_providers { + juju = { + source = "juju/juju" + version = ">= 0.14.0" + } + } +} \ No newline at end of file diff --git a/terraform/modules/minio/main.tf b/terraform/modules/minio/main.tf new file mode 100644 index 00000000..f8edccb5 --- /dev/null +++ b/terraform/modules/minio/main.tf @@ -0,0 +1,43 @@ +resource "juju_application" "minio" { + # Coordinator requires s3 + name = var.minio_app + model = var.model_name + trust = true + + charm { + name = var.minio_app + channel = var.channel + } + units = 1 + + config = { + access-key = var.minio_user + secret-key = var.minio_password + } +} + +resource "null_resource" "s3management" { + triggers = { + # model_name = var.model_name + always_run = timestamp() + } + provisioner "local-exec" { + environment = { + MINIO_USER = var.minio_user + MINIO_PASSWORD = var.minio_password + } + command = <<-EOT + bash "${path.module}/scripts/s3management.sh" \ + --model-name ${var.model_name} \ + --minio-app ${var.minio_app} \ + --mc-binary-url ${var.mc_binary_url} \ + --minio-url "http://${var.minio_app}-0.${var.minio_app}-endpoints.${var.model_name}.svc.cluster.local:9000" \ + --loki-bucket ${var.loki.bucket_name} \ + --mimir-bucket ${var.mimir.bucket_name} \ + --tempo-bucket ${var.tempo.bucket_name} \ + --loki-integrator ${var.loki.s3_integrator_name} \ + --mimir-integrator ${var.mimir.s3_integrator_name} \ + --tempo-integrator ${var.tempo.s3_integrator_name} + EOT + } +} \ No newline at end of file diff --git a/terraform/modules/minio/outputs.tf b/terraform/modules/minio/outputs.tf new file mode 100644 index 00000000..7d42f35f --- /dev/null +++ b/terraform/modules/minio/outputs.tf @@ -0,0 +1,4 @@ +output "minio_name" { + value = var.minio_app + description = "The application name for Minio" +} diff --git a/terraform/modules/minio/scripts/s3management.sh b/terraform/modules/minio/scripts/s3management.sh new file mode 100755 index 00000000..ffc0a4d1 --- /dev/null +++ b/terraform/modules/minio/scripts/s3management.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +set -euo pipefail + +# Variables +MODEL_NAME="" +MINIO_APP="" +MC_BINARY_URL="" +MINIO_URL="" +MINIO_USER="${MINIO_USER:-}" +MINIO_PASSWORD="${MINIO_PASSWORD:-}" +LOKI_BUCKET="" +MIMIR_BUCKET="" +TEMPO_BUCKET="" +LOKI_INTEGRATOR="" +MIMIR_INTEGRATOR="" +TEMPO_INTEGRATOR="" + +MC_BINARY="/root/minio/mc" + +# Parse arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --model-name) MODEL_NAME="$2"; shift ;; + --minio-app) MINIO_APP="$2"; shift ;; + --mc-binary-url) MC_BINARY_URL="$2"; shift ;; + --minio-url) MINIO_URL="$2"; shift ;; + --minio-user) MINIO_USER="$2"; shift ;; + --minio-password) MINIO_PASSWORD="$2"; shift ;; + --loki-bucket) LOKI_BUCKET="$2"; shift ;; + --mimir-bucket) MIMIR_BUCKET="$2"; shift ;; + --tempo-bucket) TEMPO_BUCKET="$2"; shift ;; + --loki-integrator) LOKI_INTEGRATOR="$2"; shift ;; + --mimir-integrator) MIMIR_INTEGRATOR="$2"; shift ;; + --tempo-integrator) TEMPO_INTEGRATOR="$2"; shift ;; + *) echo "Unknown parameter passed: $1"; exit 1 ;; + esac + shift +done + +# Credentials validation +if [[ -z "$MINIO_USER" || -z "$MINIO_PASSWORD" ]]; then + echo "Error: MINIO_USER and MINIO_PASSWORD must be set either as arguments or environment variables." + exit 1 +fi + + +# Functions +wait_for_app() { + local app="$1" + local status="${2:-active}" + + echo "Waiting for application $app in model $MODEL_NAME..." + juju wait-for application "$app" -m "$MODEL_NAME" --query="status=='$status'" --timeout 20m +} + +bucket_exists() { + local bucket_name="$1" + juju ssh -m "$MODEL_NAME" "$MINIO_APP/leader" "$MC_BINARY" ls local/ | grep -q "$bucket_name" +} + +mc_exists() { + echo "Checking if mc is already downlaoded..." + juju ssh -m "$MODEL_NAME" "$MINIO_APP/leader" ls "$MC_BINARY" +} + +configure_s3() { + local bucket_name="$1" + local integrator="$2" + + + echo "Checking if bucket $bucket_name exists..." + if bucket_exists "$bucket_name"; then + echo "Bucket $bucket_name already exists. Skipping creation." + else + echo "Creating bucket $bucket_name..." + juju ssh -m "$MODEL_NAME" "$MINIO_APP/leader" "$MC_BINARY" mb local/"$bucket_name" + fi + + wait_for_app "$integrator" "blocked" + + echo "Configuring $integrator..." + juju config "$integrator" endpoint="$MINIO_URL" bucket="$bucket_name" + + echo "Syncing S3 credentials for $integrator..." + juju run -m "$MODEL_NAME" "$integrator/leader" sync-s3-credentials access-key="$MINIO_USER" secret-key="$MINIO_PASSWORD" +} + +configure_minio() { + # Wait for MinIO app + wait_for_app "$MINIO_APP" + + if mc_exists; then + echo "MinIO client is already downloaded. Skipping download." + else + echo "Downloading MinIO client..." + juju ssh -m "$MODEL_NAME" "$MINIO_APP/leader" curl "$MC_BINARY_URL" --create-dirs -o "$MC_BINARY" + fi + + juju ssh -m "$MODEL_NAME" "$MINIO_APP/leader" chmod +x "$MC_BINARY" + juju ssh -m "$MODEL_NAME" "$MINIO_APP/leader" "$MC_BINARY" alias set local "$MINIO_URL" "$MINIO_USER" "$MINIO_PASSWORD" +} + +# Configure MinIO +configure_minio + +# Configure buckets and sync credentials +configure_s3 "$LOKI_BUCKET" "$LOKI_INTEGRATOR" +configure_s3 "$MIMIR_BUCKET" "$MIMIR_INTEGRATOR" +configure_s3 "$TEMPO_BUCKET" "$TEMPO_INTEGRATOR" + +echo "S3 configuration complete!" diff --git a/terraform/modules/minio/variables.tf b/terraform/modules/minio/variables.tf new file mode 100644 index 00000000..8649e8d2 --- /dev/null +++ b/terraform/modules/minio/variables.tf @@ -0,0 +1,47 @@ +variable "channel" { + description = "Charms channel" + type = string + default = "latest/edge" +} + +variable "model_name" { + description = "Model name" + type = string +} + +variable "minio_app" { + description = "Minio user" + type = string + default = "minio" +} + +variable "minio_user" { + description = "Minio user" + type = string +} + +variable "minio_password" { + description = "Minio Password" + type = string +} + +variable "mc_binary_url" { + description = "mc binary URL" + type = string + default = "https://dl.min.io/client/mc/release/linux-amd64/mc" +} + +variable "loki" { + description = "Configuration outputs from the Loki module, including bucket and integrator details." + type = any +} + +variable "mimir" { + description = "Configuration outputs from the Mimir module, including bucket and integrator details." + type = any +} + +variable "tempo" { + description = "Configuration outputs from the Tempo module, including bucket and integrator details." + type = any +} diff --git a/terraform/modules/minio/version.tf b/terraform/modules/minio/version.tf new file mode 100644 index 00000000..77b64403 --- /dev/null +++ b/terraform/modules/minio/version.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5" + required_providers { + juju = { + source = "juju/juju" + version = "~> 0.14" + } + } +} \ No newline at end of file diff --git a/terraform/modules/tempo/README.md b/terraform/modules/tempo/README.md new file mode 100644 index 00000000..47ad1aee --- /dev/null +++ b/terraform/modules/tempo/README.md @@ -0,0 +1,63 @@ +# Terraform module for Tempo solution + +This is a Terraform module facilitating the deployment of Tempo solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). + +The solution consists of the following Terraform modules: +- [tempo-coordinator-k8s](https://github.com/canonical/tempo-coordinator-k8s-operator): ingress, cluster coordination, single integration facade. +- [tempo-worker-k8s](https://github.com/canonical/tempo-worker-k8s-operator): run one or more tempo application components. +- [s3-integrator](https://github.com/canonical/s3-integrator): facade for S3 storage configurations. +- [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator): certificates operator to secure traffic with TLS. + +This Terraform module deploys Tempo in its [microservices mode](https://grafana.com/docs/tempo/latest/setup/deployment/#microservices-mode), which runs each one of the required roles in distinct processes. [See](https://discourse.charmhub.io/t/topic/15484) to understand more about Tempo roles. + + +> [!NOTE] +> `s3-integrator` itself doesn't act as an S3 object storage system. For the solution to be functional, `s3-integrator` needs to point to an S3-like storage. See [this guide](https://discourse.charmhub.io/t/cos-lite-docs-set-up-minio/15211) to learn how to connect to an S3-like storage for traces. + +## Requirements +This module requires a `juju` model to be available. Refer to the [usage section](#usage) below for more details. + +## API + +### Inputs +The module offers the following configurable inputs: + +| Name | Type | Description | Default | +| - | - | - | - | +| `channel`| string | Channel that the charms are deployed from | | +| `compactor_units`| number | Number of Tempo worker units with compactor role | 1 | +| `distributor_units`| number | Number of Tempo worker units with distributor role | 1 | +| `ingester_units`| number | Number of Tempo worker units with ingester role | 1 | +| `metrics_generator_units`| number | Number of Tempo worker units with metrics-generator role | 1 | +| `model`| string | Name of the model that the charm is deployed on | | +| `querier_units`| number | Number of Tempo worker units with querier role | 1 | +| `query_frontend_units`| number | Number of Tempo worker units with query-frontend role | 1 | +| `coordinator_units`| number | Number of Tempo coordinator units | 1 | +| `s3_integrator_name` | string | Name of the s3-integrator app | 1 | +| `s3_bucket` | string | Name of the bucke in which Tempo stores traces | 1 | +| `s3_access_key` | string | Access key credential to connect to the S3 provider | 1 | +| `s3_secret_key` | string | Secret key credential to connect to the S3 provider | 1 | +| `s3_endpoint` | string | Endpoint of the S3 provider | 1 | + + +### Outputs +Upon application, the module exports the following outputs: + +| Name | Type | Description | +| - | - | - | +| `app_names`| map(string) | Names of the deployed applications | +| `endpoints`| map(string) | Map of all `provides` and `requires` endpoints | + +## Usage + + +### Basic usage + +Users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. + +To deploy this module with its needed dependency, you can run `terraform apply -var="model_name=" -auto-approve`. This would deploy all Tempo components in the same model. + +### Microservice deployment + +By default, this Terraform module will deploy each Tempo worker with `1` unit. To configure the module to run `x` units of any worker role, you can run `terraform apply -var="model_name=" -var="_units=" -auto-approve`. +See [Tempo worker roles](https://discourse.charmhub.io/t/tempo-worker-roles/15484) for the recommended scale for each role. diff --git a/terraform/modules/tempo/main.tf b/terraform/modules/tempo/main.tf new file mode 100644 index 00000000..db82bac9 --- /dev/null +++ b/terraform/modules/tempo/main.tf @@ -0,0 +1,247 @@ +resource "juju_secret" "tempo_s3_credentials_secret" { + model = var.model + name = "tempo_s3_credentials" + value = { + access-key = var.s3_access_key + secret-key = var.s3_secret_key + } + info = "Credentials for the S3 endpoint" +} + +resource "juju_access_secret" "tempo_s3_secret_access" { + model = var.model + applications = [ + juju_application.s3_integrator.name + ] + secret_id = juju_secret.tempo_s3_credentials_secret.secret_id +} + +# TODO: Replace s3_integrator resource to use its remote terraform module once available +resource "juju_application" "s3_integrator" { + name = var.s3_integrator_name + model = var.model + trust = true + + charm { + name = "s3-integrator" + channel = var.s3_integrator_channel + revision = var.s3_integrator_revision + } + config = { + endpoint = var.s3_endpoint + bucket = var.s3_bucket + credentials = "secret:${juju_secret.tempo_s3_credentials_secret.secret_id}" + } + units = 1 +} + +module "tempo_coordinator" { + source = "git::https://github.com/canonical/tempo-coordinator-k8s-operator//terraform" + model = var.model + channel = var.channel + revision = var.coordinator_revision + units = var.coordinator_units + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=tempo,anti-pod.topology-key=kubernetes.io/hostname" : null +} + +module "tempo_querier" { + source = "git::https://github.com/canonical/tempo-worker-k8s-operator//terraform" + app_name = var.querier_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.querier_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-all = false + role-querier = true + } + revision = var.worker_revision + units = var.querier_units + depends_on = [ + module.tempo_coordinator + ] +} + +module "tempo_query_frontend" { + source = "git::https://github.com/canonical/tempo-worker-k8s-operator//terraform" + app_name = var.query_frontend_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.query_frontend_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-all = false + role-query-frontend = true + } + revision = var.worker_revision + units = var.query_frontend_units + depends_on = [ + module.tempo_coordinator + ] +} + +module "tempo_ingester" { + source = "git::https://github.com/canonical/tempo-worker-k8s-operator//terraform" + app_name = var.ingester_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.ingester_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-all = false + role-ingester = true + } + revision = var.worker_revision + units = var.ingester_units + depends_on = [ + module.tempo_coordinator + ] +} + +module "tempo_distributor" { + source = "git::https://github.com/canonical/tempo-worker-k8s-operator//terraform" + app_name = var.distributor_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.distributor_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-all = false + role-distributor = true + } + revision = var.worker_revision + units = var.distributor_units + depends_on = [ + module.tempo_coordinator + ] +} + +module "tempo_compactor" { + source = "git::https://github.com/canonical/tempo-worker-k8s-operator//terraform" + app_name = var.compactor_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.compactor_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-all = false + role-compactor = true + } + revision = var.worker_revision + units = var.compactor_units + depends_on = [ + module.tempo_coordinator + ] +} + +module "tempo_metrics_generator" { + source = "git::https://github.com/canonical/tempo-worker-k8s-operator//terraform" + app_name = var.metrics_generator_name + model = var.model + channel = var.channel + constraints = var.anti_affinity ? "arch=amd64 tags=anti-pod.app.kubernetes.io/name=${var.metrics_generator_name},anti-pod.topology-key=kubernetes.io/hostname" : null + config = { + role-all = false + role-metrics-generator = true + } + revision = var.worker_revision + units = var.metrics_generator_units + depends_on = [ + module.tempo_coordinator + ] +} + +#Integrations + +resource "juju_integration" "coordinator_to_s3_integrator" { + model = var.model + + application { + name = juju_application.s3_integrator.name + endpoint = "s3-credentials" + } + + application { + name = module.tempo_coordinator.app_name + endpoint = "s3" + } +} + +resource "juju_integration" "coordinator_to_querier" { + model = var.model + + application { + name = module.tempo_coordinator.app_name + endpoint = "tempo-cluster" + } + + application { + name = module.tempo_querier.app_name + endpoint = "tempo-cluster" + } +} + +resource "juju_integration" "coordinator_to_query_frontend" { + model = var.model + + application { + name = module.tempo_coordinator.app_name + endpoint = "tempo-cluster" + } + + application { + name = module.tempo_query_frontend.app_name + endpoint = "tempo-cluster" + } +} + +resource "juju_integration" "coordinator_to_ingester" { + model = var.model + + application { + name = module.tempo_coordinator.app_name + endpoint = "tempo-cluster" + } + + application { + name = module.tempo_ingester.app_name + endpoint = "tempo-cluster" + } +} + +resource "juju_integration" "coordinator_to_distributor" { + model = var.model + + application { + name = module.tempo_coordinator.app_name + endpoint = "tempo-cluster" + } + + application { + name = module.tempo_distributor.app_name + endpoint = "tempo-cluster" + } +} + +resource "juju_integration" "coordinator_to_compactor" { + model = var.model + + application { + name = module.tempo_coordinator.app_name + endpoint = "tempo-cluster" + } + + application { + name = module.tempo_compactor.app_name + endpoint = "tempo-cluster" + } +} + +resource "juju_integration" "coordinator_to_metrics_generator" { + model = var.model + + application { + name = module.tempo_coordinator.app_name + endpoint = "tempo-cluster" + } + + application { + name = module.tempo_metrics_generator.app_name + endpoint = "tempo-cluster" + } +} diff --git a/terraform/modules/tempo/outputs.tf b/terraform/modules/tempo/outputs.tf new file mode 100644 index 00000000..a4579bec --- /dev/null +++ b/terraform/modules/tempo/outputs.tf @@ -0,0 +1,33 @@ +output "app_names" { + value = merge( + { + tempo_s3_integrator = juju_application.s3_integrator.name, + tempo_coordinator = module.tempo_coordinator.app_name, + tempo_querier = module.tempo_querier.app_name, + tempo_query_frontend = module.tempo_query_frontend.app_name, + tempo_ingester = module.tempo_ingester.app_name, + tempo_distributor = module.tempo_distributor.app_name, + tempo_compactor = module.tempo_compactor.app_name, + tempo_metrics_generator = module.tempo_metrics_generator.app_name, + } + ) +} + +output "endpoints" { + value = { + # Requires + logging = "logging", + ingress = "ingress", + certificates = "certificates", + send-remote-write = "send-remote-write", + receive_datasource = "receive-datasource" + catalogue = "catalogue", + + # Provides + tempo_cluster = "tempo-cluster" + grafana_dashboard = "grafana-dashboard", + grafana_source = "grafana-source", + metrics_endpoint = "metrics-endpoint", + tracing = "tracing", + } +} diff --git a/terraform/modules/tempo/variables.tf b/terraform/modules/tempo/variables.tf new file mode 100644 index 00000000..42db6db6 --- /dev/null +++ b/terraform/modules/tempo/variables.tf @@ -0,0 +1,176 @@ +variable "model" { + description = "Reference to an existing model resource or data source for the model to deploy to" + type = string +} + +variable "channel" { + description = "Channel that the charms are deployed from" + type = string +} + +variable "s3_integrator_channel" { + description = "Channel that the s3-integrator charm is deployed from" + type = string + default = "2/edge" +} + +variable "coordinator_revision" { + description = "Revision number of the coordinator charm" + type = number + default = null +} + +variable "worker_revision" { + description = "Revision number of the worker charm" + type = number + default = null +} + +variable "s3_integrator_revision" { + description = "Revision number of the s3-integrator charm" + type = number + default = 157 # FIXME: https://github.com/canonical/observability/issues/342 +} + +variable "s3_bucket" { + description = "Bucket name" + type = string + default = "tempo" +} + +variable "s3_access_key" { + description = "S3 access-key credential" + type = string +} + +variable "s3_secret_key" { + description = "S3 secret-key credential" + type = string + sensitive = true +} + +variable "s3_endpoint" { + description = "S3 endpoint" + type = string +} + +variable "anti_affinity" { + description = "Enable anti-affinity constraints" + type = bool + default = true +} + +# -------------- # App Names -------------- + +variable "querier_name" { + description = "Name of the Tempo querier app" + type = string + default = "tempo-querier" +} + +variable "query_frontend_name" { + description = "Name of the Tempo query-frontend app" + type = string + default = "tempo-query-frontend" +} + +variable "ingester_name" { + description = "Name of the Tempo ingester app" + type = string + default = "tempo-ingester" +} + +variable "distributor_name" { + description = "Name of the Tempo distributor app" + type = string + default = "tempo-distributor" +} + +variable "compactor_name" { + description = "Name of the Tempo compactor app" + type = string + default = "tempo-compactor" +} + +variable "metrics_generator_name" { + description = "Name of the Tempo metrics-generator app" + type = string + default = "tempo-metrics-generator" +} + +variable "s3_integrator_name" { + description = "Name of the s3-integrator app" + type = string + default = "tempo-s3-integrator" +} + +# -------------- # Units Per App -------------- + +variable "compactor_units" { + description = "Number of Tempo worker units with compactor role" + type = number + default = 1 + validation { + condition = var.compactor_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "distributor_units" { + description = "Number of Tempo worker units with distributor role" + type = number + default = 1 + validation { + condition = var.distributor_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "ingester_units" { + description = "Number of Tempo worker units with ingester role" + type = number + default = 1 + validation { + condition = var.ingester_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "metrics_generator_units" { + description = "Number of Tempo worker units with metrics-generator role" + type = number + default = 1 + validation { + condition = var.metrics_generator_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "coordinator_units" { + description = "Number of Tempo coordinator units" + type = number + default = 1 + validation { + condition = var.coordinator_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} + +variable "querier_units" { + description = "Number of Tempo worker units with querier role" + type = number + default = 1 + validation { + condition = var.querier_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} +variable "query_frontend_units" { + description = "Number of Tempo worker units with query-frontend role" + type = number + default = 1 + validation { + condition = var.query_frontend_units >= 1 + error_message = "The number of units must be greater than or equal to 1." + } +} diff --git a/terraform/modules/tempo/versions.tf b/terraform/modules/tempo/versions.tf new file mode 100644 index 00000000..cde98c1a --- /dev/null +++ b/terraform/modules/tempo/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.5" + required_providers { + juju = { + source = "juju/juju" + version = ">= 0.14.0" + } + } +} \ No newline at end of file diff --git a/terraform/scripts/hcl-generator/generate_integrations.py b/terraform/scripts/hcl-generator/generate_integrations.py new file mode 100644 index 00000000..28d5dee1 --- /dev/null +++ b/terraform/scripts/hcl-generator/generate_integrations.py @@ -0,0 +1,116 @@ +import yaml +import sys +import hcl2 +import os +import argparse +from pathlib import Path + +OUTPUTS_TF_FILE = "outputs.tf" + + +def build_parser(): + """Create and configure the argument parser for the script.""" + + parser = argparse.ArgumentParser(description="Process YAML and generate Terraform output.") + + parser.add_argument( + '--input-yaml', + required=True, + help='Path to the input YAML file (charmcraft.yaml or metadata.yaml)' + ) + + parser.add_argument( + '--outputs-tf', + default=OUTPUTS_TF_FILE, + help=f'Path to the output Terraform file (default: {OUTPUTS_TF_FILE})' + ) + + return parser + + +class HCLGenerator(object): + def __init__(self, input_yaml: str, outputs_tf: str): + self.input_yaml = Path(input_yaml) + if outputs_tf == OUTPUTS_TF_FILE: + self.outputs_tf = Path(os.getcwd()) / outputs_tf + else: + self.outputs_tf = Path(outputs_tf) + + def parse_yaml(self): + """Parse the YAML file and return the data""" + try: + with open(self.input_yaml, "r") as file: + return yaml.safe_load(file) + except Exception as e: + print(f"Error reading YAML file: {e}") + sys.exit(1) + + @staticmethod + def format_keys(section): + """ + Format the keys by replacing hyphens with underscores + Although HCL allows dashes in keys, this does not allow for dot notation + """ + return {key.replace("-", "_"): key for key in section} + + def create_file_with_parents(self): + """Create the file and parent directories if they don't exist""" + self.outputs_tf.parent.mkdir(parents=True, exist_ok=True) + self.outputs_tf.touch(exist_ok=True) + + def generate_output(self, data): + """Generate the output string based on the parsed YAML data""" + requires = data.get("requires", {}) + provides = data.get("provides", {}) + + # Format the keys in the 'requires' and 'provides' sections + requires_formatted = self.format_keys(requires) + provides_formatted = self.format_keys(provides) + + # Prepare the output string + output = 'output "endpoints" {\n value = {\n' + + # Add requires + output += " # Requires\n" + for key, value in requires_formatted.items(): + output += f' {key:<20} = "{value}",\n' + + # Add provides + output += " # Provides\n" + for key, value in provides_formatted.items(): + output += f' {key:<20} = "{value}",\n' + + output += " }\n}" + # TODO: Create a function for `terraform format` of the output + return output + + def write_hcl_file(self, hcl_content): + """Parse the YAML file and return the data""" + + self.create_file_with_parents() + + with open(self.outputs_tf, "r") as file: + read_obj = hcl2.load(file) + + if read_obj: + print("WE HAD CONTENTS") + + with open(self.outputs_tf, "w") as file: + file.write(hcl_content) + + +def main(): + """Main function to handle script execution""" + + parser = build_parser() + args = parser.parse_args() + + gen = HCLGenerator(args.input_yaml, args.outputs_tf) + data = gen.parse_yaml() + output_raw = gen.generate_output(data) + print(output_raw) + gen.write_hcl_file(output_raw) + + +if __name__ == "__main__": + main() From 893c1c00e61fda43d81241fba51a6e0c88639872 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 13 Jun 2025 12:55:55 -0400 Subject: [PATCH 02/19] feat: tf CI --- .github/workflows/_local-pull-request.yaml | 32 +++++++++++++++++++++ justfile | 33 ++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 .github/workflows/_local-pull-request.yaml create mode 100644 justfile diff --git a/.github/workflows/_local-pull-request.yaml b/.github/workflows/_local-pull-request.yaml new file mode 100644 index 00000000..05be23e6 --- /dev/null +++ b/.github/workflows/_local-pull-request.yaml @@ -0,0 +1,32 @@ +name: Pull Requests + +on: + pull_request: + branches: + - main + +jobs: + lint-terraform: + name: Terraform lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo snap install terraform --classic + sudo snap install just --classic + - name: Lint the Terraform modules + run: just lint-terraform + lint-workflows: + name: Workflows Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo snap install astral-uv --classic + sudo snap install just --classic + - name: Lint the workflows + run: just lint-workflows diff --git a/justfile b/justfile new file mode 100644 index 00000000..716a3097 --- /dev/null +++ b/justfile @@ -0,0 +1,33 @@ +set quiet # Recipes are silent by default +set export # Just variables are exported to the environment + +terraform := `which terraform || which tofu || echo ""` # require 'terraform' or 'opentofu' + +[private] +default: + just --list + +# Lint everything +[group("Lint")] +lint: lint-terraform lint-workflows + +# Format everything +[group("Format")] +fmt: format-terraform + +# Lint the Terraform modules +[group("Lint")] +lint-terraform: + if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi + $terraform fmt -check -recursive -diff + +# Lint the Github workflows +[group("Lint")] +lint-workflows: + uvx --from=actionlint-py actionlint + +# Format the Terraform modules +[group("Format")] +format-terraform: + if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi + $terraform fmt -recursive -diff From 8dd132a8958f793a86b0c3ff91cccb1469836eb5 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Fri, 13 Jun 2025 12:57:47 -0400 Subject: [PATCH 03/19] chore: fmt TF file --- .../installation/cos-canonical-k8s-sandbox.tf | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf b/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf index 9afa4867..2847032c 100644 --- a/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf +++ b/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf @@ -1,13 +1,13 @@ module "cos" { - source = "git::https://github.com/canonical/observability//terraform/modules/cos" - model_name = "cos" - channel = "1/stable" - s3_endpoint = "http://{{IPADDR}}:8080" - s3_password = "secret-key" - s3_user = "access-key" - loki_bucket = "loki" - mimir_bucket = "mimir" - tempo_bucket = "tempo" - ssc_channel = "1/stable" - anti_affinity = true + source = "git::https://github.com/canonical/observability//terraform/modules/cos" + model_name = "cos" + channel = "1/stable" + s3_endpoint = "http://{{IPADDR}}:8080" + s3_password = "secret-key" + s3_user = "access-key" + loki_bucket = "loki" + mimir_bucket = "mimir" + tempo_bucket = "tempo" + ssc_channel = "1/stable" + anti_affinity = true } From c53ad2ae44f91232d3fb7c291b1369ff2f5c9684 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 10:20:22 -0400 Subject: [PATCH 04/19] feat: validate TF in CI --- .github/workflows/_local-pull-request.yaml | 22 ++++- justfile | 14 ++- terraform/modules/cos-lite/README.md | 18 ++-- terraform/modules/cos-lite/main.tf | 108 ++++++++++----------- terraform/modules/cos-lite/variables.tf | 2 +- terraform/modules/cos/README.md | 14 +-- terraform/modules/cos/outputs.tf | 2 +- 7 files changed, 96 insertions(+), 84 deletions(-) diff --git a/.github/workflows/_local-pull-request.yaml b/.github/workflows/_local-pull-request.yaml index 05be23e6..394aa344 100644 --- a/.github/workflows/_local-pull-request.yaml +++ b/.github/workflows/_local-pull-request.yaml @@ -6,6 +6,18 @@ on: - main jobs: + lint-workflows: + name: Workflows Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo snap install astral-uv --classic + sudo snap install just --classic + - name: Lint the workflows + run: just lint-workflows lint-terraform: name: Terraform lint runs-on: ubuntu-latest @@ -18,15 +30,15 @@ jobs: sudo snap install just --classic - name: Lint the Terraform modules run: just lint-terraform - lint-workflows: - name: Workflows Lint + validate-terraform: + name: Terraform validate runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Install dependencies run: | - sudo snap install astral-uv --classic + sudo snap install terraform --classic sudo snap install just --classic - - name: Lint the workflows - run: just lint-workflows + - name: Validate the Terraform modules + run: just validate-terraform diff --git a/justfile b/justfile index 716a3097..8736439c 100644 --- a/justfile +++ b/justfile @@ -15,16 +15,22 @@ lint: lint-terraform lint-workflows [group("Format")] fmt: format-terraform +# Lint the Github workflows +[group("Lint")] +lint-workflows: + uvx --from=actionlint-py actionlint + # Lint the Terraform modules [group("Lint")] lint-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi $terraform fmt -check -recursive -diff -# Lint the Github workflows -[group("Lint")] -lint-workflows: - uvx --from=actionlint-py actionlint +# Validate the Terraform modules +[group("Validate")] +validate-terraform: + if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi + $terraform validate # Format the Terraform modules [group("Format")] diff --git a/terraform/modules/cos-lite/README.md b/terraform/modules/cos-lite/README.md index c99645ae..9e95d9d7 100644 --- a/terraform/modules/cos-lite/README.md +++ b/terraform/modules/cos-lite/README.md @@ -3,11 +3,13 @@ Terraform module for COS solution This is a Terraform module facilitating the deployment of COS solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). The COS Lite solution consists of the following Terraform modules: -- [grafana-k8s](https://github.com/canonical/grafana-k8s-operator): Visualization, monitoring, and dashboards. - [alertmanager-k8s](https://github.com/canonical/alertmanager-k8s-operator): Handles alerts sent by clients applications. -- [prometheus-k8s](https://github.com/canonical/prometheus-k8s-operator/tree/main/terraform/): Backend for metrics +- [catalogue-k8s](https://github.com/canonical/catalogue-k8s-operator/tree/main/terraform): UI catalogue. +- [grafana-k8s](https://github.com/canonical/grafana-k8s-operator): Visualization, monitoring, and dashboards. - [loki-k8s](https://github.com/canonical/loki-k8s-operator/tree/main/terraform): Backend for logs +- [prometheus-k8s](https://github.com/canonical/prometheus-k8s-operator/tree/main/terraform/): Backend for metrics - [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator): certificates operator to secure traffic with TLS. +- [traefik](https://github.com/canonical/traefik-k8s-operator/tree/main/terraform): ingress. ## Requirements @@ -19,11 +21,11 @@ This module requires a `juju` model to be available. Refer to the [usage section The module offers the following configurable inputs: -| Name | Type | Description | Required | -|--------------|--------|------------------------------------------------------------------------|-------------| -| `channel` | string | Channel that the charms are deployed from | latest/edge | -| `model_name` | string | Name of the model that the charm is deployed on | | -| `use_tls` | bool | Specify whether to use TLS or not for coordinator-worker communication | | +| Name | Type | Description | Default | +|--|--|--|--| +| `channel` | string | Channel that all the charms (unless overwritten) are deployed from | +| `model` | string | Reference to an existing model resource or data source for the model to deploy to | +| `use_tls` | bool | Specify whether to use TLS or not for coordinator-worker communication | true | ### Outputs @@ -42,5 +44,5 @@ Upon application, the module exports the following outputs: Users should ensure that Terraform is aware of the `juju_model` dependency of the charm module. -To deploy this module with its needed dependency, you can run `terraform apply -var="model_name=" -auto-approve`. This would deploy all COS HA solution modules in the same model. +To deploy this module with its needed dependency, you can run `terraform apply -var="model=" -auto-approve`. This would deploy all COS HA solution modules in the same model. diff --git a/terraform/modules/cos-lite/main.tf b/terraform/modules/cos-lite/main.tf index 71331838..b3343dcc 100644 --- a/terraform/modules/cos-lite/main.tf +++ b/terraform/modules/cos-lite/main.tf @@ -1,62 +1,60 @@ # -------------- # Applications -------------- -module "ssc" { - count = var.use_tls ? 1 : 0 - source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" - model = var.model_name - channel = var.channel -} - module "alertmanager" { - source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform" - app_name = "alertmanager" - model_name = var.model_name - channel = var.channel + source = "git::https://github.com/canonical/alertmanager-k8s-operator//terraform" + app_name = "alertmanager" + model = var.model + channel = var.channel } module "catalogue" { - source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform" - app_name = "catalogue" - model_name = var.model_name - channel = var.channel + source = "git::https://github.com/canonical/catalogue-k8s-operator//terraform" + app_name = "catalogue" + model = var.model + channel = var.channel } -module "prometheus" { - source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform" - app_name = "prometheus" - model_name = var.model_name - channel = var.channel +module "grafana" { + source = "git::https://github.com/canonical/grafana-k8s-operator//terraform" + app_name = "grafana" + model = var.model + channel = var.channel } module "loki" { source = "git::https://github.com/canonical/loki-k8s-operator//terraform" app_name = "loki" - model_name = var.model_name + model_name = var.model channel = var.channel } -module "grafana" { - source = "git::https://github.com/canonical/grafana-k8s-operator//terraform" - app_name = "grafana" - model_name = var.model_name +module "prometheus" { + source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform" + app_name = "prometheus" + model_name = var.model channel = var.channel } -module "traefik" { - source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" - app_name = "traefik" - model_name = var.model_name - channel = var.channel +module "ssc" { + count = var.use_tls ? 1 : 0 + source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" + model = var.model + channel = var.channel } +module "traefik" { + source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" + app_name = "traefik" + model = var.model + channel = var.channel +} # -------------- # Integrations -------------- - # Provided by Alertmanager resource "juju_integration" "alertmanager_grafana_dashboards" { - model = var.model_name + model = var.model application { name = module.alertmanager.app_name @@ -70,7 +68,7 @@ resource "juju_integration" "alertmanager_grafana_dashboards" { } resource "juju_integration" "alertmanager_prometheus" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -84,7 +82,7 @@ resource "juju_integration" "alertmanager_prometheus" { } resource "juju_integration" "alertmanager_self_monitoring_prometheus" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -98,7 +96,7 @@ resource "juju_integration" "alertmanager_self_monitoring_prometheus" { } resource "juju_integration" "alertmanager_loki" { - model = var.model_name + model = var.model application { name = module.loki.app_name @@ -113,7 +111,7 @@ resource "juju_integration" "alertmanager_loki" { resource "juju_integration" "grafana_source_alertmanager" { - model = var.model_name + model = var.model application { name = module.alertmanager.app_name @@ -129,7 +127,7 @@ resource "juju_integration" "grafana_source_alertmanager" { # Provided by Grafana resource "juju_integration" "grafana_self_monitoring_prometheus" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -146,7 +144,7 @@ resource "juju_integration" "grafana_self_monitoring_prometheus" { # Provided by Prometheus resource "juju_integration" "prometheus_grafana_dashboards_provider" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -160,7 +158,7 @@ resource "juju_integration" "prometheus_grafana_dashboards_provider" { } resource "juju_integration" "prometheus_grafana_source" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -179,7 +177,7 @@ resource "juju_integration" "prometheus_grafana_source" { # Provided by Loki resource "juju_integration" "loki_grafana_dashboards_provider" { - model = var.model_name + model = var.model application { name = module.loki.app_name @@ -193,7 +191,7 @@ resource "juju_integration" "loki_grafana_dashboards_provider" { } resource "juju_integration" "loki_grafana_source" { - model = var.model_name + model = var.model application { name = module.loki.app_name @@ -207,7 +205,7 @@ resource "juju_integration" "loki_grafana_source" { } resource "juju_integration" "loki_self_monitoring_prometheus" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -224,7 +222,7 @@ resource "juju_integration" "loki_self_monitoring_prometheus" { # Provided by Catalogue resource "juju_integration" "catalogue_alertmanager" { - model = var.model_name + model = var.model application { name = module.catalogue.app_name @@ -238,7 +236,7 @@ resource "juju_integration" "catalogue_alertmanager" { } resource "juju_integration" "catalogue_grafana" { - model = var.model_name + model = var.model application { name = module.catalogue.app_name @@ -252,7 +250,7 @@ resource "juju_integration" "catalogue_grafana" { } resource "juju_integration" "catalogue_prometheus" { - model = var.model_name + model = var.model application { name = module.catalogue.app_name @@ -269,7 +267,7 @@ resource "juju_integration" "catalogue_prometheus" { # Provided by Traefik resource "juju_integration" "alertmanager_ingress" { - model = var.model_name + model = var.model application { name = module.traefik.app_name @@ -284,7 +282,7 @@ resource "juju_integration" "alertmanager_ingress" { resource "juju_integration" "catalogue_ingress" { - model = var.model_name + model = var.model application { name = module.traefik.app_name @@ -298,7 +296,7 @@ resource "juju_integration" "catalogue_ingress" { } resource "juju_integration" "grafana_ingress" { - model = var.model_name + model = var.model application { name = module.traefik.app_name @@ -312,7 +310,7 @@ resource "juju_integration" "grafana_ingress" { } resource "juju_integration" "prometheus_ingress" { - model = var.model_name + model = var.model application { name = module.traefik.app_name @@ -326,7 +324,7 @@ resource "juju_integration" "prometheus_ingress" { } resource "juju_integration" "loki_ingress" { - model = var.model_name + model = var.model application { name = module.traefik.app_name @@ -340,7 +338,7 @@ resource "juju_integration" "loki_ingress" { } resource "juju_integration" "traefik_self_monitoring_prometheus" { - model = var.model_name + model = var.model application { name = module.prometheus.app_name @@ -357,28 +355,28 @@ resource "juju_integration" "traefik_self_monitoring_prometheus" { resource "juju_offer" "alertmanager-karma-dashboard" { name = "alertmanager-karma-dashboard" - model = var.model_name + model = var.model application_name = module.alertmanager.app_name endpoint = "karma-dashboard" } resource "juju_offer" "grafana-dashboards" { name = "grafana-dashboards" - model = var.model_name + model = var.model application_name = module.grafana.app_name endpoint = "grafana-dashboard" } resource "juju_offer" "loki-logging" { name = "loki-logging" - model = var.model_name + model = var.model application_name = module.loki.app_name endpoint = "logging" } resource "juju_offer" "prometheus-receive-remote-write" { name = "prometheus-receive-remote-write" - model = var.model_name + model = var.model application_name = module.prometheus.app_name endpoint = "receive-remote-write" } diff --git a/terraform/modules/cos-lite/variables.tf b/terraform/modules/cos-lite/variables.tf index c8f3a356..fd74d96a 100644 --- a/terraform/modules/cos-lite/variables.tf +++ b/terraform/modules/cos-lite/variables.tf @@ -4,7 +4,7 @@ variable "channel" { default = "latest/edge" } -variable "model_name" { +variable "model" { description = "Model name" type = string } diff --git a/terraform/modules/cos/README.md b/terraform/modules/cos/README.md index e0387019..151ff574 100644 --- a/terraform/modules/cos/README.md +++ b/terraform/modules/cos/README.md @@ -2,8 +2,9 @@ This is a Terraform module facilitating the deployment of COS solution, using the [Terraform juju provider](https://github.com/juju/terraform-provider-juju/). For more information, refer to the provider [documentation](https://registry.terraform.io/providers/juju/juju/latest/docs). -The HA solution consists of the following Terraform modules: +The HA solution consists of the following components: - [alertmanager-k8s](https://github.com/canonical/alertmanager-k8s-operator/tree/main/terraform): Handles alerts sent by clients applications. +- [catalogue-k8s](https://github.com/canonical/catalogue-k8s-operator/tree/main/terraform): UI catalogue. - [grafana-k8s](https://github.com/canonical/grafana-k8s-operator/tree/main/terraform): Visualization, monitoring, and dashboards. - [grafana-agent-k8s](https://github.com/canonical/grafana-agent-k8s-operator/tree/main/terraform): Aggregate and send telemetry data. - [loki](https://github.com/canonical/observability/tree/main/terraform/modules/loki): Backend for logs. @@ -74,15 +75,8 @@ Upon application, the module exports the following outputs: | Name | Type | Description | | - | - | - | -| `alertmanager`| module | Alertmanager module | -| `catalogue`| module | Catalogue module | -| `grafana`| module | Grafana module | -| `grafana_agent`| module | Grafana agent module | -| `loki`| module | Loki module | -| `mimir`| module | Mimir module | -| `ssc`| module | Self-signed certificates module | -| `tempo`| module | Tempo module | -| `traefik`| module | Traefik module | +| `components`| map(any) | All TF charm submodule which make up this product module | +| `offers`| map(any) | All offers which are exposed by this product module | ## Usage diff --git a/terraform/modules/cos/outputs.tf b/terraform/modules/cos/outputs.tf index 0de44f9b..ab886241 100644 --- a/terraform/modules/cos/outputs.tf +++ b/terraform/modules/cos/outputs.tf @@ -9,7 +9,7 @@ output "offers" { } } -# -------------- # Sub-modules -------------- # +# -------------- # Submodules -------------- # output "components" { value = { From 95b02a00d9ca4170628cfe62daf2760c5ff290bf Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 10:23:40 -0400 Subject: [PATCH 05/19] test: validate TF CI --- terraform/modules/cos-lite/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/modules/cos-lite/main.tf b/terraform/modules/cos-lite/main.tf index b3343dcc..e1414099 100644 --- a/terraform/modules/cos-lite/main.tf +++ b/terraform/modules/cos-lite/main.tf @@ -29,7 +29,7 @@ module "loki" { } module "prometheus" { - source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform" + source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform?ref=v44" app_name = "prometheus" model_name = var.model channel = var.channel From e7f2b7b6abbc3a182bd6a4eb498dfa77ee4d5d34 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 10:47:41 -0400 Subject: [PATCH 06/19] chore: fix TF just recipes --- justfile | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/justfile b/justfile index 8736439c..a63199ae 100644 --- a/justfile +++ b/justfile @@ -1,6 +1,9 @@ set quiet # Recipes are silent by default set export # Just variables are exported to the environment +repo-root := invocation_directory() +tf-dir := 'terraform/modules' + terraform := `which terraform || which tofu || echo ""` # require 'terraform' or 'opentofu' [private] @@ -24,16 +27,16 @@ lint-workflows: [group("Lint")] lint-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - $terraform fmt -check -recursive -diff - -# Validate the Terraform modules -[group("Validate")] -validate-terraform: - if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - $terraform validate + cd {{tf-dir}} && $terraform fmt -check -recursive -diff && cd {{repo-root}} # Format the Terraform modules [group("Format")] format-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - $terraform fmt -recursive -diff + cd {{tf-dir}} && $terraform fmt -recursive -diff && cd {{repo-root}} + +# Validate the Terraform modules +[group("Validate")] +validate-terraform: + if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi + cd {{tf-dir}} && $terraform validate && cd {{repo-root}} From 3cb84d30572b6a159511e11d4622f64ad0238bdf Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 11:16:19 -0400 Subject: [PATCH 07/19] fix: justfile TF recipes --- justfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/justfile b/justfile index a63199ae..19e34b3a 100644 --- a/justfile +++ b/justfile @@ -1,9 +1,6 @@ set quiet # Recipes are silent by default set export # Just variables are exported to the environment -repo-root := invocation_directory() -tf-dir := 'terraform/modules' - terraform := `which terraform || which tofu || echo ""` # require 'terraform' or 'opentofu' [private] @@ -12,7 +9,7 @@ default: # Lint everything [group("Lint")] -lint: lint-terraform lint-workflows +lint: lint-workflows lint-terraform # Format everything [group("Format")] @@ -25,18 +22,21 @@ lint-workflows: # Lint the Terraform modules [group("Lint")] +[working-directory("./terraform/modules")] lint-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - cd {{tf-dir}} && $terraform fmt -check -recursive -diff && cd {{repo-root}} + for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform fmt -check -recursive -diff); done # Format the Terraform modules [group("Format")] +[working-directory("./terraform/modules")] format-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - cd {{tf-dir}} && $terraform fmt -recursive -diff && cd {{repo-root}} + for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform fmt -recursive -diff); done # Validate the Terraform modules [group("Validate")] +[working-directory("./terraform/modules")] validate-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - cd {{tf-dir}} && $terraform validate && cd {{repo-root}} + for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform validate); done From ea8c2f653e9bf2dff0fda34f31a1d7f8e755e1d6 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 11:17:35 -0400 Subject: [PATCH 08/19] chore: revert test, solution works now --- terraform/modules/cos-lite/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/modules/cos-lite/main.tf b/terraform/modules/cos-lite/main.tf index e1414099..b3343dcc 100644 --- a/terraform/modules/cos-lite/main.tf +++ b/terraform/modules/cos-lite/main.tf @@ -29,7 +29,7 @@ module "loki" { } module "prometheus" { - source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform?ref=v44" + source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform" app_name = "prometheus" model_name = var.model channel = var.channel From ed567db9eb4116a9012117261b6caa9132c6eb53 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 11:35:15 -0400 Subject: [PATCH 09/19] fix: Justfile and README --- justfile | 6 +++--- terraform/modules/README.md | 3 +++ 2 files changed, 6 insertions(+), 3 deletions(-) create mode 100644 terraform/modules/README.md diff --git a/justfile b/justfile index 19e34b3a..5b55d679 100644 --- a/justfile +++ b/justfile @@ -25,18 +25,18 @@ lint-workflows: [working-directory("./terraform/modules")] lint-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform fmt -check -recursive -diff); done + set -e; for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform fmt -check -recursive -diff) || exit 1; done # Format the Terraform modules [group("Format")] [working-directory("./terraform/modules")] format-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform fmt -recursive -diff); done + set -e; for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform fmt -recursive -diff) || exit 1; done # Validate the Terraform modules [group("Validate")] [working-directory("./terraform/modules")] validate-terraform: if [ -z "${terraform}" ]; then echo "ERROR: please install terraform or opentofu"; exit 1; fi - for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform validate); done + set -e; for repo in */; do (cd "$repo" && echo "Processing ${repo%/}..." && $terraform init -upgrade -reconfigure && $terraform validate) || exit 1; done diff --git a/terraform/modules/README.md b/terraform/modules/README.md new file mode 100644 index 00000000..2e9a432d --- /dev/null +++ b/terraform/modules/README.md @@ -0,0 +1,3 @@ +## Dev workflow + +Use the [justfile](https://github.com/canonical/observability-stack/blob/main/justfile) to lint, format, and validate your TF changes. From a239fd028d6c4178b86e9b377e482d5ea32924f9 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 12:12:00 -0400 Subject: [PATCH 10/19] chore: update module source paths --- .github/workflows/_local-pull-request.yaml | 14 ++------------ terraform/modules/aws-infra/README.md | 2 +- terraform/modules/cos/README.md | 10 +++++----- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/.github/workflows/_local-pull-request.yaml b/.github/workflows/_local-pull-request.yaml index 394aa344..9c0c6a68 100644 --- a/.github/workflows/_local-pull-request.yaml +++ b/.github/workflows/_local-pull-request.yaml @@ -4,20 +4,10 @@ on: pull_request: branches: - main + paths: + - '**/*.tf' jobs: - lint-workflows: - name: Workflows Lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo snap install astral-uv --classic - sudo snap install just --classic - - name: Lint the workflows - run: just lint-workflows lint-terraform: name: Terraform lint runs-on: ubuntu-latest diff --git a/terraform/modules/aws-infra/README.md b/terraform/modules/aws-infra/README.md index c36c58af..15bafdca 100644 --- a/terraform/modules/aws-infra/README.md +++ b/terraform/modules/aws-infra/README.md @@ -31,7 +31,7 @@ In order to provision the AWS infrastructure required for COS, create a `main.tf ```hcl module "aws_infra" { - source = "git::https://github.com/canonical/observability//terraform/modules/aws-infra" + source = "git::https://github.com/canonical/observability-stack//terraform/modules/aws-infra" region = var.region cos_cloud_name = var.cos_cloud_name cos_controller_name = var.cos_controller_name diff --git a/terraform/modules/cos/README.md b/terraform/modules/cos/README.md index 151ff574..a9a9ba9f 100644 --- a/terraform/modules/cos/README.md +++ b/terraform/modules/cos/README.md @@ -7,9 +7,9 @@ The HA solution consists of the following components: - [catalogue-k8s](https://github.com/canonical/catalogue-k8s-operator/tree/main/terraform): UI catalogue. - [grafana-k8s](https://github.com/canonical/grafana-k8s-operator/tree/main/terraform): Visualization, monitoring, and dashboards. - [grafana-agent-k8s](https://github.com/canonical/grafana-agent-k8s-operator/tree/main/terraform): Aggregate and send telemetry data. -- [loki](https://github.com/canonical/observability/tree/main/terraform/modules/loki): Backend for logs. -- [mimir](https://github.com/canonical/observability/tree/main/terraform/modules/mimir): Backend for metrics. -- [tempo](https://github.com/canonical/observability/tree/main/terraform/modules/tempo): Backend for traces. +- [loki](https://github.com/canonical/observability-stack/tree/main/terraform/modules/loki): Backend for logs. +- [mimir](https://github.com/canonical/observability-stack/tree/main/terraform/modules/mimir): Backend for metrics. +- [tempo](https://github.com/canonical/observability-stack/tree/main/terraform/modules/tempo): Backend for traces. - [s3-integrator](https://github.com/canonical/s3-integrator): facade for S3 storage configurations. - [self-signed-certificates](https://github.com/canonical/self-signed-certificates-operator/tree/main/terraform): certificates operator to secure traffic with TLS. - [traefik](https://github.com/canonical/traefik-k8s-operator/tree/main/terraform): ingress. @@ -113,7 +113,7 @@ terraform { # COS module that deploy the whole Canonical Observability Stack module "cos" { - source = "git::https://github.com/canonical/observability//terraform/modules/cos" + source = "git::https://github.com/canonical/observability-stack//terraform/modules/cos" model = "cos" channel = "2/edge" s3_integrator_channel = "2/edge" @@ -320,7 +320,7 @@ In order to deploy COS on AWS, create a `main.tf` file with the following conten ```hcl # COS module that deploy the whole Canonical Observability Stack module "cos" { - source = "git::https://github.com/canonical/observability//terraform/modules/cos" + source = "git::https://github.com/canonical/observability-stack//terraform/modules/cos" model_name = var.model_name channel = var.channel s3_endpoint = var.s3_endpoint From 0e2f14c7e3cc84629498b5f44b6e90621b4ac0cd Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 12:23:36 -0400 Subject: [PATCH 11/19] chore: add back the lint-workflows to PRs --- .github/workflows/_local-pull-request.yaml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/_local-pull-request.yaml b/.github/workflows/_local-pull-request.yaml index 9c0c6a68..b66d2340 100644 --- a/.github/workflows/_local-pull-request.yaml +++ b/.github/workflows/_local-pull-request.yaml @@ -8,6 +8,18 @@ on: - '**/*.tf' jobs: + lint-workflows: + name: Workflows Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install dependencies + run: | + sudo snap install astral-uv --classic + sudo snap install just --classic + - name: Lint the workflows + run: just lint-workflows lint-terraform: name: Terraform lint runs-on: ubuntu-latest From 6c639636f17bd610ca9b9ddc05238bd8b10e4ecb Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Mon, 16 Jun 2025 14:31:06 -0400 Subject: [PATCH 12/19] feat: TF TLS part 1 --- terraform/modules/cos-lite/main.tf | 104 +++++++++++++++++++++--- terraform/modules/cos-lite/variables.tf | 15 ++++ 2 files changed, 109 insertions(+), 10 deletions(-) diff --git a/terraform/modules/cos-lite/main.tf b/terraform/modules/cos-lite/main.tf index b3343dcc..c6a34f00 100644 --- a/terraform/modules/cos-lite/main.tf +++ b/terraform/modules/cos-lite/main.tf @@ -39,14 +39,14 @@ module "ssc" { count = var.use_tls ? 1 : 0 source = "git::https://github.com/canonical/self-signed-certificates-operator//terraform" model = var.model - channel = var.channel + channel = var.ssc_channel } module "traefik" { source = "git::https://github.com/canonical/traefik-k8s-operator//terraform" app_name = "traefik" model = var.model - channel = var.channel + channel = var.traefik_channel } # -------------- # Integrations -------------- @@ -109,7 +109,6 @@ resource "juju_integration" "alertmanager_loki" { } } - resource "juju_integration" "grafana_source_alertmanager" { model = var.model @@ -140,7 +139,6 @@ resource "juju_integration" "grafana_self_monitoring_prometheus" { } } - # Provided by Prometheus resource "juju_integration" "prometheus_grafana_dashboards_provider" { @@ -171,9 +169,6 @@ resource "juju_integration" "prometheus_grafana_source" { } } - - - # Provided by Loki resource "juju_integration" "loki_grafana_dashboards_provider" { @@ -218,7 +213,6 @@ resource "juju_integration" "loki_self_monitoring_prometheus" { } } - # Provided by Catalogue resource "juju_integration" "catalogue_alertmanager" { @@ -263,7 +257,6 @@ resource "juju_integration" "catalogue_prometheus" { } } - # Provided by Traefik resource "juju_integration" "alertmanager_ingress" { @@ -280,7 +273,6 @@ resource "juju_integration" "alertmanager_ingress" { } } - resource "juju_integration" "catalogue_ingress" { model = var.model @@ -351,6 +343,98 @@ resource "juju_integration" "traefik_self_monitoring_prometheus" { } } +# Provided by Self-Signed-Certificates + +resource "juju_integration" "traefik_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.certificates + } +} + +resource "juju_integration" "alertmanager_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.certificates + } +} + +resource "juju_integration" "catalogue_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.certificates + } +} + +resource "juju_integration" "grafana_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.certificates + } +} + +resource "juju_integration" "loki_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.certificates + } +} + +resource "juju_integration" "prometheus_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.certificates + } +} + # -------------- # Offers -------------- resource "juju_offer" "alertmanager-karma-dashboard" { diff --git a/terraform/modules/cos-lite/variables.tf b/terraform/modules/cos-lite/variables.tf index fd74d96a..49f686d6 100644 --- a/terraform/modules/cos-lite/variables.tf +++ b/terraform/modules/cos-lite/variables.tf @@ -14,3 +14,18 @@ variable "use_tls" { type = bool default = true } + +# -------------- # External channels -------------- +# O11y does not own these charms, so we allow users to specify their channels directly. + +variable "ssc_channel" { + description = "Channel that the self-signed certificates charm is deployed from" + type = string + default = "1/stable" +} + +variable "traefik_channel" { + description = "Channel that the Traefik charm is deployed from" + type = string + default = "latest/stable" +} \ No newline at end of file From 90bcd70814aead28a8f50badf33e2142eb80b6e1 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Wed, 18 Jun 2025 08:25:26 -0400 Subject: [PATCH 13/19] chore: flatten dir --- terraform/{modules => }/README.md | 0 terraform/{modules => }/aws-infra/README.md | 0 terraform/{modules => }/aws-infra/justfile | 0 terraform/{modules => }/aws-infra/main.tf | 0 terraform/{modules => }/aws-infra/outputs.tf | 0 .../{modules => }/aws-infra/variables.tf | 0 terraform/{modules => }/aws-infra/versions.tf | 0 terraform/{modules => }/cos-lite/README.md | 0 terraform/{modules => }/cos-lite/main.tf | 52 +++++--- terraform/{modules => }/cos-lite/outputs.tf | 0 terraform/{modules => }/cos-lite/variables.tf | 2 +- terraform/{modules => }/cos-lite/versions.tf | 0 terraform/{modules => }/cos/README.md | 0 terraform/{modules => }/cos/main.tf | 125 ++++++++++++++++++ terraform/{modules => }/cos/outputs.tf | 0 terraform/{modules => }/cos/variables.tf | 0 terraform/{modules => }/cos/versions.tf | 0 terraform/{modules => }/loki/README.md | 0 terraform/{modules => }/loki/main.tf | 0 terraform/{modules => }/loki/outputs.tf | 0 terraform/{modules => }/loki/variables.tf | 0 terraform/{modules => }/loki/versions.tf | 0 terraform/{modules => }/mimir/README.md | 0 terraform/{modules => }/mimir/main.tf | 0 terraform/{modules => }/mimir/outputs.tf | 0 terraform/{modules => }/mimir/variables.tf | 0 terraform/{modules => }/mimir/versions.tf | 0 terraform/{modules => }/minio/main.tf | 0 terraform/{modules => }/minio/outputs.tf | 0 .../minio/scripts/s3management.sh | 0 terraform/{modules => }/minio/variables.tf | 0 terraform/{modules => }/minio/version.tf | 0 .../hcl-generator/generate_integrations.py | 116 ---------------- terraform/{modules => }/tempo/README.md | 0 terraform/{modules => }/tempo/main.tf | 0 terraform/{modules => }/tempo/outputs.tf | 0 terraform/{modules => }/tempo/variables.tf | 0 terraform/{modules => }/tempo/versions.tf | 0 38 files changed, 156 insertions(+), 139 deletions(-) rename terraform/{modules => }/README.md (100%) rename terraform/{modules => }/aws-infra/README.md (100%) rename terraform/{modules => }/aws-infra/justfile (100%) rename terraform/{modules => }/aws-infra/main.tf (100%) rename terraform/{modules => }/aws-infra/outputs.tf (100%) rename terraform/{modules => }/aws-infra/variables.tf (100%) rename terraform/{modules => }/aws-infra/versions.tf (100%) rename terraform/{modules => }/cos-lite/README.md (100%) rename terraform/{modules => }/cos-lite/main.tf (93%) rename terraform/{modules => }/cos-lite/outputs.tf (100%) rename terraform/{modules => }/cos-lite/variables.tf (99%) rename terraform/{modules => }/cos-lite/versions.tf (100%) rename terraform/{modules => }/cos/README.md (100%) rename terraform/{modules => }/cos/main.tf (84%) rename terraform/{modules => }/cos/outputs.tf (100%) rename terraform/{modules => }/cos/variables.tf (100%) rename terraform/{modules => }/cos/versions.tf (100%) rename terraform/{modules => }/loki/README.md (100%) rename terraform/{modules => }/loki/main.tf (100%) rename terraform/{modules => }/loki/outputs.tf (100%) rename terraform/{modules => }/loki/variables.tf (100%) rename terraform/{modules => }/loki/versions.tf (100%) rename terraform/{modules => }/mimir/README.md (100%) rename terraform/{modules => }/mimir/main.tf (100%) rename terraform/{modules => }/mimir/outputs.tf (100%) rename terraform/{modules => }/mimir/variables.tf (100%) rename terraform/{modules => }/mimir/versions.tf (100%) rename terraform/{modules => }/minio/main.tf (100%) rename terraform/{modules => }/minio/outputs.tf (100%) rename terraform/{modules => }/minio/scripts/s3management.sh (100%) rename terraform/{modules => }/minio/variables.tf (100%) rename terraform/{modules => }/minio/version.tf (100%) delete mode 100644 terraform/scripts/hcl-generator/generate_integrations.py rename terraform/{modules => }/tempo/README.md (100%) rename terraform/{modules => }/tempo/main.tf (100%) rename terraform/{modules => }/tempo/outputs.tf (100%) rename terraform/{modules => }/tempo/variables.tf (100%) rename terraform/{modules => }/tempo/versions.tf (100%) diff --git a/terraform/modules/README.md b/terraform/README.md similarity index 100% rename from terraform/modules/README.md rename to terraform/README.md diff --git a/terraform/modules/aws-infra/README.md b/terraform/aws-infra/README.md similarity index 100% rename from terraform/modules/aws-infra/README.md rename to terraform/aws-infra/README.md diff --git a/terraform/modules/aws-infra/justfile b/terraform/aws-infra/justfile similarity index 100% rename from terraform/modules/aws-infra/justfile rename to terraform/aws-infra/justfile diff --git a/terraform/modules/aws-infra/main.tf b/terraform/aws-infra/main.tf similarity index 100% rename from terraform/modules/aws-infra/main.tf rename to terraform/aws-infra/main.tf diff --git a/terraform/modules/aws-infra/outputs.tf b/terraform/aws-infra/outputs.tf similarity index 100% rename from terraform/modules/aws-infra/outputs.tf rename to terraform/aws-infra/outputs.tf diff --git a/terraform/modules/aws-infra/variables.tf b/terraform/aws-infra/variables.tf similarity index 100% rename from terraform/modules/aws-infra/variables.tf rename to terraform/aws-infra/variables.tf diff --git a/terraform/modules/aws-infra/versions.tf b/terraform/aws-infra/versions.tf similarity index 100% rename from terraform/modules/aws-infra/versions.tf rename to terraform/aws-infra/versions.tf diff --git a/terraform/modules/cos-lite/README.md b/terraform/cos-lite/README.md similarity index 100% rename from terraform/modules/cos-lite/README.md rename to terraform/cos-lite/README.md diff --git a/terraform/modules/cos-lite/main.tf b/terraform/cos-lite/main.tf similarity index 93% rename from terraform/modules/cos-lite/main.tf rename to terraform/cos-lite/main.tf index c6a34f00..7d9a3f58 100644 --- a/terraform/modules/cos-lite/main.tf +++ b/terraform/cos-lite/main.tf @@ -24,15 +24,23 @@ module "grafana" { module "loki" { source = "git::https://github.com/canonical/loki-k8s-operator//terraform" app_name = "loki" - model_name = var.model - channel = var.channel + model_name = var.model + channel = var.charms["loki"].channel + config = var.charms["loki"].config + constraints = var.charms["loki"].constraints + revision = var.charms["loki"].revision + units = var.charms["loki"].units } module "prometheus" { source = "git::https://github.com/canonical/prometheus-k8s-operator//terraform" app_name = "prometheus" model_name = var.model - channel = var.channel + channel = var.charms["prometheus"].channel + config = var.charms["prometheus"].config + constraints = var.charms["prometheus"].constraints + revision = var.charms["prometheus"].revision + units = var.charms["prometheus"].units } module "ssc" { @@ -345,21 +353,6 @@ resource "juju_integration" "traefik_self_monitoring_prometheus" { # Provided by Self-Signed-Certificates -resource "juju_integration" "traefik_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.certificates - } -} - resource "juju_integration" "alertmanager_certificates" { count = var.use_tls ? 1 : 0 model = var.model @@ -435,30 +428,45 @@ resource "juju_integration" "prometheus_certificates" { } } +resource "juju_integration" "traefik_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.certificates + } +} + # -------------- # Offers -------------- -resource "juju_offer" "alertmanager-karma-dashboard" { +resource "juju_offer" "alertmanager_karma_dashboard" { name = "alertmanager-karma-dashboard" model = var.model application_name = module.alertmanager.app_name endpoint = "karma-dashboard" } -resource "juju_offer" "grafana-dashboards" { +resource "juju_offer" "grafana_dashboards" { name = "grafana-dashboards" model = var.model application_name = module.grafana.app_name endpoint = "grafana-dashboard" } -resource "juju_offer" "loki-logging" { +resource "juju_offer" "loki_logging" { name = "loki-logging" model = var.model application_name = module.loki.app_name endpoint = "logging" } -resource "juju_offer" "prometheus-receive-remote-write" { +resource "juju_offer" "prometheus_receive_remote_write" { name = "prometheus-receive-remote-write" model = var.model application_name = module.prometheus.app_name diff --git a/terraform/modules/cos-lite/outputs.tf b/terraform/cos-lite/outputs.tf similarity index 100% rename from terraform/modules/cos-lite/outputs.tf rename to terraform/cos-lite/outputs.tf diff --git a/terraform/modules/cos-lite/variables.tf b/terraform/cos-lite/variables.tf similarity index 99% rename from terraform/modules/cos-lite/variables.tf rename to terraform/cos-lite/variables.tf index 49f686d6..463b42b2 100644 --- a/terraform/modules/cos-lite/variables.tf +++ b/terraform/cos-lite/variables.tf @@ -28,4 +28,4 @@ variable "traefik_channel" { description = "Channel that the Traefik charm is deployed from" type = string default = "latest/stable" -} \ No newline at end of file +} diff --git a/terraform/modules/cos-lite/versions.tf b/terraform/cos-lite/versions.tf similarity index 100% rename from terraform/modules/cos-lite/versions.tf rename to terraform/cos-lite/versions.tf diff --git a/terraform/modules/cos/README.md b/terraform/cos/README.md similarity index 100% rename from terraform/modules/cos/README.md rename to terraform/cos/README.md diff --git a/terraform/modules/cos/main.tf b/terraform/cos/main.tf similarity index 84% rename from terraform/modules/cos/main.tf rename to terraform/cos/main.tf index 07309b5e..75015d5d 100644 --- a/terraform/modules/cos/main.tf +++ b/terraform/cos/main.tf @@ -33,6 +33,7 @@ module "grafana_agent" { } module "loki" { + # TODO: Update to remove the modules path source = "git::https://github.com/canonical/observability-stack//terraform/modules/loki?ref=feat/tf-migration" model = var.model channel = var.channel @@ -52,6 +53,7 @@ module "loki" { } module "mimir" { + # TODO: Update to remove the modules path source = "git::https://github.com/canonical/observability-stack//terraform/modules/mimir?ref=feat/tf-migration" model = var.model channel = var.channel @@ -79,6 +81,7 @@ module "ssc" { } module "tempo" { + # TODO: Update to remove the modules path source = "git::https://github.com/canonical/observability-stack//terraform/modules/tempo?ref=feat/tf-migration" model = var.model channel = var.channel @@ -576,6 +579,128 @@ resource "juju_integration" "grafana_tracing_grafana_agent_traicing_provider" { } } +# Provided by Self-Signed-Certificates + +resource "juju_integration" "alertmanager_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.certificates + } +} + +resource "juju_integration" "catalogue_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.certificates + } +} + +resource "juju_integration" "grafana_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.certificates + } +} + +resource "juju_integration" "grafana_agent_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.grafana_agent.app_name + endpoint = module.grafana_agent.endpoints.certificates + } +} + +resource "juju_integration" "loki_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.loki.app_names.loki_coordinator + endpoint = module.loki.endpoints.certificates + } +} + +resource "juju_integration" "mimir_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.mimir.app_names.mimir_coordinator + endpoint = module.mimir.endpoints.certificates + } +} + +resource "juju_integration" "tempo_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.tempo.app_names.tempo_coordinator + endpoint = module.tempo.endpoints.certificates + } +} + +resource "juju_integration" "traefik_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.certificates + } +} + # -------------- # Offers -------------- resource "juju_offer" "alertmanager-karma-dashboard" { diff --git a/terraform/modules/cos/outputs.tf b/terraform/cos/outputs.tf similarity index 100% rename from terraform/modules/cos/outputs.tf rename to terraform/cos/outputs.tf diff --git a/terraform/modules/cos/variables.tf b/terraform/cos/variables.tf similarity index 100% rename from terraform/modules/cos/variables.tf rename to terraform/cos/variables.tf diff --git a/terraform/modules/cos/versions.tf b/terraform/cos/versions.tf similarity index 100% rename from terraform/modules/cos/versions.tf rename to terraform/cos/versions.tf diff --git a/terraform/modules/loki/README.md b/terraform/loki/README.md similarity index 100% rename from terraform/modules/loki/README.md rename to terraform/loki/README.md diff --git a/terraform/modules/loki/main.tf b/terraform/loki/main.tf similarity index 100% rename from terraform/modules/loki/main.tf rename to terraform/loki/main.tf diff --git a/terraform/modules/loki/outputs.tf b/terraform/loki/outputs.tf similarity index 100% rename from terraform/modules/loki/outputs.tf rename to terraform/loki/outputs.tf diff --git a/terraform/modules/loki/variables.tf b/terraform/loki/variables.tf similarity index 100% rename from terraform/modules/loki/variables.tf rename to terraform/loki/variables.tf diff --git a/terraform/modules/loki/versions.tf b/terraform/loki/versions.tf similarity index 100% rename from terraform/modules/loki/versions.tf rename to terraform/loki/versions.tf diff --git a/terraform/modules/mimir/README.md b/terraform/mimir/README.md similarity index 100% rename from terraform/modules/mimir/README.md rename to terraform/mimir/README.md diff --git a/terraform/modules/mimir/main.tf b/terraform/mimir/main.tf similarity index 100% rename from terraform/modules/mimir/main.tf rename to terraform/mimir/main.tf diff --git a/terraform/modules/mimir/outputs.tf b/terraform/mimir/outputs.tf similarity index 100% rename from terraform/modules/mimir/outputs.tf rename to terraform/mimir/outputs.tf diff --git a/terraform/modules/mimir/variables.tf b/terraform/mimir/variables.tf similarity index 100% rename from terraform/modules/mimir/variables.tf rename to terraform/mimir/variables.tf diff --git a/terraform/modules/mimir/versions.tf b/terraform/mimir/versions.tf similarity index 100% rename from terraform/modules/mimir/versions.tf rename to terraform/mimir/versions.tf diff --git a/terraform/modules/minio/main.tf b/terraform/minio/main.tf similarity index 100% rename from terraform/modules/minio/main.tf rename to terraform/minio/main.tf diff --git a/terraform/modules/minio/outputs.tf b/terraform/minio/outputs.tf similarity index 100% rename from terraform/modules/minio/outputs.tf rename to terraform/minio/outputs.tf diff --git a/terraform/modules/minio/scripts/s3management.sh b/terraform/minio/scripts/s3management.sh similarity index 100% rename from terraform/modules/minio/scripts/s3management.sh rename to terraform/minio/scripts/s3management.sh diff --git a/terraform/modules/minio/variables.tf b/terraform/minio/variables.tf similarity index 100% rename from terraform/modules/minio/variables.tf rename to terraform/minio/variables.tf diff --git a/terraform/modules/minio/version.tf b/terraform/minio/version.tf similarity index 100% rename from terraform/modules/minio/version.tf rename to terraform/minio/version.tf diff --git a/terraform/scripts/hcl-generator/generate_integrations.py b/terraform/scripts/hcl-generator/generate_integrations.py deleted file mode 100644 index 28d5dee1..00000000 --- a/terraform/scripts/hcl-generator/generate_integrations.py +++ /dev/null @@ -1,116 +0,0 @@ -import yaml -import sys -import hcl2 -import os -import argparse -from pathlib import Path - -OUTPUTS_TF_FILE = "outputs.tf" - - -def build_parser(): - """Create and configure the argument parser for the script.""" - - parser = argparse.ArgumentParser(description="Process YAML and generate Terraform output.") - - parser.add_argument( - '--input-yaml', - required=True, - help='Path to the input YAML file (charmcraft.yaml or metadata.yaml)' - ) - - parser.add_argument( - '--outputs-tf', - default=OUTPUTS_TF_FILE, - help=f'Path to the output Terraform file (default: {OUTPUTS_TF_FILE})' - ) - - return parser - - -class HCLGenerator(object): - def __init__(self, input_yaml: str, outputs_tf: str): - self.input_yaml = Path(input_yaml) - if outputs_tf == OUTPUTS_TF_FILE: - self.outputs_tf = Path(os.getcwd()) / outputs_tf - else: - self.outputs_tf = Path(outputs_tf) - - def parse_yaml(self): - """Parse the YAML file and return the data""" - try: - with open(self.input_yaml, "r") as file: - return yaml.safe_load(file) - except Exception as e: - print(f"Error reading YAML file: {e}") - sys.exit(1) - - @staticmethod - def format_keys(section): - """ - Format the keys by replacing hyphens with underscores - Although HCL allows dashes in keys, this does not allow for dot notation - """ - return {key.replace("-", "_"): key for key in section} - - def create_file_with_parents(self): - """Create the file and parent directories if they don't exist""" - self.outputs_tf.parent.mkdir(parents=True, exist_ok=True) - self.outputs_tf.touch(exist_ok=True) - - def generate_output(self, data): - """Generate the output string based on the parsed YAML data""" - requires = data.get("requires", {}) - provides = data.get("provides", {}) - - # Format the keys in the 'requires' and 'provides' sections - requires_formatted = self.format_keys(requires) - provides_formatted = self.format_keys(provides) - - # Prepare the output string - output = 'output "endpoints" {\n value = {\n' - - # Add requires - output += " # Requires\n" - for key, value in requires_formatted.items(): - output += f' {key:<20} = "{value}",\n' - - # Add provides - output += " # Provides\n" - for key, value in provides_formatted.items(): - output += f' {key:<20} = "{value}",\n' - - output += " }\n}" - # TODO: Create a function for `terraform format` of the output - return output - - def write_hcl_file(self, hcl_content): - """Parse the YAML file and return the data""" - - self.create_file_with_parents() - - with open(self.outputs_tf, "r") as file: - read_obj = hcl2.load(file) - - if read_obj: - print("WE HAD CONTENTS") - - with open(self.outputs_tf, "w") as file: - file.write(hcl_content) - - -def main(): - """Main function to handle script execution""" - - parser = build_parser() - args = parser.parse_args() - - gen = HCLGenerator(args.input_yaml, args.outputs_tf) - data = gen.parse_yaml() - output_raw = gen.generate_output(data) - print(output_raw) - gen.write_hcl_file(output_raw) - - -if __name__ == "__main__": - main() diff --git a/terraform/modules/tempo/README.md b/terraform/tempo/README.md similarity index 100% rename from terraform/modules/tempo/README.md rename to terraform/tempo/README.md diff --git a/terraform/modules/tempo/main.tf b/terraform/tempo/main.tf similarity index 100% rename from terraform/modules/tempo/main.tf rename to terraform/tempo/main.tf diff --git a/terraform/modules/tempo/outputs.tf b/terraform/tempo/outputs.tf similarity index 100% rename from terraform/modules/tempo/outputs.tf rename to terraform/tempo/outputs.tf diff --git a/terraform/modules/tempo/variables.tf b/terraform/tempo/variables.tf similarity index 100% rename from terraform/modules/tempo/variables.tf rename to terraform/tempo/variables.tf diff --git a/terraform/modules/tempo/versions.tf b/terraform/tempo/versions.tf similarity index 100% rename from terraform/modules/tempo/versions.tf rename to terraform/tempo/versions.tf From 8fcc5c0e0ca50b3349577d3fd1bacc1d33b68861 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Wed, 18 Jun 2025 15:58:22 -0400 Subject: [PATCH 14/19] chore: lower case offers resources --- terraform/cos-lite/main.tf | 6 +++--- terraform/cos-lite/outputs.tf | 8 ++++---- terraform/cos/main.tf | 8 ++++---- terraform/cos/outputs.tf | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/terraform/cos-lite/main.tf b/terraform/cos-lite/main.tf index 79c377e5..933f5bb7 100644 --- a/terraform/cos-lite/main.tf +++ b/terraform/cos-lite/main.tf @@ -444,21 +444,21 @@ resource "juju_offer" "alertmanager_karma_dashboard" { endpoint = "karma-dashboard" } -resource "juju_offer" "grafana-dashboards" { +resource "juju_offer" "grafana_dashboards" { name = "grafana-dashboards" model = var.model application_name = module.grafana.app_name endpoint = "grafana-dashboard" } -resource "juju_offer" "loki-logging" { +resource "juju_offer" "loki_logging" { name = "loki-logging" model = var.model application_name = module.loki.app_name endpoint = "logging" } -resource "juju_offer" "prometheus-receive-remote-write" { +resource "juju_offer" "prometheus_receive_remote_write" { name = "prometheus-receive-remote-write" model = var.model application_name = module.prometheus.app_name diff --git a/terraform/cos-lite/outputs.tf b/terraform/cos-lite/outputs.tf index 3e9fed61..ddce7ec8 100644 --- a/terraform/cos-lite/outputs.tf +++ b/terraform/cos-lite/outputs.tf @@ -2,10 +2,10 @@ output "offers" { value = { - alertmanager_karma_dashboard = juju_offer.alertmanager-karma-dashboard - grafana_dashboards = juju_offer.grafana-dashboards - loki_logging = juju_offer.loki-logging - prometheus_receive_remote_write = juju_offer.prometheus-receive-remote-write + alertmanager_karma_dashboard = juju_offer.alertmanager_karma_dashboard + grafana_dashboards = juju_offer.grafana_dashboards + loki_logging = juju_offer.loki_logging + prometheus_receive_remote_write = juju_offer.prometheus_receive_remote_write } } diff --git a/terraform/cos/main.tf b/terraform/cos/main.tf index 785736d1..3e14d98a 100644 --- a/terraform/cos/main.tf +++ b/terraform/cos/main.tf @@ -700,28 +700,28 @@ resource "juju_integration" "traefik_certificates" { # -------------- # Offers -------------- -resource "juju_offer" "alertmanager-karma-dashboard" { +resource "juju_offer" "alertmanager_karma_dashboard" { name = "alertmanager-karma-dashboard" model = var.model application_name = module.alertmanager.app_name endpoint = "karma-dashboard" } -resource "juju_offer" "grafana-dashboards" { +resource "juju_offer" "grafana_dashboards" { name = "grafana-dashboards" model = var.model application_name = module.grafana.app_name endpoint = "grafana-dashboard" } -resource "juju_offer" "loki-logging" { +resource "juju_offer" "loki_logging" { name = "loki-logging" model = var.model application_name = module.loki.app_names.loki_coordinator endpoint = "logging" } -resource "juju_offer" "mimir-receive-remote-write" { +resource "juju_offer" "mimir_receive_remote_write" { name = "mimir-receive-remote-write" model = var.model application_name = module.mimir.app_names.mimir_coordinator diff --git a/terraform/cos/outputs.tf b/terraform/cos/outputs.tf index ab886241..58e1ff83 100644 --- a/terraform/cos/outputs.tf +++ b/terraform/cos/outputs.tf @@ -2,10 +2,10 @@ output "offers" { value = { - alertmanager_karma_dashboard = juju_offer.alertmanager-karma-dashboard - grafana_dashboards = juju_offer.grafana-dashboards - loki_logging = juju_offer.loki-logging - mimir_receive_remote_write = juju_offer.mimir-receive-remote-write + alertmanager_karma_dashboard = juju_offer.alertmanager_karma_dashboard + grafana_dashboards = juju_offer.grafana_dashboards + loki_logging = juju_offer.loki_logging + mimir_receive_remote_write = juju_offer.mimir_receive_remote_write } } From a037e9ce2607a54e01abc8165020026129ededf6 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 19 Jun 2025 12:03:33 -0400 Subject: [PATCH 15/19] chore: cleanup PR --- .github/workflows/_local-pull-request.yaml | 46 ------------------- .../installation/cos-canonical-k8s-sandbox.tf | 2 +- 2 files changed, 1 insertion(+), 47 deletions(-) delete mode 100644 .github/workflows/_local-pull-request.yaml diff --git a/.github/workflows/_local-pull-request.yaml b/.github/workflows/_local-pull-request.yaml deleted file mode 100644 index b66d2340..00000000 --- a/.github/workflows/_local-pull-request.yaml +++ /dev/null @@ -1,46 +0,0 @@ -name: Pull Requests - -on: - pull_request: - branches: - - main - paths: - - '**/*.tf' - -jobs: - lint-workflows: - name: Workflows Lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo snap install astral-uv --classic - sudo snap install just --classic - - name: Lint the workflows - run: just lint-workflows - lint-terraform: - name: Terraform lint - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo snap install terraform --classic - sudo snap install just --classic - - name: Lint the Terraform modules - run: just lint-terraform - validate-terraform: - name: Terraform validate - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Install dependencies - run: | - sudo snap install terraform --classic - sudo snap install just --classic - - name: Validate the Terraform modules - run: just validate-terraform diff --git a/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf b/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf index 96df22e3..0182f644 100644 --- a/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf +++ b/docs/tutorial/installation/cos-canonical-k8s-sandbox.tf @@ -11,4 +11,4 @@ module "cos" { ssc_channel = "1/stable" traefik_channel = "latest/stable" anti_affinity = true -} +} \ No newline at end of file From 549b9f9c6b0bc8cb0f4142c7d75b83bb173088a8 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 19 Jun 2025 14:50:57 -0400 Subject: [PATCH 16/19] chore: remove COS changes --- terraform/cos-lite/main.tf | 108 +++++-------------------------------- 1 file changed, 12 insertions(+), 96 deletions(-) diff --git a/terraform/cos-lite/main.tf b/terraform/cos-lite/main.tf index 933f5bb7..0714ab7f 100644 --- a/terraform/cos-lite/main.tf +++ b/terraform/cos-lite/main.tf @@ -109,6 +109,7 @@ resource "juju_integration" "alertmanager_loki" { } } + resource "juju_integration" "grafana_source_alertmanager" { model = var.model @@ -139,6 +140,7 @@ resource "juju_integration" "grafana_self_monitoring_prometheus" { } } + # Provided by Prometheus resource "juju_integration" "prometheus_grafana_dashboards_provider" { @@ -169,6 +171,9 @@ resource "juju_integration" "prometheus_grafana_source" { } } + + + # Provided by Loki resource "juju_integration" "loki_grafana_dashboards_provider" { @@ -213,6 +218,7 @@ resource "juju_integration" "loki_self_monitoring_prometheus" { } } + # Provided by Catalogue resource "juju_integration" "catalogue_alertmanager" { @@ -257,6 +263,7 @@ resource "juju_integration" "catalogue_prometheus" { } } + # Provided by Traefik resource "juju_integration" "alertmanager_ingress" { @@ -273,6 +280,7 @@ resource "juju_integration" "alertmanager_ingress" { } } + resource "juju_integration" "catalogue_ingress" { model = var.model @@ -343,122 +351,30 @@ resource "juju_integration" "traefik_self_monitoring_prometheus" { } } -# Provided by Self-Signed-Certificates - -resource "juju_integration" "alertmanager_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.alertmanager.app_name - endpoint = module.alertmanager.endpoints.certificates - } -} - -resource "juju_integration" "catalogue_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.catalogue.app_name - endpoint = module.catalogue.endpoints.certificates - } -} - -resource "juju_integration" "grafana_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.grafana.app_name - endpoint = module.grafana.endpoints.certificates - } -} - -resource "juju_integration" "loki_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.loki.app_name - endpoint = module.loki.endpoints.certificates - } -} - -resource "juju_integration" "prometheus_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.prometheus.app_name - endpoint = module.prometheus.endpoints.certificates - } -} - -resource "juju_integration" "traefik_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.certificates - } -} - # -------------- # Offers -------------- -resource "juju_offer" "alertmanager_karma_dashboard" { +resource "juju_offer" "alertmanager-karma-dashboard" { name = "alertmanager-karma-dashboard" model = var.model application_name = module.alertmanager.app_name endpoint = "karma-dashboard" } -resource "juju_offer" "grafana_dashboards" { +resource "juju_offer" "grafana-dashboards" { name = "grafana-dashboards" model = var.model application_name = module.grafana.app_name endpoint = "grafana-dashboard" } -resource "juju_offer" "loki_logging" { +resource "juju_offer" "loki-logging" { name = "loki-logging" model = var.model application_name = module.loki.app_name endpoint = "logging" } -resource "juju_offer" "prometheus_receive_remote_write" { +resource "juju_offer" "prometheus-receive-remote-write" { name = "prometheus-receive-remote-write" model = var.model application_name = module.prometheus.app_name From 79af7a68893871c2e3ed43b1a5c0d72379e19859 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 19 Jun 2025 15:16:37 -0400 Subject: [PATCH 17/19] chore: update offers --- terraform/cos-lite/main.tf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/terraform/cos-lite/main.tf b/terraform/cos-lite/main.tf index 0714ab7f..88d4d6f2 100644 --- a/terraform/cos-lite/main.tf +++ b/terraform/cos-lite/main.tf @@ -353,28 +353,28 @@ resource "juju_integration" "traefik_self_monitoring_prometheus" { # -------------- # Offers -------------- -resource "juju_offer" "alertmanager-karma-dashboard" { +resource "juju_offer" "alertmanager_karma_dashboard" { name = "alertmanager-karma-dashboard" model = var.model application_name = module.alertmanager.app_name endpoint = "karma-dashboard" } -resource "juju_offer" "grafana-dashboards" { +resource "juju_offer" "grafana_dashboards" { name = "grafana-dashboards" model = var.model application_name = module.grafana.app_name endpoint = "grafana-dashboard" } -resource "juju_offer" "loki-logging" { +resource "juju_offer" "loki_logging" { name = "loki-logging" model = var.model application_name = module.loki.app_name endpoint = "logging" } -resource "juju_offer" "prometheus-receive-remote-write" { +resource "juju_offer" "prometheus_receive_remote_write" { name = "prometheus-receive-remote-write" model = var.model application_name = module.prometheus.app_name From 3139c620a4ec6213149edc3a3d0a609c6358e15e Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 19 Jun 2025 15:44:58 -0400 Subject: [PATCH 18/19] chore: remove COS changes --- terraform/cos/main.tf | 122 ------------------------------------------ 1 file changed, 122 deletions(-) diff --git a/terraform/cos/main.tf b/terraform/cos/main.tf index 3e14d98a..dc59cd13 100644 --- a/terraform/cos/main.tf +++ b/terraform/cos/main.tf @@ -576,128 +576,6 @@ resource "juju_integration" "grafana_tracing_grafana_agent_traicing_provider" { } } -# Provided by Self-Signed-Certificates - -resource "juju_integration" "alertmanager_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.alertmanager.app_name - endpoint = module.alertmanager.endpoints.certificates - } -} - -resource "juju_integration" "catalogue_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.catalogue.app_name - endpoint = module.catalogue.endpoints.certificates - } -} - -resource "juju_integration" "grafana_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.grafana.app_name - endpoint = module.grafana.endpoints.certificates - } -} - -resource "juju_integration" "grafana_agent_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.grafana_agent.app_name - endpoint = module.grafana_agent.endpoints.certificates - } -} - -resource "juju_integration" "loki_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.loki.app_names.loki_coordinator - endpoint = module.loki.endpoints.certificates - } -} - -resource "juju_integration" "mimir_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.mimir.app_names.mimir_coordinator - endpoint = module.mimir.endpoints.certificates - } -} - -resource "juju_integration" "tempo_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.tempo.app_names.tempo_coordinator - endpoint = module.tempo.endpoints.certificates - } -} - -resource "juju_integration" "traefik_certificates" { - count = var.use_tls ? 1 : 0 - model = var.model - - application { - name = module.ssc[0].app_name - endpoint = module.ssc[0].provides.certificates - } - - application { - name = module.traefik.app_name - endpoint = module.traefik.endpoints.certificates - } -} - # -------------- # Offers -------------- resource "juju_offer" "alertmanager_karma_dashboard" { From f2de92f79baf2ada8dfc6dcbf48c25273806d469 Mon Sep 17 00:00:00 2001 From: Michael Thamm Date: Thu, 19 Jun 2025 15:48:49 -0400 Subject: [PATCH 19/19] chore: undo the COS Lite removal --- terraform/cos-lite/main.tf | 100 ++++++++++++++++++++++++++++++++++--- 1 file changed, 92 insertions(+), 8 deletions(-) diff --git a/terraform/cos-lite/main.tf b/terraform/cos-lite/main.tf index 88d4d6f2..933f5bb7 100644 --- a/terraform/cos-lite/main.tf +++ b/terraform/cos-lite/main.tf @@ -109,7 +109,6 @@ resource "juju_integration" "alertmanager_loki" { } } - resource "juju_integration" "grafana_source_alertmanager" { model = var.model @@ -140,7 +139,6 @@ resource "juju_integration" "grafana_self_monitoring_prometheus" { } } - # Provided by Prometheus resource "juju_integration" "prometheus_grafana_dashboards_provider" { @@ -171,9 +169,6 @@ resource "juju_integration" "prometheus_grafana_source" { } } - - - # Provided by Loki resource "juju_integration" "loki_grafana_dashboards_provider" { @@ -218,7 +213,6 @@ resource "juju_integration" "loki_self_monitoring_prometheus" { } } - # Provided by Catalogue resource "juju_integration" "catalogue_alertmanager" { @@ -263,7 +257,6 @@ resource "juju_integration" "catalogue_prometheus" { } } - # Provided by Traefik resource "juju_integration" "alertmanager_ingress" { @@ -280,7 +273,6 @@ resource "juju_integration" "alertmanager_ingress" { } } - resource "juju_integration" "catalogue_ingress" { model = var.model @@ -351,6 +343,98 @@ resource "juju_integration" "traefik_self_monitoring_prometheus" { } } +# Provided by Self-Signed-Certificates + +resource "juju_integration" "alertmanager_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.alertmanager.app_name + endpoint = module.alertmanager.endpoints.certificates + } +} + +resource "juju_integration" "catalogue_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.catalogue.app_name + endpoint = module.catalogue.endpoints.certificates + } +} + +resource "juju_integration" "grafana_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.grafana.app_name + endpoint = module.grafana.endpoints.certificates + } +} + +resource "juju_integration" "loki_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.loki.app_name + endpoint = module.loki.endpoints.certificates + } +} + +resource "juju_integration" "prometheus_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.prometheus.app_name + endpoint = module.prometheus.endpoints.certificates + } +} + +resource "juju_integration" "traefik_certificates" { + count = var.use_tls ? 1 : 0 + model = var.model + + application { + name = module.ssc[0].app_name + endpoint = module.ssc[0].provides.certificates + } + + application { + name = module.traefik.app_name + endpoint = module.traefik.endpoints.certificates + } +} + # -------------- # Offers -------------- resource "juju_offer" "alertmanager_karma_dashboard" {