From 19f336baa793e878b66e185c2fc2b75fed1757b0 Mon Sep 17 00:00:00 2001 From: Olamide Date: Wed, 25 Oct 2023 15:42:44 +0100 Subject: [PATCH] Add helm chart to install AWS Cloudwatch adapter module --- aws/platform/main.tf | 24 +++ .../README.md | 44 ++++ .../main.tf | 28 +++ .../makefile | 67 ++++++ .../outputs.tf | 9 + .../variables.tf | 21 ++ .../versions.tf | 9 + aws/platform/variables.tf | 6 + platform/main.tf | 7 + platform/modules/cloudwatch-adapter/README.md | 29 +++ .../cloudwatch-adapter/chart/Chart.yaml | 9 + .../chart/templates/manifest.yaml | 204 ++++++++++++++++++ .../cloudwatch-adapter/chart/values.yaml | 5 + platform/modules/cloudwatch-adapter/main.tf | 7 + platform/modules/cloudwatch-adapter/makefile | 67 ++++++ .../modules/cloudwatch-adapter/outputs.tf | 0 .../modules/cloudwatch-adapter/variables.tf | 16 ++ .../modules/cloudwatch-adapter/versions.tf | 9 + platform/variables.tf | 6 + 19 files changed, 567 insertions(+) create mode 100644 aws/platform/modules/cloudwatch-adapter-service-account-role/README.md create mode 100644 aws/platform/modules/cloudwatch-adapter-service-account-role/main.tf create mode 100644 aws/platform/modules/cloudwatch-adapter-service-account-role/makefile create mode 100644 aws/platform/modules/cloudwatch-adapter-service-account-role/outputs.tf create mode 100644 aws/platform/modules/cloudwatch-adapter-service-account-role/variables.tf create mode 100644 aws/platform/modules/cloudwatch-adapter-service-account-role/versions.tf create mode 100644 platform/modules/cloudwatch-adapter/README.md create mode 100644 platform/modules/cloudwatch-adapter/chart/Chart.yaml create mode 100644 platform/modules/cloudwatch-adapter/chart/templates/manifest.yaml create mode 100644 platform/modules/cloudwatch-adapter/chart/values.yaml create mode 100644 platform/modules/cloudwatch-adapter/main.tf create mode 100644 platform/modules/cloudwatch-adapter/makefile create mode 100644 platform/modules/cloudwatch-adapter/outputs.tf create mode 100644 platform/modules/cloudwatch-adapter/variables.tf create mode 100644 platform/modules/cloudwatch-adapter/versions.tf diff --git a/aws/platform/main.tf b/aws/platform/main.tf index 254d96b9..70669747 100644 --- a/aws/platform/main.tf +++ b/aws/platform/main.tf @@ -24,6 +24,11 @@ module "common_platform" { var.cert_manager_values ) + cloudwatch_adapter_values = concat( + local.cloudwatch_adapter_values, + var.cloudwatch_adapter_values + ) + cluster_autoscaler_values = concat( local.cluster_autoscaler_values, var.cluster_autoscaler_values @@ -128,6 +133,15 @@ module "cloudwatch_logs" { skip_destroy = var.logs_skip_destroy } +module "cloudwatch_adapter_service_account_role" { + source = "./modules/cloudwatch-adapter-service-account-role" + + aws_namespace = [module.cluster_name.full] + aws_tags = var.aws_tags + k8s_namespace = var.k8s_namespace + oidc_issuer = data.aws_ssm_parameter.oidc_issuer.value +} + module "cluster_autoscaler_service_account_role" { source = "./modules/cluster-autoscaler-service-account-role" @@ -251,6 +265,16 @@ locals { }) ] + cloudwatch_adapter_values = [ + yamlencode({ + serviceAccount = { + annotations = { + "eks.amazonaws.com/role-arn" = module.cloudwatch_adapter_service_account_role.arn + } + } + }) + ] + cluster_autoscaler_values = [ yamlencode({ autoDiscovery = { diff --git a/aws/platform/modules/cloudwatch-adapter-service-account-role/README.md b/aws/platform/modules/cloudwatch-adapter-service-account-role/README.md new file mode 100644 index 00000000..ab2ac79f --- /dev/null +++ b/aws/platform/modules/cloudwatch-adapter-service-account-role/README.md @@ -0,0 +1,44 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.14.8 | +| [aws](#requirement\_aws) | ~> 4.0 | + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | ~> 4.0 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [cluster\_autoscaler\_service\_account\_role](#module\_cluster\_autoscaler\_service\_account\_role) | ../../../service-account-role | n/a | + +## Resources + +| Name | Type | +|------|------| +| [aws_iam_policy.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_policy) | resource | +| [aws_iam_role_policy_attachment.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/iam_role_policy_attachment) | resource | +| [aws_iam_policy_document.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [aws\_namespace](#input\_aws\_namespace) | Prefix to be applied to created AWS resources | `list(string)` | `[]` | no | +| [aws\_tags](#input\_aws\_tags) | Tags to be applied to created AWS resources | `map(string)` | `{}` | no | +| [k8s\_namespace](#input\_k8s\_namespace) | Kubernetes namespace in which resources should be created | `string` | n/a | yes | +| [oidc\_issuer](#input\_oidc\_issuer) | OIDC issuer of the Kubernetes cluster | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| [arn](#output\_arn) | The ARN of the created role | +| [service\_account\_role\_arn](#output\_service\_account\_role\_arn) | ARN of the AWS IAM role created for service accounts | + \ No newline at end of file diff --git a/aws/platform/modules/cloudwatch-adapter-service-account-role/main.tf b/aws/platform/modules/cloudwatch-adapter-service-account-role/main.tf new file mode 100644 index 00000000..c05c594d --- /dev/null +++ b/aws/platform/modules/cloudwatch-adapter-service-account-role/main.tf @@ -0,0 +1,28 @@ +module "cloudwatch_adapter_service_account_role" { + source = "../../../service-account-role" + + name = "cloudwatch-adapter" + namespace = var.aws_namespace + oidc_issuers = [var.oidc_issuer] + service_accounts = ["${var.k8s_namespace}:cloudwatch-adapter"] + tags = var.aws_tags +} + +resource "aws_iam_policy" "this" { + name = module.cloudwatch_adapter_service_account_role.name + policy = data.aws_iam_policy_document.this.json +} + +resource "aws_iam_role_policy_attachment" "this" { + role = module.cloudwatch_adapter_service_account_role.name + policy_arn = aws_iam_policy.this.arn +} + +data "aws_iam_policy_document" "this" { + statement { + actions = [ + "cloudwatch:GetMetricData" + ] + resources = ["*"] + } +} diff --git a/aws/platform/modules/cloudwatch-adapter-service-account-role/makefile b/aws/platform/modules/cloudwatch-adapter-service-account-role/makefile new file mode 100644 index 00000000..364a9e25 --- /dev/null +++ b/aws/platform/modules/cloudwatch-adapter-service-account-role/makefile @@ -0,0 +1,67 @@ +MODULEFILES := $(wildcard *.tf) +TFLINTRC ?= ../../.tflint.hcl +TFDOCSRC ?= ../../.terraform-docs.yml + +.PHONY: default +default: checkfmt validate docs lint + +.PHONY: checkfmt +checkfmt: .fmt + +.PHONY: fmt +fmt: $(MODULEFILES) + terraform fmt + @touch .fmt + +.PHONY: validate +validate: .validate + +.PHONY: docs +docs: README.md + +.PHONY: lint +lint: .lint + +.lint: $(MODULEFILES) .lintinit + tflint --config=$(TFLINTRC) + @touch .lint + +.lintinit: $(TFLINTRC) + tflint --init --config=$(TFLINTRC) --module + @touch .lintinit + +README.md: $(MODULEFILES) + terraform-docs --config "$(TFDOCSRC)" markdown table . --output-file README.md + +.fmt: $(MODULEFILES) + terraform fmt -check + @touch .fmt + +.PHONY: init +init: .init + +.init: versions.tf .dependencies + terraform init -backend=false + @touch .init + +.validate: .init $(MODULEFILES) $(wildcard *.tf.example) + echo | cat - $(wildcard *.tf.example) > test.tf + if AWS_DEFAULT_REGION=us-east-1 terraform validate; then \ + rm test.tf; \ + touch .validate; \ + else \ + rm test.tf; \ + false; \ + fi + +.dependencies: *.tf + @grep -ohE \ + "\b(backend|provider|resource|module) ['\"][[:alpha:]][[:alnum:]]*|\bsource *=.*" *.tf | \ + sed "s/['\"]//" | sort | uniq | \ + tee /tmp/initdeps | \ + diff -q .dependencies - >/dev/null 2>&1 || \ + mv /tmp/initdeps .dependencies + +.PHONY: clean +clean: + rm -rf .dependencies .fmt .init .lint .lintinit .terraform* .validate diff --git a/aws/platform/modules/cloudwatch-adapter-service-account-role/outputs.tf b/aws/platform/modules/cloudwatch-adapter-service-account-role/outputs.tf new file mode 100644 index 00000000..d778c65f --- /dev/null +++ b/aws/platform/modules/cloudwatch-adapter-service-account-role/outputs.tf @@ -0,0 +1,9 @@ +output "arn" { + description = "The ARN of the created role" + value = module.cloudwatch_adapter_service_account_role.instance.arn +} + +output "service_account_role_arn" { + description = "ARN of the AWS IAM role created for service accounts" + value = module.cloudwatch_adapter_service_account_role.instance.arn +} diff --git a/aws/platform/modules/cloudwatch-adapter-service-account-role/variables.tf b/aws/platform/modules/cloudwatch-adapter-service-account-role/variables.tf new file mode 100644 index 00000000..e0b085c9 --- /dev/null +++ b/aws/platform/modules/cloudwatch-adapter-service-account-role/variables.tf @@ -0,0 +1,21 @@ +variable "aws_namespace" { + type = list(string) + default = [] + description = "Prefix to be applied to created AWS resources" +} + +variable "aws_tags" { + type = map(string) + description = "Tags to be applied to created AWS resources" + default = {} +} + +variable "k8s_namespace" { + type = string + description = "Kubernetes namespace in which resources should be created" +} + +variable "oidc_issuer" { + type = string + description = "OIDC issuer of the Kubernetes cluster" +} diff --git a/aws/platform/modules/cloudwatch-adapter-service-account-role/versions.tf b/aws/platform/modules/cloudwatch-adapter-service-account-role/versions.tf new file mode 100644 index 00000000..020f4777 --- /dev/null +++ b/aws/platform/modules/cloudwatch-adapter-service-account-role/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.14.8" + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} diff --git a/aws/platform/variables.tf b/aws/platform/variables.tf index 0c7ae6eb..5e6c4279 100644 --- a/aws/platform/variables.tf +++ b/aws/platform/variables.tf @@ -51,6 +51,12 @@ variable "certificate_issuer" { default = null } +variable "cloudwatch_adapter_values" { + description = "Overrides to pass to the Helm chart" + type = list(string) + default = [] +} + variable "cluster_autoscaler_values" { description = "Overrides to pass to the Helm chart" type = list(string) diff --git a/platform/main.tf b/platform/main.tf index 5f17b1ae..794bf878 100644 --- a/platform/main.tf +++ b/platform/main.tf @@ -60,6 +60,13 @@ module "cert_manager" { k8s_namespace = local.flightdeck_namespace } +module "cloudwatch_adapter" { + source = "./modules/cloudwatch-adapter" + + chart_values = var.cloudwatch_adapter_values + k8s_namespace = local.flightdeck_namespace +} + module "cluster_autoscaler" { source = "./modules/cluster-autoscaler" diff --git a/platform/modules/cloudwatch-adapter/README.md b/platform/modules/cloudwatch-adapter/README.md new file mode 100644 index 00000000..5b1b30e7 --- /dev/null +++ b/platform/modules/cloudwatch-adapter/README.md @@ -0,0 +1,29 @@ + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 0.14.8 | +| [helm](#requirement\_helm) | ~> 2.4 | + +## Providers + +| Name | Version | +|------|---------| +| [helm](#provider\_helm) | ~> 2.4 | + +## Resources + +| Name | Type | +|------|------| +| [helm_release.ingress_config](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [domain\_names](#input\_domain\_names) | Domains which are allowed in this cluster | `list(string)` | `[]` | no | +| [issuer](#input\_issuer) | YAML spec for the cert-manager issuer | `string` | `null` | no | +| [k8s\_namespace](#input\_k8s\_namespace) | Kubernetes namespace in which secrets should be created | `string` | n/a | yes | +| [name](#input\_name) | Name for the Helm release | `string` | `"ingress-config"` | no | + \ No newline at end of file diff --git a/platform/modules/cloudwatch-adapter/chart/Chart.yaml b/platform/modules/cloudwatch-adapter/chart/Chart.yaml new file mode 100644 index 00000000..37282ea6 --- /dev/null +++ b/platform/modules/cloudwatch-adapter/chart/Chart.yaml @@ -0,0 +1,9 @@ +apiVersion: v2 +name: cloudwatch-adapter +description: Configuration for cloudwatch adapter to fetch AWS Cloudwatch metrics in kubernetes + +type: application + +version: 0.1.0 + +appVersion: 1.0.0 diff --git a/platform/modules/cloudwatch-adapter/chart/templates/manifest.yaml b/platform/modules/cloudwatch-adapter/chart/templates/manifest.yaml new file mode 100644 index 00000000..6c37f99e --- /dev/null +++ b/platform/modules/cloudwatch-adapter/chart/templates/manifest.yaml @@ -0,0 +1,204 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cloudwatch-adapter:system:auth-delegator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: system:auth-delegator +subjects: +- kind: ServiceAccount + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: cloudwatch-adapter-auth-reader + namespace: kube-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: extension-apiserver-authentication-reader +subjects: +- kind: ServiceAccount + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: cloudwatch-adapter + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} +spec: + replicas: 1 + selector: + matchLabels: + app: cloudwatch-adapter + template: + metadata: + labels: + app: cloudwatch-adapter + name: cloudwatch-adapter + spec: + serviceAccountName: cloudwatch-adapter + securityContext: + fsGroup: 65534 + containers: + - name: cloudwatch-adapter + image: chankh/k8s-cloudwatch-adapter:v0.10.0 + args: + - /adapter + - --cert-dir=/tmp + - --secure-port=6443 + - --logtostderr=true + - --v=2 + ports: + - containerPort: 6443 + name: https + - containerPort: 8080 + name: http + volumeMounts: + - mountPath: /tmp + name: temp-vol + volumes: + - name: temp-vol + emptyDir: {} +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cloudwatch-adapter-resource-reader +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cloudwatch-adapter-resource-reader +subjects: +- kind: ServiceAccount + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{ toYaml . | nindent 4 | trim }} + {{- end }} +--- +apiVersion: v1 +kind: Service +metadata: + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} +spec: + ports: + - name: https + port: 443 + targetPort: 6443 + - name: http + port: 80 + targetPort: 8080 + selector: + app: cloudwatch-adapter +--- +apiVersion: apiregistration.k8s.io/v1 +kind: APIService +metadata: + name: v1beta1.external.metrics.k8s.io +spec: + service: + name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} + group: external.metrics.k8s.io + version: v1beta1 + insecureSkipTLSVerify: true + groupPriorityMinimum: 100 + versionPriority: 100 +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cloudwatch-adapter:external-metrics-reader +rules: +- apiGroups: + - external.metrics.k8s.io + resources: ["*"] + verbs: ["*"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cloudwatch-adapter-resource-reader +rules: +- apiGroups: + - "" + resources: + - namespaces + - pods + - services + - configmaps + verbs: + - get + - list +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cloudwatch-adapter:external-metrics-reader +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cloudwatch-adapter:external-metrics-reader +subjects: +- kind: ServiceAccount + name: horizontal-pod-autoscaler + namespace: kube-system +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: externalmetrics.metrics.aws +spec: + group: metrics.aws + version: v1alpha1 + names: + kind: ExternalMetric + plural: externalmetrics + singular: externalmetric + scope: Namespaced +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: cloudwatch-adapter:crd-metrics-reader + labels: + app: cloudwatch-adapter +rules: +- apiGroups: + - metrics.aws + resources: + - "externalmetrics" + verbs: + - list + - get + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: cloudwatch-adapter:crd-metrics-reader + labels: + app: cloudwatch-adapter +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: cloudwatch-adapter:crd-metrics-reader +subjects: + - name: cloudwatch-adapter + namespace: {{ .Release.Namespace }} + kind: ServiceAccount diff --git a/platform/modules/cloudwatch-adapter/chart/values.yaml b/platform/modules/cloudwatch-adapter/chart/values.yaml new file mode 100644 index 00000000..cfb24e53 --- /dev/null +++ b/platform/modules/cloudwatch-adapter/chart/values.yaml @@ -0,0 +1,5 @@ +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + annotations: {} \ No newline at end of file diff --git a/platform/modules/cloudwatch-adapter/main.tf b/platform/modules/cloudwatch-adapter/main.tf new file mode 100644 index 00000000..aebe3268 --- /dev/null +++ b/platform/modules/cloudwatch-adapter/main.tf @@ -0,0 +1,7 @@ +resource "helm_release" "cloudwatch_adapter" { + chart = "${path.module}/chart" + name = var.name + namespace = var.k8s_namespace + values = var.chart_values +} + diff --git a/platform/modules/cloudwatch-adapter/makefile b/platform/modules/cloudwatch-adapter/makefile new file mode 100644 index 00000000..364a9e25 --- /dev/null +++ b/platform/modules/cloudwatch-adapter/makefile @@ -0,0 +1,67 @@ +MODULEFILES := $(wildcard *.tf) +TFLINTRC ?= ../../.tflint.hcl +TFDOCSRC ?= ../../.terraform-docs.yml + +.PHONY: default +default: checkfmt validate docs lint + +.PHONY: checkfmt +checkfmt: .fmt + +.PHONY: fmt +fmt: $(MODULEFILES) + terraform fmt + @touch .fmt + +.PHONY: validate +validate: .validate + +.PHONY: docs +docs: README.md + +.PHONY: lint +lint: .lint + +.lint: $(MODULEFILES) .lintinit + tflint --config=$(TFLINTRC) + @touch .lint + +.lintinit: $(TFLINTRC) + tflint --init --config=$(TFLINTRC) --module + @touch .lintinit + +README.md: $(MODULEFILES) + terraform-docs --config "$(TFDOCSRC)" markdown table . --output-file README.md + +.fmt: $(MODULEFILES) + terraform fmt -check + @touch .fmt + +.PHONY: init +init: .init + +.init: versions.tf .dependencies + terraform init -backend=false + @touch .init + +.validate: .init $(MODULEFILES) $(wildcard *.tf.example) + echo | cat - $(wildcard *.tf.example) > test.tf + if AWS_DEFAULT_REGION=us-east-1 terraform validate; then \ + rm test.tf; \ + touch .validate; \ + else \ + rm test.tf; \ + false; \ + fi + +.dependencies: *.tf + @grep -ohE \ + "\b(backend|provider|resource|module) ['\"][[:alpha:]][[:alnum:]]*|\bsource *=.*" *.tf | \ + sed "s/['\"]//" | sort | uniq | \ + tee /tmp/initdeps | \ + diff -q .dependencies - >/dev/null 2>&1 || \ + mv /tmp/initdeps .dependencies + +.PHONY: clean +clean: + rm -rf .dependencies .fmt .init .lint .lintinit .terraform* .validate diff --git a/platform/modules/cloudwatch-adapter/outputs.tf b/platform/modules/cloudwatch-adapter/outputs.tf new file mode 100644 index 00000000..e69de29b diff --git a/platform/modules/cloudwatch-adapter/variables.tf b/platform/modules/cloudwatch-adapter/variables.tf new file mode 100644 index 00000000..8995a498 --- /dev/null +++ b/platform/modules/cloudwatch-adapter/variables.tf @@ -0,0 +1,16 @@ +variable "chart_values" { + description = "Overrides to pass to the Helm chart" + type = list(string) + default = [] +} + +variable "k8s_namespace" { + type = string + description = "Kubernetes namespace in which secrets should be created" +} + +variable "name" { + description = "Name for the Helm release" + type = string + default = "cloudwatch-adapter" +} diff --git a/platform/modules/cloudwatch-adapter/versions.tf b/platform/modules/cloudwatch-adapter/versions.tf new file mode 100644 index 00000000..0ed6889f --- /dev/null +++ b/platform/modules/cloudwatch-adapter/versions.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 0.14.8" + required_providers { + helm = { + source = "hashicorp/helm" + version = "~> 2.4" + } + } +} diff --git a/platform/variables.tf b/platform/variables.tf index 3243ce56..e8dc621e 100644 --- a/platform/variables.tf +++ b/platform/variables.tf @@ -16,6 +16,12 @@ variable "certificate_issuer" { default = null } +variable "cloudwatch_adapter_values" { + description = "Overrides to pass to the Helm chart" + type = list(string) + default = [] +} + variable "cluster_autoscaler_values" { description = "Overrides to pass to the Helm chart" type = list(string)