diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03d66da --- /dev/null +++ b/.gitignore @@ -0,0 +1,40 @@ +# Local .terraform directories +**/.terraform/* + +# terraform lock file. +**/.terraform.lock.hcl + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +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 + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# It's a module, shouldn't have a providers.tf +provider*.tf diff --git a/README.md b/README.md new file mode 100644 index 0000000..98d7d5a --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# tfmod-loki + +Installs the loki as the log aggregation sink, and promtail to forward the logs +to loki. + + +# tfmod-loki diff --git a/aws_data.tf b/aws_data.tf new file mode 100644 index 0000000..80f71e7 --- /dev/null +++ b/aws_data.tf @@ -0,0 +1,12 @@ + +data "aws_caller_identity" "current" {} + +data "aws_arn" "current" { + arn = data.aws_caller_identity.current.arn +} + +data "aws_iam_openid_connect_provider" "openid" { + url = data.aws_eks_cluster.cluster.identity[0].oidc[0].issuer +} + + diff --git a/copy_images.tf b/copy_images.tf new file mode 100644 index 0000000..efa3598 --- /dev/null +++ b/copy_images.tf @@ -0,0 +1,65 @@ +locals { + loki_key = format("%v#%v", "grafana/loki", var.loki_tag) + kubectl_key = format("%v#%v", "grafana/kubectl", var.kubectl_image_tag) + provisioner_key = format("%v#%v", "grafana/enterprise-logs-provisioner", var.enterprise_logs_provisioner_tag) + gateway_key = format("%v#%v", "grafana/nginx-unprivileged", var.gateway_tag) + + image_config = [ + { + enabled = true + dest_path = null + name = "grafana/loki" + source_image = "grafana/loki" + source_registry = "docker.io" + source_tag = var.loki_tag + tag = var.loki_tag + }, + { + enabled = true + dest_path = null + name = "grafana/kubectl" + source_image = "bitnami/kubectl" + source_registry = "docker.io" + source_tag = var.kubectl_image_tag + tag = var.kubectl_image_tag + }, + { + enabled = true + dest_path = null + name = "grafana/enterprise-logs-provisioner" + source_image = "grafana/enterprise-logs-provisioner" + source_registry = "docker.io" + source_tag = var.enterprise_logs_provisioner_tag + tag = var.enterprise_logs_provisioner_tag + }, + { + enabled = true + dest_path = null + name = "grafana/nginx-unprivileged" + source_image = "nginxinc/nginx-unprivileged" + source_registry = "docker.io" + source_tag = var.gateway_tag + tag = var.gateway_tag + }, + ] +} + +module "images" { + source = "git@github.e.it.census.gov:terraform-modules/aws-ecr-copy-images.git/?ref=2.0.2" + + profile = var.profile + application_name = var.cluster_name + image_config = local.image_config + tags = {} + + ### optional + ## account_alias = "" + ## account_id = "" + ## destination_password = "" + ## destination_username = "" + ## override_prefixes = {} + ## region = "" + ## source_password = "" + ## source_username = "" +} + diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..09e6b39 --- /dev/null +++ b/main.tf @@ -0,0 +1,289 @@ +resource "kubernetes_namespace" "ns" { + count = var.create_namespace == "true" ? 1 : 0 + + metadata { + name = var.namespace + labels = { + istio-injection = "enabled" + } + } +} + +data "kubernetes_namespace" "existing-ns" { + count = var.create_namespace == "true" ? 0 : 1 + + metadata { + name = var.namespace + } +} + +locals { + ns = try(kubernetes_namespace.ns[0].metadata[0].name, data.kubernetes_namespace.existing-ns[0].metadata[0].name) +} + +resource "helm_release" "loki" { + chart = "loki" + version = var.loki_chart_version + name = "loki" + namespace = local.ns + repository = "https://grafana.github.io/helm-charts" + + set { + name = "kubectlImage.registry" + value = module.images.images[local.kubectl_key].dest_registry + } + set { + name = "kubectlImage.repository" + value = module.images.images[local.kubectl_key].dest_repository + } + set { + name = "kubectlImage.tag" + value = module.images.images[local.kubectl_key].tag + } + + set { + name = "loki.image.registry" + value = module.images.images[local.loki_key].dest_registry + } + set { + name = "loki.image.repository" + value = module.images.images[local.loki_key].dest_repository + } + set { + name = "loki.image.tag" + value = module.images.images[local.loki_key].tag + } + + + set { + name = "table_manager.retention_deletes_enabled" + value = var.table_manager_retention_deletes_enabled + } + set { + name = "table_manager.retention_period" + value = var.table_manager_retention_period + } + + set { + name = "loki.auth_enabled" + value = "true" + } + + set { + name = "loki.limits_config.retention_period" + value = var.table_manager_retention_period + } + set { + name = "loki.limits_config.ingestion_rate_strategy" + value = "local" + } + set { + name = "loki.limits_config.max_global_streams_per_user" + value = "5000" + } + set { + name = "loki.limits_config.max_query_length" + value = var.table_manager_retention_period + } + set { + name = "loki.limits_config.max_query_parallelism" + value = "32" + } + set { + name = "loki.limits_config.max_streams_per_user" + value = "10000" + } + + set { + name = "loki.storage.bucketNames.chunks" + value = module.loki-s3.s3_requested_bucket_name + } + set { + name = "loki.storage.bucketNames.ruler" + value = module.loki-s3.s3_requested_bucket_name + } + set { + name = "loki.storage.bucketNames.admin" + value = module.loki-s3.s3_requested_bucket_name + } + set { + name = "loki.storage.type" + value = "s3" + } + set { + name = "loki.storage.s3.s3" + value = format("s3://%v", var.region) + } + set { + name = "loki.storage.s3.region" + value = var.region + } + + + set { + name = "loki.schemaConfig.configs[0].from" + value = "2023-09-09" + } + set { + name = "loki.schemaConfig.configs[0].index.period" + value = "24h" + } + set { + name = "loki.schemaConfig.configs[0].index.prefix" + value = "loki_sb_index_" + } + set { + name = "loki.schemaConfig.configs[0].object_store" + value = "s3" + } + set { + name = "loki.schemaConfig.configs[0].schema" + value = "v12" + } + set { + name = "loki.schemaConfig.configs[0].store" + value = "boltdb-shipper" + } + + + set { + name = "loki.storage_config.aws.s3" + value = format("s3://%v/%v", + var.region, + module.loki-s3.s3_requested_bucket_name + ) + } + set { + name = "loki.storage_config.boltdb_shipper.active_index_directory" + value = "/loki/index" + } + set { + name = "loki.storage_config.boltdb_shipper.shared_store" + value = "s3" + } + set { + name = "loki.storage_config.boltdb_shipper.cache_location" + value = "/loki/boltdb-cache" + } + + set { + name = "loki.compactor.working_directory" + value = "/loki/compactor" + } + set { + name = "loki.compactor.shared_store" + value = "aws" + } + set { + name = "loki.compactor.compaction_interval" + value = "10m" + } + set { + name = "loki.compactor.retention_enabled" + value = "true" + } + set { + name = "loki.compactor.retention_delete_delay" + value = "2h" + } + set { + name = "loki.compactor.retention_delete_worker_count" + value = "150" + } + + set { + name = "loki.analytics.reporting_enabled" + value = "false" + } + + set { + name = "loki.provisioner.image.registry" + value = module.images.images[local.provisioner_key].dest_registry + } + set { + name = "loki.provisioner.image.repository" + value = module.images.images[local.provisioner_key].dest_repository + } + set { + name = "loki.provisioner.image.tag" + value = module.images.images[local.provisioner_key].tag + } + + set { + name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn" + value = format("r-eks-%v-irsa-loki-sa", var.cluster_name) + } + + set { + name = "test.enabled" + value = "false" + } + set { + name = "monitoring.dashboards.enabled" + value = "false" + } + set { + name = "monitoring.rules.enabled" + value = "false" + } + set { + name = "monitoring.serviceMonitor.enabled" + value = "false" + } + set { + name = "monitoring.selfMonitoring.enabled" + value = "false" + } + set { + name = "monitoring.lokiCanary.enabled" + value = "false" + } + + set { + name = "write.extraVolumesMounts[0].name" + value = "data" + } + set { + name = "write.extraVolumesMounts[0].mountPath" + value = "/loki" + } + set { + name = "write.extraVolumes[0].name" + value = "loki" + } + set { + name = "write.extraVolumes[0].emptyDir" + value = "{}" + } + + set { + name = "write.persistence.storageClass" + value = var.rwo_storage_class + } + set { + name = "backend.persistence.storageClass" + value = var.rwo_storage_class + } + + set { + name = "memberlist.service.publishNotReadyAddresses" + value = "true" + } + + set { + name = "gateway.image.registry" + value = module.images.images[local.gateway_key].dest_registry + } + set { + name = "gateway.image.repository" + value = module.images.images[local.provisioner_key].dest_repository + } + set { + name = "gateway.image.tag" + value = module.images.images[local.provisioner_key].tag + } + # referencing vi /home/z/zawac002/eks-middleware-deployment/charts/loki-scalable/dev-values.yaml + # /home/z/zawac002/eks-middleware-deployment/charts/loki-scalable + # Using diff orig_values.yaml dev-values.yaml | less + # Up through: +} diff --git a/outputs.tf b/outputs.tf new file mode 100644 index 0000000..c105e07 --- /dev/null +++ b/outputs.tf @@ -0,0 +1,5 @@ + +output "private_url" { + description = "The endpoint which can be used inside of the cluster to access loki." + value = format("http://loki.%v.svc.cluster.local", local.ns) +} diff --git a/policies.tf b/policies.tf new file mode 100644 index 0000000..bd0731f --- /dev/null +++ b/policies.tf @@ -0,0 +1,78 @@ +## bucket policy +data "aws_iam_policy_document" "loki-s3-policy" { + statement { + sid = "LokiS3bucketPermissions" + effect = "Allow" + actions = [ + "s3:GetObject*", + "s3:PutObject*", + "s3:List*", + "s3:DeleteObject*", + "s3:PutBucketVersioning", + "s3:GetBucket*", + "s3:AbortMultipartUpload" + ] + resources = [ + "arn:${data.aws_arn.current.partition}:s3:::v-s3-${module.loki-s3.s3_requested_bucket_name}", + "arn:${data.aws_arn.current.partition}:s3:::v-s3-${module.loki-s3.s3_requested_bucket_name}/*" + ] + } +} + +resource "aws_iam_policy" "loki-s3-policy" { + name = trim(format("%v-loki-s3-policy", var.cluster_name), "adsd-") + description = "Policy for Loki S3 bucket." + + policy = data.aws_iam_policy_document.loki-s3-policy.json +} + +## kms policy +data "aws_iam_policy_document" "loki-kms-policy" { + statement { + sid = "AllowLokiRoleKMSActions1" + effect = "Allow" + actions = [ + "kms:GenerateDataKey", + "kms:Decrypt", + "kms:Encrypt", + "kms:DescribeKey", + "kms:ReEncrypt*" + ] + resources = [ + "${module.loki-s3.kms_key_arn}" + ] + } +} + +resource "aws_iam_policy" "loki-kms-policy" { + name = trim(format("%v-loki-kms-policy", var.cluster_name), "adsd-") + description = "KMS Policy for Loki S3 bucket." + + policy = data.aws_iam_policy_document.loki-kms-policy.json +} + + +## loki role +data "aws_iam_policy_document" "loki-assume" { + statement { + actions = ["sts:AssumeRoleWithWebIdentity"] + + principals { + type = "Federated" + identifiers = [ + "${data.aws_iam_openid_connect_provider.openid.arn}", + ] + } + + condition { + test = "StringEquals" + variable = "${data.aws_iam_openid_connect_provider.openid.url}:sub" + values = [ + "system:serviceaccount:${var.namespace}:loki" + ] + } + + effect = "Allow" + } +} + diff --git a/requirements.tf b/requirements.tf new file mode 100644 index 0000000..1b7a5da --- /dev/null +++ b/requirements.tf @@ -0,0 +1,26 @@ +terraform { + required_version = ">= 0.13" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.14.0" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.11.0" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.23.0" + } + null = { + source = "hashicorp/null" + version = ">= 3.2.1" + } + template = { + source = "hashicorp/template" + version = ">= 2.2.0" + } + } +} diff --git a/roles.tf b/roles.tf new file mode 100644 index 0000000..7cd27d3 --- /dev/null +++ b/roles.tf @@ -0,0 +1,19 @@ +## loki role +resource "aws_iam_role" "loki-role" { + name = format("r-eks-%v-irsa-loki-sa", var.cluster_name) + + assume_role_policy = data.aws_iam_policy_document.loki-assume.json +} + +## attach policies to role + +resource "aws_iam_role_policy_attachment" "loki-role-s3-attachment" { + policy_arn = aws_iam_policy.loki-s3-policy.arn + role = aws_iam_role.loki-role.name +} + +resource "aws_iam_role_policy_attachment" "loki-role-kms-attachment" { + policy_arn = aws_iam_policy.loki-kms-policy.arn + role = aws_iam_role.loki-role.name +} + diff --git a/s3.tf b/s3.tf new file mode 100644 index 0000000..9a7eb15 --- /dev/null +++ b/s3.tf @@ -0,0 +1,15 @@ +## create bucket +locals { + account_id = data.aws_caller_identity.current.account_id +} + +data "aws_s3_bucket" "s3_server_access_logs" { + bucket = format("inf-logs-%v-%v", local.account_id, var.region) +} + +module "loki-s3" { + source = "git@github.e.it.census.gov:terraform-modules/aws-s3.git//standard?ref=tf-upgrade" + + bucket_name = format("%v-loki", var.cluster_name) + access_log_bucket = data.aws_s3_bucket.s3_server_access_logs.id +} diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..e196574 --- /dev/null +++ b/variables.tf @@ -0,0 +1,78 @@ +variable "region" { + description = "The region holding these resources (for the s3 bucket.)" + type = string +} + +variable "cluster_name" { + description = "EKS cluster name name component used through out the EKS cluster describing its purpose (ex: dice-dev)" + type = string +} + +variable "profile" { + description = "AWS config profile used to upload images into ECR" + type = string + default = "" +} + +variable "namespace" { + description = "The namespace into which grafana will be deployed" + type = string + default = "loki" +} + +variable "create_namespace" { + description = "Indicates whether the `namespace` needs to be created ('true') or already exists (not `true`)" + type = string + default = "true" +} + +variable "rwo_storage_class" { + description = "Specify the storage class for read/write/once persistent volumes." + type = string + default = "gp3" +} + +variable "table_manager_retention_deletes_enabled" { + description = "" + type = string + default = "false" +} + +variable "table_manager_retention_period" { + description = "Loki defaults to 0" + type = string + default = "2160h" +} + +# helm add repo grafana "https://grafana.github.io/helm-charts" +# helm search repo grafana/loki +variable "loki_chart_version" { + description = "Which version of the grafana/loki helm chart to use." + type = string + default = "5.15.0" +} + +# The [APP VERSION] associated with the helm chart. +variable "loki_tag" { + description = "The tag of the loki image to use." + type = string + default = "2.8.4" +} + +variable "kubectl_image_tag" { + description = "The version of bitnami/kubectl image to use." + type = string + default = "1.27.1" +} + +variable "enterprise_logs_provisioner_tag" { + description = "The version of the grafana/enterprise-logs-provisioner image to use." + type = string + default = "v1.7.0" +} + +variable "gateway_tag" { + description = "The version of nginxinc/nginx-unprivileged to use for the gateway." + type = string + default = "1.19-alpine" +}