Kubernetes Prometheus PagerDuty Incident Management on AWS EKS with Terraform

Architecture Pro-Tip:

For highly resilient incident management, consider deploying Prometheus and Alertmanager in a high-availability configuration across multiple availability zones within your EKS cluster. Additionally, leverage AWS Systems Manager Parameter Store or Secrets Manager to securely store sensitive information like PagerDuty API keys, integrating them into your Terraform or Helm deployments to avoid hardcoding credentials. This approach enhances security posture and operational robustness, crucial for critical incident response systems.

In today's dynamic cloud-native environments, proactive and efficient incident management is paramount for maintaining service reliability and ensuring business continuity. As organizations increasingly adopt Kubernetes on AWS Elastic Kubernetes Service (EKS) for container orchestration, the complexity of monitoring and responding to operational issues scales significantly. This comprehensive guide delves into establishing a robust incident management pipeline by integrating Prometheus for monitoring and alerting, PagerDuty for sophisticated incident response, all orchestrated and provisioned on AWS EKS using Terraform. We will walk through the architectural considerations, deployment strategies, and configuration specifics to empower your DevOps teams with an automated, scalable, and highly effective incident resolution framework, minimizing downtime and operational toil in your cloud infrastructure. This setup forms the bedrock of a resilient microservices architecture, crucial for modern enterprises.

1. Understanding the Incident Management Stack on AWS EKS

Building an effective incident management system requires a carefully selected stack of tools that work seamlessly together. Our chosen architecture leverages AWS EKS as the foundational Kubernetes platform, providing a managed, scalable, and highly available environment for running containerized applications. On top of EKS, Prometheus serves as the backbone of our monitoring solution, collecting metrics from various Kubernetes components and applications. Its powerful query language (PromQL) and flexible alerting rules make it indispensable for real-time observability. Complementing Prometheus is Alertmanager, which handles the deduplication, grouping, and routing of alerts generated by Prometheus. Finally, PagerDuty acts as the central hub for incident response, transforming raw alerts into actionable incidents, managing on-call schedules, escalation policies, and providing a comprehensive workflow for incident resolution. The entire infrastructure, from EKS cluster provisioning to the deployment of monitoring agents and configuration of alerts, is managed declaratively using Terraform, ensuring consistency, repeatability, and version control across environments. This integrated approach not only streamlines operations but also significantly reduces the mean time to detect (MTTD) and mean time to resolve (MTTR) incidents.

2. Provisioning AWS EKS with Terraform

The first step in setting up our incident management system is to provision the underlying Kubernetes cluster on AWS EKS. Terraform is the ideal tool for this, allowing us to define our infrastructure as code (IaC) and manage the entire lifecycle of our EKS cluster in a consistent and automated manner. This includes creating the VPC, subnets, security groups, IAM roles for EKS, and the EKS cluster itself, along with its node groups. Using a modular approach with Terraform helps in managing complexity and promoting reusability. Below is a simplified example of how you might structure your Terraform configuration to provision an EKS cluster. Remember to replace placeholder values with your specific AWS account and region details. This foundational setup ensures that all subsequent deployments of Prometheus, Alertmanager, and your applications will run on a well-defined and version-controlled infrastructure, enabling true GitOps practices from the ground up.

resource "aws_vpc" "eks_vpc" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true enable_dns_support = true tags = { Name = "eks-incident-management-vpc" } } resource "aws_subnet" "eks_subnet_public_a" { vpc_id = aws_vpc.eks_vpc.id cidr_block = "10.0.1.0/24" availability_zone = "us-east-1a" # Change to your region/AZ tags = { Name = "eks-subnet-public-a" } } resource "aws_subnet" "eks_subnet_private_a" { vpc_id = aws_vpc.eks_vpc.id cidr_block = "10.0.10.0/24" availability_zone = "us-east-1a" # Change to your region/AZ tags = { Name = "eks-subnet-private-a" } } resource "aws_iam_role" "eks_cluster_role" { name = "eks-cluster-role" assume_role_policy = jsonencode({ Version = "2012-10-17" Statement = [ { Effect = "Allow" Principal = { Service = "eks.amazonaws.com" } Action = "sts:AssumeRole" } ] }) } resource "aws_iam_role_policy_attachment" "eks_cluster_policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy" role = aws_iam_role.eks_cluster_role.name } resource "aws_iam_role_policy_attachment" "eks_service_policy" { policy_arn = "arn:aws:iam::aws:policy/AmazonEKSServicePolicy" role = aws_iam_role.eks_cluster_role.name } resource "aws_eks_cluster" "incident_cluster" { name = "incident-management-eks" role_arn = aws_iam_role.eks_cluster_role.arn vpc_config { subnet_ids = [aws_subnet.eks_subnet_private_a.id] security_group_ids = [] } tags = { Name = "incident-management-eks" } }

After applying this Terraform configuration, you will have a functional EKS cluster. It's important to configure `kubeconfig` to connect to your newly provisioned EKS cluster. This typically involves using the AWS CLI command `aws eks update-kubeconfig --name incident-management-eks --region your-aws-region`. This command generates or updates your `kubeconfig` file, allowing `kubectl` to interact with your cluster. Once connected, you can verify the cluster's status and nodes, preparing for the deployment of Prometheus and Alertmanager. Remember that a production-grade EKS setup would involve more intricate networking, robust IAM policies, and multiple node groups for different workloads, potentially managed via the `terraform-aws-modules/eks/aws` module for better abstraction.

3. Deploying Prometheus and Alertmanager on EKS

With the EKS cluster in place, the next crucial step is to deploy Prometheus and Alertmanager. The most common and recommended way to achieve this on Kubernetes is by using their respective Helm charts, which encapsulate the complexity of Kubernetes deployments into easily manageable packages. The Prometheus community provides a robust `kube-prometheus-stack` Helm chart that includes Prometheus, Alertmanager, Grafana, and various Kubernetes exporters, offering a comprehensive monitoring solution out-of-the-box. When deploying, it's vital to consider persistent storage for Prometheus's time-series data to ensure data retention across pod restarts or redeployments. This is typically achieved using PersistentVolumes and PersistentVolumeClaims backed by AWS EBS or EFS. Below is an example of a `values.yaml` file snippet to customize the `kube-prometheus-stack` deployment, focusing on Alertmanager for PagerDuty integration and setting up basic resource requests/limits. This configuration forms the core of your observability platform, collecting vital metrics that will drive your alerting strategy.

# prometheus-values.yaml alertmanager: enabled: true alertmanagerSpec: retention: 120h # Store alerts for 5 days storage: volumeClaimTemplate: spec: storageClassName: gp2 # Or your preferred StorageClass resources: requests: storage: 10Gi # Placeholder for PagerDuty configuration, will be detailed later configSecret: alertmanager-config prometheus: enabled: true prometheusSpec: serviceMonitorSelectorNilUsesLabels: false # Required for Prometheus to discover ServiceMonitors podMonitorSelectorNilUsesLabels: false ruleSelectorNilUsesLabels: false scrapeInterval: 1m storageSpec: volumeClaimTemplate: spec: storageClassName: gp2 # Or your preferred StorageClass resources: requests: storage: 50Gi # Adjust based on your metrics retention needs kubeControllerManager: enabled: true kubeEtcd: enabled: true kubeScheduler: enabled: true kubeProxy: enabled: true

To deploy Prometheus and Alertmanager using this configuration, you would typically use the Helm command. First, add the Prometheus community Helm repository, then install the chart, passing your custom `values.yaml` file. This command will deploy all components of the stack into your EKS cluster, making them ready to scrape metrics and process alerts. After deployment, you can verify the status of the Prometheus and Alertmanager pods and services using `kubectl get pods -n prometheus` (assuming you deploy to the `prometheus` namespace). Ensure all pods are running and healthy before proceeding to the next steps, as their operational status is critical for reliable incident detection.

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm repo update helm install prometheus prometheus-community/kube-prometheus-stack -n prometheus --create-namespace -f prometheus-values.yaml

4. Configuring Alertmanager for PagerDuty Integration

Alertmanager is responsible for processing alerts sent by Prometheus, including deduplicating, grouping, and routing them to the appropriate receivers. To integrate with PagerDuty, we need to configure Alertmanager to send alerts to a PagerDuty service using its API key. First, you must create a new service in PagerDuty and obtain its integration key. It's best practice to store this sensitive API key in a Kubernetes secret, which Alertmanager can then reference. The Alertmanager configuration, typically provided as a YAML file, defines the receivers (like PagerDuty), routes for sending alerts, and inhibition/silence rules. The example below shows a basic `alertmanager.yaml` snippet that configures a PagerDuty receiver. This configuration should be placed into a Kubernetes Secret and referenced by the `configSecret` parameter in your `prometheus-values.yaml` from the previous section. This setup ensures that whenever Prometheus fires an alert that matches a routing rule, Alertmanager will process it and notify PagerDuty, initiating your defined incident response workflow.

# alertmanager.yaml content (to be base64 encoded and put in a secret) global: resolve_timeout: 5m route: group_by: ['alertname', 'cluster', 'service'] group_wait: 30s group_interval: 5m repeat_interval: 1h receiver: 'pagerduty-default' routes: - match: severity: critical receiver: 'pagerduty-critical' continue: true - match: severity: warning receiver: 'pagerduty-warning' receivers: - name: 'pagerduty-default' pagerduty_configs: - service_key: "{{ .Secret.pagerduty_default_key }}" # Referencing key from secret severity: '{{ .CommonLabels.severity | title }}' details: description: '{{ .Annotations.description }}' summary: '{{ .CommonLabels.alertname }} on {{ .CommonLabels.instance }}' url: '{{ .GeneratorURL }}' - name: 'pagerduty-critical' pagerduty_configs: - service_key: "{{ .Secret.pagerduty_critical_key }}" # Dedicated critical key severity: 'critical' details: description: '{{ .Annotations.description }}' summary: '{{ .CommonLabels.alertname }} on {{ .CommonLabels.instance }} (Critical)' url: '{{ .GeneratorURL }}' - name: 'pagerduty-warning' pagerduty_configs: - service_key: "{{ .Secret.pagerduty_default_key }}" # Can reuse default or use a separate one severity: 'warning' details: description: '{{ .Annotations.description }}' summary: '{{ .CommonLabels.alertname }} on {{ .CommonLabels.instance }} (Warning)' url: '{{ .GeneratorURL }}'

To apply this configuration, you would first create a Kubernetes Secret containing the `alertmanager.yaml` content (base64 encoded) and your PagerDuty service keys. Then, ensure the Prometheus Helm chart's `alertmanager.alertmanagerSpec.configSecret` points to this new secret. This modular approach keeps sensitive information out of direct configuration files and allows for easy rotation. Once the Alertmanager configuration is updated and the pod restarts, it will be ready to send alerts to PagerDuty based on the defined routing rules. It is crucial to test this integration thoroughly by generating a test alert to ensure that PagerDuty incidents are created correctly and follow the expected escalation paths.

5. Defining Prometheus Alerting Rules

With Prometheus and Alertmanager deployed and integrated with PagerDuty, the next step is to define meaningful alerting rules. These rules, written in PromQL, instruct Prometheus when to fire an alert based on specific metric thresholds or patterns. Effective alerting rules are critical for distinguishing actionable incidents from mere noise. Each rule defines an `alertname`, the `expr` (PromQL query), `for` duration (how long the condition must be met before firing), and `labels`/`annotations` to provide context. Labels are used for routing and grouping by Alertmanager, while annotations provide human-readable descriptions and runbook links. Below are examples of common Kubernetes-related alerting rules that could trigger PagerDuty incidents, ensuring critical resource saturation or application failures are immediately escalated. It is essential to continuously review and refine these rules, tuning them to your application's behavior and operational requirements, to avoid alert fatigue while catching genuine issues promptly.

# rules.yaml for Prometheus groups: - name: kubernetes.rules rules: - alert: HighCPUUtilization expr: sum(rate(container_cpu_usage_seconds_total{image!="", container!="POD"}[5m])) by (namespace, pod) > 0.8 for: 5m labels: severity: critical namespace: kube-system # Example, adjust as needed annotations: summary: "High CPU utilization detected on pod {{ $labels.pod }} in namespace {{ $labels.namespace }}" description: "CPU usage for pod {{ $labels.pod }} has been above 80% for 5 minutes. Investigate potential bottlenecks or resource constraints. Runbook: [Link to CPU Troubleshooting Guide]" - alert: HighMemoryUtilization expr: sum(container_memory_usage_bytes{image!="", container!="POD"}) by (namespace, pod) / sum(container_spec_memory_limit_bytes{image!="", container!="POD"}) by (namespace, pod) > 0.9 for: 10m labels: severity: critical namespace: default # Example, adjust as needed annotations: summary: "High Memory utilization detected on pod {{ $labels.pod }} in namespace {{ $labels.namespace }}" description: "Memory usage for pod {{ $labels.pod }} has been above 90% of its limit for 10 minutes. This could lead to OOMKills. Runbook: [Link to Memory Optimization Guide]" - alert: PodRestartingFrequently expr: changes(kube_pod_container_status_restarts_total[15m]) > 2 for: 5m labels: severity: warning annotations: summary: "Pod {{ $labels.pod }} in namespace {{ $labels.namespace }} restarting frequently" description: "Container {{ $labels.container }} in pod {{ $labels.pod }} has restarted more than twice in the last 15 minutes. Check pod logs for errors. Runbook: [Link to Pod Crashloop Troubleshooting]" - alert: KubeApiServerDown expr: absent(up{job="kubernetes-apiservers"}) for: 1m labels: severity: critical annotations: summary: "Kubernetes API Server is down" description: "The Kubernetes API server is unreachable. This is a critical infrastructure issue affecting cluster management. Immediate investigation required. Runbook: [Link to EKS Control Plane Troubleshooting]"

These rules are typically deployed as PrometheusRule objects within Kubernetes, which the `kube-prometheus-stack` chart automatically discovers. You can create a Kubernetes manifest for these rules (e.g., `prometheusrules.yaml`) and apply it with `kubectl apply -f prometheusrules.yaml -n prometheus`. It's crucial to define `severity` labels that align with your Alertmanager routing and PagerDuty escalation policies, ensuring that critical alerts trigger immediate notifications while warnings might go to a less urgent channel or a different PagerDuty service. Regularly test these rules by simulating failure conditions in a staging environment to validate their effectiveness and the entire incident workflow from detection to resolution.

6. Automating the Deployment with Terraform and Helm

While we've used Helm commands manually for deploying Prometheus, a truly automated and GitOps-friendly approach involves integrating Helm deployments directly into our Terraform workflow. Terraform's `helm_release` resource allows us to declare the desired state of Helm chart deployments, making them part of our infrastructure as code. This means the entire incident management stack – from the EKS cluster to Prometheus, Alertmanager, and its configurations – can be provisioned, updated, and destroyed with a single `terraform apply` command. This significantly improves consistency, reduces manual errors, and simplifies environment replication. Below is an example of how you might include the Prometheus and Alertmanager Helm chart deployment within your Terraform configuration, referencing the `prometheus-values.yaml` and the Alertmanager configuration secret. This approach unifies your infrastructure and application deployments under a single, declarative framework.

# main.tf (continued from EKS setup) # Create a Kubernetes Namespace for Prometheus resource "kubernetes_namespace" "prometheus" { metadata { name = "prometheus" } } # Define the PagerDuty API Key as a Kubernetes Secret # It's better to get this from a secure source like AWS Secrets Manager resource "kubernetes_secret" "alertmanager_pagerduty_keys" { metadata { name = "pagerduty-api-keys" namespace = kubernetes_namespace.prometheus.metadata[0].name } data = { # Base64 encode your actual PagerDuty service keys here "pagerduty_default_key" = base64encode("your_default_pagerduty_key") "pagerduty_critical_key" = base64encode("your_critical_pagerduty_key") } type = "Opaque" } # Define the Alertmanager configuration as a Kubernetes Secret resource "kubernetes_secret" "alertmanager_config" { metadata { name = "alertmanager-config" namespace = kubernetes_namespace.prometheus.metadata[0].name } data = { "alertmanager.yaml" = base64encode(file("alertmanager.yaml")) } type = "Opaque" } # Deploy Prometheus and Alertmanager using the Helm provider resource "helm_release" "kube_prometheus_stack" { name = "prometheus" repository = "https://prometheus-community.github.io/helm-charts" chart = "kube-prometheus-stack" namespace = kubernetes_namespace.prometheus.metadata[0].name version = "50.x.x" # Specify a stable chart version timeout = 600 # Pass custom values from the prometheus-values.yaml file values = [ file("prometheus-values.yaml") ] # Override specific values to link Alertmanager to the secret set { name = "alertmanager.alertmanagerSpec.configSecret" value = kubernetes_secret.alertmanager_config.metadata[0].name } set { name = "alertmanager.secret.pagerduty_default_key" # Pass key from secret to Alertmanager template value = base64decode(kubernetes_secret.alertmanager_pagerduty_keys.data["pagerduty_default_key"]) } set { name = "alertmanager.secret.pagerduty_critical_key" value = base64decode(kubernetes_secret.alertmanager_pagerduty_keys.data["pagerduty_critical_key"]) } # Ensure the Alertmanager uses the templated secret for PagerDuty set { name = "alertmanager.alertmanagerSpec.templateFile" value = "alertmanager-template.yaml" # You might need a custom template to pull secret values } # Example of deploying Prometheus rules (assuming rules.yaml is a file) # This might require a custom Helm chart for your rules or a separate kubernetes_manifest # for PrometheusRule objects, as kube-prometheus-stack has its own way of handling them. # For simplicity, if rules.yaml contains Kubernetes PrometheusRule objects: # resource "kubernetes_manifest" "prometheus_rules" { # manifest = yamldecode(file("rules.yaml")) # depends_on = [helm_release.kube_prometheus_stack] # } }

To make the `alertmanager.yaml` and `prometheus-values.yaml` files accessible to Terraform, ensure they are in the same directory as your `main.tf` or provide correct file paths. The `alertmanager.yaml` content should be specifically designed to handle the secret injection using Go templating that Alertmanager expects, often requiring a slight modification or a custom `alertmanager-template.yaml` within the Helm chart overrides if direct secret mapping isn't sufficient. This Terraform setup orchestrates the full lifecycle, from cluster creation to application deployment, enabling a truly automated and reliable incident management system. Running `terraform plan` followed by `terraform apply` will execute these steps, bringing your entire monitoring and alerting infrastructure online in a controlled manner.

7. Testing and Validating Your Incident Management Setup

After deploying the entire stack, thorough testing and validation are crucial to ensure the incident management system functions as expected. This involves verifying that Prometheus is correctly scraping metrics, Alertmanager is processing alerts, and PagerDuty is receiving and escalating incidents according to your defined policies. You can simulate various failure scenarios to trigger your Prometheus alerting rules. For example, you might intentionally deploy a pod that consumes excessive CPU or memory, or a pod that consistently crashes, to trigger the `HighCPUUtilization`, `HighMemoryUtilization`, or `PodRestartingFrequently` alerts. Alternatively, you can use `curl` commands to directly send test alerts to Alertmanager for quick verification of the PagerDuty integration without inducing actual system failures.

# To send a test alert directly to Alertmanager (replace with your Alertmanager service URL) # First, expose Alertmanager if it's not already accessible, e.g., via port-forward # kubectl port-forward -n prometheus svc/prometheus-kube-prometheus-alertmanager 9093:9093 # Then, use curl to send a test alert: curl -X POST -H 'Content-Type: application/json' -d '[ { "labels": { "alertname": "TestPagerDutyAlert", "severity": "critical", "instance": "test-instance", "namespace": "test-namespace" }, "annotations": { "summary": "This is a test alert for PagerDuty integration.", "description": "Please verify if this alert creates an incident in PagerDuty." }, "generatorURL": "http://prometheus.example.com/graph?g0.expr=vector(1)" } ]' http://localhost:9093/api/v2/alerts

After sending a test alert, immediately check the PagerDuty console to confirm that an incident has been created and that the correct on-call teams are notified. Review the incident details for accuracy, ensuring that labels and annotations from Prometheus are correctly mapped. You should also check the Alertmanager UI (if exposed) to see the status of the alert. If alerts are not firing or incidents are not created, examine Prometheus logs for rule evaluation errors, Alertmanager logs for routing issues, and PagerDuty integration settings for misconfigurations. This iterative process of testing, observing, and refining ensures that your incident management system is robust, reliable, and ready to handle real-world operational challenges effectively. Continuous monitoring of the monitoring system itself is also a best practice, often referred to as 'meta-monitoring', ensuring the tools reporting issues are always operational.

Conclusion: Empowering DevOps with Automated Incident Response

Implementing a sophisticated incident management system with Kubernetes, Prometheus, PagerDuty, and Terraform on AWS EKS provides a powerful framework for maintaining high availability and reliability in modern cloud-native applications. By leveraging Terraform for infrastructure as code, organizations can ensure that their EKS clusters, along with Prometheus and Alertmanager, are provisioned and configured consistently and efficiently. Prometheus's robust monitoring and alerting capabilities, coupled with Alertmanager's intelligent alert routing, ensure that critical issues are detected promptly. PagerDuty then takes these actionable alerts and orchestrates a streamlined incident response, mobilizing the right teams with the right context to resolve issues rapidly. This integrated approach not only minimizes the impact of incidents but also fosters a culture of reliability engineering, enabling DevOps teams to operate complex systems with confidence. Continuous iteration, refinement of alerting rules, and regular testing are key to evolving this system alongside your applications and infrastructure, ultimately leading to a more resilient and performant cloud environment.

Comments

Popular posts from this blog

AWS EKS Production Alerting: Integrating Prometheus, Alertmanager, and PagerDuty with Terraform

Terraform for AWS EKS Cluster Provisioning and Datadog Observability Integration

Terraform Configuration for Datadog-PagerDuty Incident Management on AWS EKS