diff --git a/README.md b/README.md index 5c97cd4..f371fd9 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ worker_groups = { | [eks-cluster](#module\_eks-cluster) | ./modules/eks | n/a | | [external-dns](#module\_external-dns) | ./modules/external-dns | n/a | | [external-secrets](#module\_external-secrets) | ./modules/external-secrets | n/a | +| [flagger](#module\_flagger) | ./modules/flagger | n/a | | [fluent-bit](#module\_fluent-bit) | ./modules/fluent-bit | n/a | | [metrics-server](#module\_metrics-server) | ./modules/metrics-server | n/a | | [nginx-ingress-controller](#module\_nginx-ingress-controller) | ./modules/nginx-ingress-controller/ | n/a | @@ -260,9 +261,11 @@ worker_groups = { | [ebs\_csi\_version](#input\_ebs\_csi\_version) | EBS CSI driver addon version | `string` | `"v1.15.0-eksbuild.1"` | no | | [efs\_id](#input\_efs\_id) | EFS filesystem id in AWS | `string` | `null` | no | | [efs\_storage\_classes](#input\_efs\_storage\_classes) | Additional storage class configurations: by default, 2 storage classes are created - efs-sc and efs-sc-root which has 0 uid. One can add another storage classes besides these 2. |
list(object({
name : string
provisioning_mode : optional(string, "efs-ap")
file_system_id : string
directory_perms : optional(string, "755")
base_path : optional(string, "/")
uid : optional(number)
}))
| `[]` | no | +| [enable\_alb\_ingress\_controller](#input\_enable\_alb\_ingress\_controller) | Whether alb ingress controller enabled. | `bool` | `true` | no | | [enable\_api\_gw\_controller](#input\_enable\_api\_gw\_controller) | Weather enable API-GW controller or not | `bool` | `false` | no | | [enable\_ebs\_driver](#input\_enable\_ebs\_driver) | Weather enable EBS-CSI driver or not | `bool` | `true` | no | | [enable\_efs\_driver](#input\_enable\_efs\_driver) | Weather install EFS driver or not in EKS | `bool` | `false` | no | +| [enable\_external\_secrets](#input\_enable\_external\_secrets) | Whether to enable external-secrets operator | `bool` | `true` | no | | [enable\_kube\_state\_metrics](#input\_enable\_kube\_state\_metrics) | Enable kube-state-metrics | `bool` | `false` | no | | [enable\_metrics\_server](#input\_enable\_metrics\_server) | METRICS-SERVER | `bool` | `false` | no | | [enable\_node\_problem\_detector](#input\_enable\_node\_problem\_detector) | n/a | `bool` | `true` | no | @@ -272,6 +275,7 @@ worker_groups = { | [enable\_waf\_for\_alb](#input\_enable\_waf\_for\_alb) | Enables WAF and WAF V2 addons for ALB | `bool` | `false` | no | | [external\_dns](#input\_external\_dns) | Allows to install external-dns helm chart and related roles, which allows to automatically create R53 records based on ingress/service domain/host configs |
object({
enabled = optional(bool, false)
configs = optional(any, {})
})
|
{
"enabled": false
}
| no | | [external\_secrets\_namespace](#input\_external\_secrets\_namespace) | The namespace of external-secret operator | `string` | `"kube-system"` | no | +| [flagger](#input\_flagger) | Allows to create/deploy flagger operator to have custom rollout strategies like canary/blue-green and also it allows to create custom flagger metric templates |
object({
enabled = optional(bool, false)
namespace = optional(string, "ingress-nginx") # The flagger operator helm being installed on same namespace as mesh/ingress provider so this field need to be set based on which ingress/mesh we are going to use, more info in https://artifacthub.io/packages/helm/flagger/flagger
configs = optional(any, {}) # available options can be found in https://artifacthub.io/packages/helm/flagger/flagger
metric_template_configs = optional(any, {}) # available options can be found in https://github.com/dasmeta/helm/tree/flagger-metric-template-0.1.0/charts/flagger-metric-template
enable_metric_template = optional(bool, false)
enable_loadtester = optional(bool, false)
})
|
{
"enabled": false
}
| no | | [fluent\_bit\_configs](#input\_fluent\_bit\_configs) | Fluent Bit configs |
object({
enabled = optional(string, true)
fluent_bit_name = optional(string, "")
log_group_name = optional(string, "")
system_log_group_name = optional(string, "")
log_retention_days = optional(number, 90)
values_yaml = optional(string, "")
configs = optional(object({
inputs = optional(string, "")
filters = optional(string, "")
outputs = optional(string, "")
cloudwatch_outputs_enabled = optional(bool, true)
}), {})
drop_namespaces = optional(list(string), [])
log_filters = optional(list(string), [])
additional_log_filters = optional(list(string), [])
kube_namespaces = optional(list(string), [])
image_pull_secrets = optional(list(string), [])
})
|
{
"additional_log_filters": [
"ELB-HealthChecker",
"Amazon-Route53-Health-Check-Service"
],
"configs": {
"cloudwatch_outputs_enabled": true,
"filters": "",
"inputs": "",
"outputs": ""
},
"drop_namespaces": [
"kube-system",
"opentelemetry-operator-system",
"adot",
"cert-manager",
"opentelemetry.*",
"meta.*"
],
"enabled": true,
"fluent_bit_name": "",
"image_pull_secrets": [],
"kube_namespaces": [
"kube.*",
"meta.*",
"adot.*",
"devops.*",
"cert-manager.*",
"git.*",
"opentelemetry.*",
"stakater.*",
"renovate.*"
],
"log_filters": [
"kube-probe",
"health",
"prometheus",
"liveness"
],
"log_group_name": "",
"log_retention_days": 90,
"system_log_group_name": "",
"values_yaml": ""
}
| no | | [manage\_aws\_auth](#input\_manage\_aws\_auth) | n/a | `bool` | `true` | no | | [map\_roles](#input\_map\_roles) | Additional IAM roles to add to the aws-auth configmap. |
list(object({
rolearn = string
username = string
groups = list(string)
}))
| `[]` | no | diff --git a/alb-ingress-controller.tf b/alb-ingress-controller.tf index d2eb28b..f7f7fdd 100644 --- a/alb-ingress-controller.tf +++ b/alb-ingress-controller.tf @@ -1,7 +1,7 @@ module "alb-ingress-controller" { source = "./modules/aws-load-balancer-controller" - count = var.create ? 1 : 0 + count = var.create && var.enable_alb_ingress_controller ? 1 : 0 account_id = local.account_id region = local.region diff --git a/examples/eks-with-flagger/0-setup.tf b/examples/eks-with-flagger/0-setup.tf new file mode 100644 index 0000000..932fd03 --- /dev/null +++ b/examples/eks-with-flagger/0-setup.tf @@ -0,0 +1,22 @@ +provider "aws" { + region = "eu-central-1" +} + +provider "helm" { + kubernetes { + host = module.this.cluster_host + cluster_ca_certificate = module.this.cluster_certificate + token = module.this.cluster_token + } +} + +# Prepare for test +data "aws_availability_zones" "available" {} +data "aws_vpcs" "ids" { + tags = { + Name = "default" + } +} +data "aws_subnet_ids" "subnets" { + vpc_id = data.aws_vpcs.ids.ids[0] +} diff --git a/examples/eks-with-flagger/1-example.tf b/examples/eks-with-flagger/1-example.tf new file mode 100644 index 0000000..3cdc6c0 --- /dev/null +++ b/examples/eks-with-flagger/1-example.tf @@ -0,0 +1,81 @@ +module "this" { + source = "../.." + + cluster_name = "test-cluster-with-flagger" + + vpc = { + link = { + id = data.aws_vpcs.ids.ids[0] + private_subnet_ids = data.aws_subnet_ids.subnets.ids + } + } + + node_groups = { + "default" : { + "desired_size" : 1, + "max_capacity" : 1, + "max_size" : 1, + "min_size" : 1 + } + + } + node_groups_default = { + "capacity_type" : "SPOT", + "instance_types" : ["t3.medium"] + } + + alarms = { + enabled = false + sns_topic = "" + } + enable_ebs_driver = false + enable_external_secrets = false + create_cert_manager = false + enable_alb_ingress_controller = false + enable_node_problem_detector = false + metrics_exporter = "disabled" + fluent_bit_configs = { + enabled = false + } + + nginx_ingress_controller_config = { + enabled = true + name = "nginx" + create_namespace = true + namespace = "ingress-nginx" + replicacount = 1 + metrics_enabled = true + } + + external_dns = { + enabled = true + configs = { + configs = { sources = ["service", "ingress"] } + } + } + + flagger = { + enabled = true + namespace = "ingress-nginx" + enable_loadtester = true + configs = { + meshProvider = "nginx" + prometheus = { + install = true + } + } + } +} + +resource "helm_release" "http_echo" { + name = "http-echo" + repository = "https://dasmeta.github.io/helm" + chart = "base" + namespace = "default" + version = "0.2.7" + wait = true + + values = [file("${path.module}/http-echo-canary-eks.yaml")] + + depends_on = [module.this] +} diff --git a/examples/eks-with-flagger/README.md b/examples/eks-with-flagger/README.md new file mode 100644 index 0000000..bd0c3cd --- /dev/null +++ b/examples/eks-with-flagger/README.md @@ -0,0 +1,37 @@ +# eks-with-flagger + + +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [aws](#provider\_aws) | 4.67.0 | +| [helm](#provider\_helm) | 2.16.1 | + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../.. | n/a | + +## Resources + +| Name | Type | +|------|------| +| [helm_release.http_echo](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [aws_availability_zones.available](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/availability_zones) | data source | +| [aws_subnet_ids.subnets](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/subnet_ids) | data source | +| [aws_vpcs.ids](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/vpcs) | data source | + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/examples/eks-with-flagger/http-echo-canary-eks.yaml b/examples/eks-with-flagger/http-echo-canary-eks.yaml new file mode 100644 index 0000000..054780b --- /dev/null +++ b/examples/eks-with-flagger/http-echo-canary-eks.yaml @@ -0,0 +1,82 @@ +image: + repository: mendhak/http-https-echo + tag: 34 + +containerPort: 8080 + +service: + enabled: true + type: ClusterIP + +autoscaling: + enabled: true + minReplicas: 1 + maxReplicas: 2 + targetCPUUtilizationPercentage: 99 + +readinessProbe: + initialDelaySeconds: 5 + failureThreshold: 1 + httpGet: + path: /health + port: http +livenessProbe: + initialDelaySeconds: 5 + failureThreshold: 3 + httpGet: + path: /health + port: http +resources: + requests: + cpu: 6m + +ingress: + enabled: true + class: nginx + hosts: + - host: http-echo.devops.dasmeta.com + paths: + - path: "/ping" + backend: + serviceName: http-echo + servicePort: 80 + +rolloutStrategy: + enabled: true + operator: flagger + configs: + progressDeadlineSeconds: 60 # the maximum time in seconds for the canary deployment to make progress before it is rollback (default 600s) + canaryReadyThreshold: 51 # minimum percentage of canary pods that must be ready before considering canary ready for traffic shifting (default 100) + primaryReadyThreshold: 51 # minimum percentage of primary pods that must be ready before considering primary ready for traffic shifting (default 100) + interval: 11s # schedule interval (default 60s) + threshold: 11 # max number of failed metric checks before rollback (default 10) + maxWeight: 31 # max traffic percentage (0-100) routed to canary (default 30) + stepWeight: 11 # canary increment step percentage (0-100) (default 10) + # min and max replicas count for primary hpa, default to main app hpa, the main app hpa values also being used for canary deploy hpa so we use this options to have custom values for primary hpa + primaryScalerMinReplicas: 2 + primaryScalerMaxReplicas: 5 + metrics: # metrics template configs to use for identifying if canary deploy handles request normally, the `request-success-rate` and `request-duration` named ones are available by default, and you can create custom metric templates + - name: request-success-rate + # minimum req success rate (non 5xx responses) percentage (0-100) + thresholdRange: + min: 99 + interval: 1m + - name: request-duration + # maximum req duration P99, milliseconds + thresholdRange: + max: 500 + interval: 1m + + webhooks: # webhooks can be used for load testing before traffic switching to canaries by using `pre-rollout` type and also generating traffic + - name: acceptance-test + type: pre-rollout + url: http://flagger-loadtester.ingress-nginx/ + timeout: 30s + metadata: + type: bash + cmd: "curl -sd 'test' http://http-echo-canary/ping | grep ping" + - name: load-test + url: http://flagger-loadtester.ingress-nginx/ + timeout: 5s + metadata: + cmd: "hey -z 1m -q 3 -c 1 http://http-echo.devops.dasmeta.com/ping" diff --git a/main.tf b/main.tf index afac249..1efbe9e 100644 --- a/main.tf +++ b/main.tf @@ -255,7 +255,7 @@ module "metrics-server" { module "external-secrets" { source = "./modules/external-secrets" - count = var.create ? 1 : 0 + count = var.create && var.enable_external_secrets ? 1 : 0 namespace = var.external_secrets_namespace @@ -388,3 +388,18 @@ module "external-dns" { module.eks-cluster ] } + +module "flagger" { + count = var.create && var.flagger.enabled ? 1 : 0 + + source = "./modules/flagger" + namespace = var.flagger.namespace + configs = var.flagger.configs + metric_template_configs = var.flagger.metric_template_configs + enable_metric_template = var.flagger.enable_metric_template + enable_loadtester = var.flagger.enable_loadtester + + depends_on = [ + module.eks-cluster + ] +} diff --git a/modules/flagger/README.md b/modules/flagger/README.md new file mode 100644 index 0000000..74be722 --- /dev/null +++ b/modules/flagger/README.md @@ -0,0 +1,65 @@ +# terraform module allows to create/deploy flagger operator to have custom rollout strategies like canary/blue-green and also it allows to create custom flagger metric templates +## for more info check https://flagger.app and https://artifacthub.io/packages/helm/flagger/flagger + + +## example +```terraform +module "flagger" { + source = "dasmeta/eks/aws//modules/flagger" + version = "2.18.0" + + configs = { + meshProvider = "nginx" + prometheus = { + install = true # most possibly the prometheus is already installed, in that case set this to false and use `metricsServer` option to set the endpoint to prometheus + } + } +} +``` + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [helm](#requirement\_helm) | >= 2.0 | + +## Providers + +| Name | Version | +|------|---------| +| [helm](#provider\_helm) | >= 2.0 | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [helm_release.flagger_loadtester](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [helm_release.flagger_metric_template](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | +| [helm_release.this](https://registry.terraform.io/providers/hashicorp/helm/latest/docs/resources/release) | resource | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [atomic](#input\_atomic) | Whether use helm deploy with --atomic flag | `bool` | `false` | no | +| [chart\_version](#input\_chart\_version) | The app chart version | `string` | `"1.38.0"` | no | +| [configs](#input\_configs) | Configurations to pass and override default ones. Check the helm chart available configs here: https://artifacthub.io/packages/helm/flagger/flagger?modal=values | `any` | `{}` | no | +| [create\_namespace](#input\_create\_namespace) | Create namespace if requested | `bool` | `true` | no | +| [enable\_loadtester](#input\_enable\_loadtester) | Whether to install loadtester helm | `bool` | `false` | no | +| [enable\_metric\_template](#input\_enable\_metric\_template) | Whether to install flagger-metric-template helm | `bool` | `false` | no | +| [metric\_template\_chart\_version](#input\_metric\_template\_chart\_version) | The metric template chart version | `string` | `"0.1.0"` | no | +| [metric\_template\_configs](#input\_metric\_template\_configs) | Configurations to pass and override default ones. Check the helm chart available configs here: https://github.com/dasmeta/helm/tree/flagger-metric-template-0.1.0/charts/flagger-metric-template | `any` | `{}` | no | +| [namespace](#input\_namespace) | The namespace to install main helm. | `string` | `"ingress-nginx"` | no | +| [wait](#input\_wait) | Whether use helm deploy with --wait flag | `bool` | `true` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [helm\_metadata](#output\_helm\_metadata) | Helm release metadata | + diff --git a/modules/flagger/examples/basic/0-setup.tf b/modules/flagger/examples/basic/0-setup.tf new file mode 100644 index 0000000..b8b4a6a --- /dev/null +++ b/modules/flagger/examples/basic/0-setup.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + helm = ">= 2.0" + } +} + +provider "helm" {} diff --git a/modules/flagger/examples/basic/1-example.tf b/modules/flagger/examples/basic/1-example.tf new file mode 100644 index 0000000..c313d25 --- /dev/null +++ b/modules/flagger/examples/basic/1-example.tf @@ -0,0 +1,10 @@ +module "this" { + source = "../.." + + configs = { + meshProvider = "nginx" + prometheus = { + install = true # most possibly the prometheus is already installed, in that case set this to false and use `metricsServer` option to set the endpoint to prometheus + } + } +} diff --git a/modules/flagger/examples/basic/README.md b/modules/flagger/examples/basic/README.md new file mode 100644 index 0000000..71a3358 --- /dev/null +++ b/modules/flagger/examples/basic/README.md @@ -0,0 +1,32 @@ +# basic + + +## Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.3.0 | +| [helm](#requirement\_helm) | >= 2.0 | + +## Providers + +No providers. + +## Modules + +| Name | Source | Version | +|------|--------|---------| +| [this](#module\_this) | ../.. | n/a | + +## Resources + +No resources. + +## Inputs + +No inputs. + +## Outputs + +No outputs. + diff --git a/modules/flagger/load-tester.tf b/modules/flagger/load-tester.tf new file mode 100644 index 0000000..446cd18 --- /dev/null +++ b/modules/flagger/load-tester.tf @@ -0,0 +1,14 @@ +resource "helm_release" "flagger_loadtester" { + count = var.enable_loadtester ? 1 : 0 + + name = "flagger-loadtester" + repository = "https://flagger.app" + chart = "loadtester" + namespace = var.namespace + version = var.metric_template_chart_version + create_namespace = false + atomic = var.atomic + wait = var.wait + + depends_on = [helm_release.this] +} diff --git a/modules/flagger/main.tf b/modules/flagger/main.tf new file mode 100644 index 0000000..fb48b2a --- /dev/null +++ b/modules/flagger/main.tf @@ -0,0 +1,12 @@ +resource "helm_release" "this" { + name = "flagger" + repository = "https://flagger.app" + chart = "flagger" + namespace = var.namespace + version = var.chart_version + create_namespace = var.create_namespace + atomic = var.atomic + wait = var.wait + + values = [jsonencode(var.configs)] +} diff --git a/modules/flagger/metric-template.tf b/modules/flagger/metric-template.tf new file mode 100644 index 0000000..964fd7e --- /dev/null +++ b/modules/flagger/metric-template.tf @@ -0,0 +1,16 @@ +resource "helm_release" "flagger_metric_template" { + count = var.enable_metric_template ? 1 : 0 + + name = "flagger-metric-template" + repository = "https://dasmeta.github.io/helm" + chart = "flagger-metric-template" + namespace = var.namespace + version = var.metric_template_chart_version + create_namespace = false + atomic = var.atomic + wait = var.wait + + values = [jsonencode(var.metric_template_configs)] + + depends_on = [helm_release.this] +} diff --git a/modules/flagger/outputs.tf b/modules/flagger/outputs.tf new file mode 100644 index 0000000..c3fbde1 --- /dev/null +++ b/modules/flagger/outputs.tf @@ -0,0 +1,4 @@ +output "helm_metadata" { + value = helm_release.this.metadata + description = "Helm release metadata" +} diff --git a/modules/flagger/variables.tf b/modules/flagger/variables.tf new file mode 100644 index 0000000..46997ce --- /dev/null +++ b/modules/flagger/variables.tf @@ -0,0 +1,59 @@ +variable "chart_version" { + type = string + default = "1.38.0" + description = "The app chart version" +} + +variable "metric_template_chart_version" { + type = string + default = "0.1.0" + description = "The metric template chart version" +} + +variable "namespace" { + description = "The namespace to install main helm." + type = string + default = "ingress-nginx" # by default it uses nginx as provider so we install flagger in nginx ingress namespace as of doc +} + +variable "create_namespace" { + type = bool + default = true + description = "Create namespace if requested" +} + +variable "atomic" { + type = bool + default = false + description = "Whether use helm deploy with --atomic flag" +} + +variable "wait" { + type = bool + default = true + description = "Whether use helm deploy with --wait flag" +} + +variable "configs" { + type = any + default = {} + description = "Configurations to pass and override default ones. Check the helm chart available configs here: https://artifacthub.io/packages/helm/flagger/flagger?modal=values" +} + +variable "enable_metric_template" { + type = bool + default = false + description = "Whether to install flagger-metric-template helm" +} + +variable "metric_template_configs" { + type = any + default = {} + description = "Configurations to pass and override default ones. Check the helm chart available configs here: https://github.com/dasmeta/helm/tree/flagger-metric-template-0.1.0/charts/flagger-metric-template" +} + +variable "enable_loadtester" { + type = bool + default = false + description = "Whether to install loadtester helm" +} diff --git a/modules/flagger/versions.tf b/modules/flagger/versions.tf new file mode 100644 index 0000000..8e20df1 --- /dev/null +++ b/modules/flagger/versions.tf @@ -0,0 +1,7 @@ +terraform { + required_version = ">= 1.3.0" + + required_providers { + helm = ">= 2.0" + } +} diff --git a/modules/nginx-ingress-controller/values.yaml.tpl b/modules/nginx-ingress-controller/values.yaml.tpl index f1e309e..483682e 100644 --- a/modules/nginx-ingress-controller/values.yaml.tpl +++ b/modules/nginx-ingress-controller/values.yaml.tpl @@ -3,6 +3,11 @@ controller: use-forwarded-headers: "true" enable-underscores-in-headers: 'true' replicaCount: ${replicacount} +%{ if metrics_enabled ~} + podAnnotations: + prometheus.io/scrape: true + prometheus.io/port: 10254 +%{ endif ~} metrics: enabled: ${metrics_enabled} diff --git a/variables.tf b/variables.tf index 8d348c5..22ba5c9 100644 --- a/variables.tf +++ b/variables.tf @@ -97,6 +97,12 @@ variable "send_alb_logs_to_cloudwatch" { description = "Whether send alb logs to CloudWatch or not." } +variable "enable_alb_ingress_controller" { + type = bool + default = true + description = "Whether alb ingress controller enabled." +} + variable "alb_log_bucket_name" { type = string default = "" @@ -186,6 +192,12 @@ variable "cluster_endpoint_public_access" { default = true } +variable "enable_external_secrets" { + type = bool + description = "Whether to enable external-secrets operator" + default = true +} + variable "external_secrets_namespace" { type = string description = "The namespace of external-secret operator" @@ -585,3 +597,18 @@ variable "external_dns" { } description = "Allows to install external-dns helm chart and related roles, which allows to automatically create R53 records based on ingress/service domain/host configs" } + +variable "flagger" { + type = object({ + enabled = optional(bool, false) + namespace = optional(string, "ingress-nginx") # The flagger operator helm being installed on same namespace as mesh/ingress provider so this field need to be set based on which ingress/mesh we are going to use, more info in https://artifacthub.io/packages/helm/flagger/flagger + configs = optional(any, {}) # available options can be found in https://artifacthub.io/packages/helm/flagger/flagger + metric_template_configs = optional(any, {}) # available options can be found in https://github.com/dasmeta/helm/tree/flagger-metric-template-0.1.0/charts/flagger-metric-template + enable_metric_template = optional(bool, false) + enable_loadtester = optional(bool, false) + }) + default = { + enabled = false + } + description = "Allows to create/deploy flagger operator to have custom rollout strategies like canary/blue-green and also it allows to create custom flagger metric templates" +}