
Eks Operation Review
- 6 installs
- 39 repo stars
- Updated August 4, 2026
- aws-samples/sample-apex-skills
eks-operation-review is a Claude Code skill that runs a structured 10-section operational excellence assessment of a live EKS cluster and produces a GREEN/AMBER/RED rated report.
About
This skill runs a structured operational excellence assessment against a live EKS cluster across 10 areas including networking, autoscaling, observability, access and identity, and cluster lifecycle. It produces a GREEN/AMBER/RED rated report with prioritized recommendations. A developer uses it to audit, review, or health-check a cluster's operational posture.
- Runs a structured 10-section EKS operational excellence assessment against a live cluster
- Produces a GREEN/AMBER/RED rated report with prioritized recommendations
- Supports section-scoped reviews of individual areas
Eks Operation Review by the numbers
- 6 all-time installs (skills.sh)
- Ranked #1,067 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
eks-operation-review capabilities & compatibility
- Capabilities
- devops · security audit
- Works with
- aws · kubernetes
- Use cases
- devops · security audit
- Pricing
- Bring your own API key
What eks-operation-review says it does
Run a structured EKS operational excellence assessment against a live cluster.
produces a GREEN/AMBER/RED rated report with prioritized recommendations.
npx skills add https://github.com/aws-samples/sample-apex-skills --skill eks-operation-reviewAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 6 |
|---|---|
| repo stars | ★ 39 |
| Last updated | August 4, 2026 |
| Repository | aws-samples/sample-apex-skills ↗ |
What it does
Audit a live EKS cluster across 10 operational areas and produce a GREEN/AMBER/RED rated report.
Who is it for?
Auditing or health-checking a live EKS cluster's operational posture with a rated report.
Skip if: Upgrade readiness, cluster discovery, or architectural design advice.
When should I use this skill?
Any request to audit, review, health-check, or score an EKS cluster's operational posture.
What you get
A GREEN/AMBER/RED rated operational assessment across 10 areas with prioritized recommendations.
- Rated 10-section operational assessment report
- Prioritized recommendations
By the numbers
- Assesses 10 operational areas
- GREEN/AMBER/RED 3-level rating
Files
EKS Operation Review
This skill performs a structured 10-section operational assessment of a live EKS cluster, producing a rated report with prioritized recommendations.
When to use
Activate for any request to audit, review, health-check, or score an EKS cluster's operational posture — including section-scoped reviews of individual areas (e.g., "check my EKS networking", "review RBAC on my cluster").
Not for: upgrade readiness assessments, cluster discovery, or architectural design advice. General Kubernetes questions, AWS troubleshooting, cluster creation, and one-off kubectl commands should be handled directly without this skill.
Instructions
Read and follow ~/.claude/apex-steering/workflows/eks-operation-review.md — it contains the full workflow, tool usage rules, and steering file map. Load each steering file from references/ before running its corresponding section.
Prerequisites
- AWS credentials with EKS read access
- Python 3.10+ and uv installed
- EKS MCP server configured (see the
eks-mcp-serverskill for setup); apex does not ship a project-root.mcp.json
7178322d0eabff80a4cedcaa14cc838e903c4d3fMIT No Attribution
Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
Access & Identity
Purpose
Assess IAM and RBAC configuration for security and operational excellence — pod-level permissions, least privilege, and API server access controls.
Checks to Execute
3.1 — Pod-Level AWS Permissions (IRSA / EKS Pod Identity)
What to check:
- OIDC provider configured (prerequisite for IRSA)
- EKS Pod Identity associations
- Service accounts with IRSA annotations (
eks.amazonaws.com/role-arn) - Node IAM role policies (are they overly broad?)
- AWS credentials in Kubernetes Secrets or pod env vars
How to check: 1. Describe cluster → identity.oidc.issuer (OIDC configured?) 2. List Pod Identity associations 3. List ServiceAccounts across all namespaces → filter for IRSA annotation 4. List node groups → describe first one → get nodeRole → get policies for that role 5. List Secrets across namespaces → check for AWS_ACCESS_KEY_ID keys (⚠️ avoid reading Secret values) 6. List pods → check container env vars for AWS_ACCESS_KEY_ID
Rating:
- 🟢 GREEN: All AWS-accessing pods use IRSA or Pod Identity, node role is minimal, no hardcoded credentials
- 🟡 AMBER: IRSA partially adopted, or node role has some extra permissions
- 🔴 RED: No IRSA/Pod Identity, node role has broad permissions (S3FullAccess, DynamoDBFullAccess), or hardcoded AWS credentials found
- ⬜ UNKNOWN: Cannot determine which pods need AWS access vs which don't
Key talking point: Node-level IAM = every pod on that node inherits the same permissions. One compromised pod gets access to everything.
---
3.2 — Least Privilege RBAC
What to check:
- ClusterRoleBindings to cluster-admin (count and subjects)
- ClusterRoles with wildcard permissions (
*verbs on*resources) - Ratio of namespace-scoped RoleBindings vs cluster-scoped ClusterRoleBindings
- Service accounts with cluster-admin
How to check: 1. List ClusterRoleBindings → filter roleRef.name == "cluster-admin" → count and list subjects 2. List ClusterRoles → check rules for verbs: ["*"] and resources: ["*"] 3. Count RoleBindings across all namespaces vs ClusterRoleBindings 4. List application namespaces (exclude kube-system, kube-public, kube-node-lease, default)
Rating:
- 🟢 GREEN: Namespace-scoped RBAC, cluster-admin limited to 1-2 break-glass bindings, periodic review
- 🟡 AMBER: Some namespace isolation but cluster-admin overused (>3 bindings)
- 🔴 RED: Developers have cluster-admin in production, wildcard service accounts, no namespace isolation
- ⬜ UNKNOWN: Cannot determine if RBAC is reviewed periodically — suggest user investigate
---
3.3 — EKS API Server Endpoint & Network Access
What to check:
- Public/private endpoint configuration
- Public access CIDR restrictions
- Cluster security group inbound rules
- Whether audit logging is enabled
How to check: 1. Describe cluster → resourcesVpcConfig.endpointPublicAccess, endpointPrivateAccess, publicAccessCidrs 2. Describe cluster → logging.clusterLogging (check if audit log type is enabled)
Rating:
- 🟢 GREEN: Private endpoint enabled, public either disabled or CIDR-restricted, audit logging on
- 🟡 AMBER: Public endpoint with CIDR restrictions, or private enabled but audit logging off
- 🔴 RED: Public endpoint open to
0.0.0.0/0, or no audit logging - ⬜ UNKNOWN: Cannot determine MFA/SSO requirements — suggest user investigate
Key talking point: An API server open to 0.0.0.0/0 is exposed to the internet. You're relying entirely on authentication.
---
3.4 — Pod Security Admission (PSA)
What to check:
- Pod Security Standards enforcement via namespace labels (
pod-security.kubernetes.io/enforce) - Which namespaces have PSA labels and at what level (privileged, baseline, restricted)
- Production namespaces without PSA enforcement
How to check: 1. List namespaces → inspect labels for pod-security.kubernetes.io/enforce, pod-security.kubernetes.io/warn, pod-security.kubernetes.io/audit 2. Count namespaces with enforcement vs without (exclude kube-system, kube-public, kube-node-lease) 3. Check if any application namespaces use privileged enforce level
Rating:
- 🟢 GREEN: PSA labels on all application namespaces,
baselineorrestrictedenforcement - 🟡 AMBER: PSA labels on some namespaces but not all, or only
warn/auditmode (no enforcement) - 🔴 RED: No PSA labels on any namespace, or application namespaces set to
privileged - ⬜ UNKNOWN: Cannot determine if third-party admission controller (OPA/Gatekeeper, Kyverno) handles pod security instead
Key talking point: PodSecurityPolicy was removed in Kubernetes 1.25. Pod Security Admission is the built-in replacement. Without it (or a third-party equivalent), any pod spec is accepted — including privileged containers.
Add-on & Component Management
Purpose
Assess add-on management maturity, node health monitoring, and cluster insights usage.
Checks to Execute
10.1 — Core Add-ons Managed via EKS Managed Add-ons
What to check:
- All EKS managed add-ons: name, version, status, health
- Compare installed versions against latest compatible for the cluster version
- Self-managed add-ons in kube-system (Helm releases)
- Deprecated in-tree EBS plugin usage
How to check: 1. List addons → describe each for version, status, health issues 2. For each of the 4 core add-ons (vpc-cni, coredns, kube-proxy, aws-ebs-csi-driver):
- Check if installed as managed add-on
- Compare installed version vs latest compatible
3. List PersistentVolumes → check for spec.awsElasticBlockStore (deprecated in-tree)
Rating:
- 🟢 GREEN: All core add-ons are EKS Managed, on latest or N-1 version, healthy
- 🟡 AMBER: Managed but behind (>1 minor version), or mix of managed and self-managed
- 🔴 RED: Core add-ons self-managed with no version tracking, health issues, or deprecated in-tree plugin
- ⬜ UNKNOWN: Cannot list add-ons
Key talking point: EKS does NOT auto-update add-ons when you upgrade the control plane. Clusters upgraded to 1.31 still running vpc-cni from 1.27 is a ticking time bomb.
---
10.2 — Node Health Monitoring & Auto-Repair
What to check:
- EKS Node Monitoring Agent add-on (
eks-node-monitoring-agent) - Node auto-repair configuration on managed node groups
- GPU nodes (need NMA for GPU failure detection)
- Current node conditions
How to check: 1. Describe addon eks-node-monitoring-agent 2. List node groups → describe each → check nodeRepairConfig 3. List nodes → check for nvidia.com/gpu in capacity (GPU nodes) 4. List nodes → inspect conditions for MemoryPressure, DiskPressure, etc.
Rating:
- 🟢 GREEN: NMA installed and node auto-repair enabled
- 🟡 AMBER: No NMA but node conditions monitored, or NMA without auto-repair
- 🔴 RED: No node health monitoring beyond basic Kubernetes conditions, especially with GPU workloads
- ⬜ UNKNOWN: Should not happen with live access
---
10.3 — EKS Cluster Insights Reviewed
What to check:
- All cluster insights with status
- Count by status (PASSING, WARNING, ERROR)
- Details on any ERROR or WARNING insights
How to check: 1. Get EKS Insights for the cluster 2. For any non-PASSING insights → get detailed description and recommendation
Rating:
- 🟢 GREEN: Insights reviewed, no ERROR/WARNING, or all addressed
- 🟡 AMBER: WARNING insights unaddressed
- 🔴 RED: ERROR insights ignored
- ⬜ UNKNOWN: Insights API not accessible
Autoscaling
Purpose
Assess cluster-level autoscaling (nodes) and workload-level autoscaling (pods), plus AZ resilience.
Checks to Execute
7.1 — Cluster Autoscaling Strategy
What to check:
- Karpenter NodePools and EC2NodeClasses
- Cluster Autoscaler deployment in kube-system
- EKS Auto Mode (cluster computeConfig)
- Node group scaling config (min/max/desired)
- Spot vs On-Demand nodes
- Currently pending pods
How to check: 1. List resources nodepools.karpenter.sh → check limits and consolidation policy. If 404/NotFound (CRD not installed) → Karpenter is not deployed, proceed to check for Cluster Autoscaler or Auto Mode. If 403/Forbidden → mark Karpenter status UNKNOWN. 2. List Deployments in kube-system → check for cluster-autoscaler 3. Describe cluster → computeConfig for Auto Mode 4. List node groups → describe each for scalingConfig and capacityType 5. List nodes → check labels for capacity type (karpenter.sh/capacity-type or eks.amazonaws.com/capacityType) 6. List pods with field selector status.phase=Pending
Rating:
- 🟢 GREEN: Karpenter or EKS Auto Mode with consolidation enabled (AWS-preferred path)
- 🟡 AMBER: Cluster Autoscaler present (legacy — consider migration to Karpenter), or Karpenter without consolidation
- 🔴 RED: No cluster autoscaling — manual node management
- ⬜ UNKNOWN: Cannot determine scale-up latency without testing
---
7.2 — Horizontal Pod Autoscaler (HPA)
What to check:
- HPAs across all namespaces (targets, min/max, current replicas)
- Multi-replica deployments without HPA
- HPAs with minReplicas=1 (single point of failure)
- KEDA ScaledObjects
- VPA resources
How to check: 1. List HPAs across all namespaces → check minReplicas, maxReplicas, current metrics 2. List Deployments with replicas > 1 → cross-reference with HPA targets 3. List HPAs where minReplicas == 1 (single point of failure for production workloads; acceptable for dev/staging) 4. List ScaledObjects (KEDA CRD). If 404/NotFound → KEDA not installed, skip. If 403/Forbidden → mark UNKNOWN. 5. List VPA resources. If 404/NotFound → VPA not installed, skip. If 403/Forbidden → mark UNKNOWN.
Rating:
- 🟢 GREEN: HPAs on stateless production workloads, min >= 2, tested under load
- 🟡 AMBER: HPAs exist but min=1, or some workloads missing HPA
- 🔴 RED: No HPAs — all workloads at fixed replica count
- ⬜ UNKNOWN: Cannot determine if HPAs have been load-tested
---
7.3 — Pod Topology Spread & AZ Resilience
What to check:
- Node distribution across AZs
- Deployments with topology spread constraints
- Deployments with pod anti-affinity
- Multi-replica deployments with neither (vulnerable to AZ failure)
How to check: 1. List nodes → group by label topology.kubernetes.io/zone 2. List Deployments → check spec.template.spec.topologySpreadConstraints 3. List Deployments → check spec.template.spec.affinity.podAntiAffinity 4. List multi-replica Deployments with neither topology spread nor anti-affinity
Rating:
- 🟢 GREEN: Nodes in 3 AZs, topology spread on production deployments
- 🟡 AMBER: Nodes in multiple AZs but no topology spread constraints
- 🔴 RED: Single-AZ deployment, or multi-replica services with no AZ spread
- ⬜ UNKNOWN: Cannot verify actual pod distribution without checking pod-to-node mapping
Key talking point: Having nodes in 3 AZs doesn't mean pods are spread. Without topology spread constraints, the scheduler may pack all pods into one AZ.
Cluster Lifecycle & Upgrades
Purpose
Assess EKS cluster version currency, data plane alignment, upgrade readiness, add-on compatibility, and upgrade process maturity.
EKS Version Support Calendar
Last verified: 2026-04-24. This table can become stale. Before rating version currency, cross-check against the official EKS version calendar.
Use this table to determine the ACTUAL support status. Do NOT guess or use training data.
| Version | Standard Support Until | Extended Support Until | Current Status |
|---|---|---|---|
| 1.35 | March 27, 2027 | March 27, 2028 | ✅ STANDARD (latest) |
| 1.34 | December 2, 2026 | December 2, 2027 | ✅ STANDARD |
| 1.33 | July 29, 2026 | July 29, 2027 | ✅ STANDARD |
| 1.32 | March 23, 2026 | March 23, 2027 | ✅ STANDARD |
| 1.31 | November 26, 2025 | November 26, 2026 | ⚠️ EXTENDED (standard ended) |
| 1.30 | July 23, 2025 | July 23, 2026 | ⚠️ EXTENDED (standard ended) |
| 1.29 | March 23, 2025 | March 23, 2026 | 🔴 EXTENDED (ending soon) |
CRITICAL: The upgradePolicy.supportType field from the API is a CONFIGURATION PREFERENCE, not the current billing status. A cluster on v1.35 with supportType: EXTENDED is still on standard support and NOT paying the extended premium. Always determine actual support status from the calendar above based on the cluster version.
Checks to Execute
1.1 — Control Plane Version Currency
What to check:
- Describe the cluster to get the current Kubernetes version and platform version
- Look up the version in the calendar above to determine actual support status
- Report the actual support status, NOT the
supportTypeAPI field
How to check: 1. Describe the cluster → get version and platformVersion 2. Match the version against the calendar table above 3. Report: version, standard/extended status, and when current support period ends
Version out-of-range guard: If the cluster version is higher than the highest version in this table (currently 1.35), rate GREEN — a newer version is by definition on standard support. Add a note: "Version 1.X is newer than the reference table (last verified YYYY-MM-DD). Rated GREEN as latest. Update the table when convenient."
Rating:
- 🟢 GREEN: On v1.35 or v1.34 (latest or N-1, standard support), OR version newer than table
- 🟡 AMBER: On v1.33 or v1.32 (standard support but older)
- 🔴 RED: On v1.31 or below (extended support — paying $0.60/hr vs $0.10/hr, 6x premium)
- ⬜ UNKNOWN: Cannot determine (should not happen with live access)
Key talking point: Extended support costs $0.60/hr vs $0.10/hr — that's ~$4,300/month extra per cluster.
---
1.2 — Data Plane Version Alignment
What to check:
- List all node groups and their Kubernetes versions
- Compare each node group version against the control plane version
- Check AMI type (AL2 vs AL2023 vs Bottlerocket)
- Check for Karpenter NodePools or EKS Auto Mode
How to check: 1. List node groups → describe each for version, AMI type, capacity type 2. List nodes via Kubernetes API → get kubelet versions 3. Check for Karpenter NodePools (nodepools.karpenter.sh). If 404/NotFound (CRD not installed) → Karpenter is not deployed, rate based on other node management. If 403/Forbidden → mark Karpenter status UNKNOWN. 4. Describe cluster → check computeConfig for Auto Mode
Rating:
- 🟢 GREEN: All nodes within N-1 of control plane, using managed node groups/Karpenter/Auto Mode
- 🟡 AMBER: Within skew policy but mixed versions, or self-managed nodes
- 🔴 RED: Any node beyond N-2 skew, or no visibility into node versions
- ⬜ UNKNOWN: No nodes found (possible if cluster is new or uses Fargate only)
Red flags: AL2 AMIs (approaching EOL), self-managed nodes with no automated upgrade path.
---
1.3 — Upgrade Readiness & Deprecated API Detection
What to check:
- EKS Cluster Insights for upgrade blockers
- Presence of deprecated API usage
- PodSecurityPolicy resources (removed in K8s 1.25)
How to check: 1. Get EKS Insights → filter for UPGRADE_READINESS category 2. List any PodSecurityPolicy resources via Kubernetes API 3. Check for Helm releases in kube-system (may use deprecated APIs)
Rating:
- 🟢 GREEN: No critical insights, no deprecated API usage detected
- 🟡 AMBER: WARNING-level insights, or no automated detection tooling
- �� RED: CRITICAL/ERROR insights, deprecated APIs actively in use
- ⬜ UNKNOWN: Insights API not accessible
---
1.4 — Add-on Version Compatibility
What to check:
- List all EKS managed add-ons with versions and health
- Compare installed versions against latest compatible for the cluster version
- Check for self-managed add-ons in kube-system (Helm releases)
How to check: 1. List addons → describe each for version, status, health 2. For each core add-on (vpc-cni, coredns, kube-proxy, aws-ebs-csi-driver), compare installed vs latest compatible
Rating:
- 🟢 GREEN: All core add-ons are EKS Managed and on latest or N-1 compatible version
- 🟡 AMBER: Managed but behind, or mix of managed and self-managed
- 🔴 RED: Core add-ons self-managed with no version tracking, or health issues present
- ⬜ UNKNOWN: Cannot list add-ons
Key talking point: EKS does NOT auto-update add-ons when you upgrade the control plane. This is the #1 thing customers forget.
---
1.5 — Upgrade Process Maturity
What to check (target cluster only):
- Cluster tags for environment classification (dev, staging, prod)
- Evidence of IaC-managed upgrades (eksctl, CloudFormation, Terraform tags)
How to check: 1. Describe the target cluster → check tags for environment indicators
Do NOT list or describe other clusters in the account. Stay within the scope of the target cluster.
Rating:
- 🟢 GREEN: Cluster has environment tags, evidence of IaC management
- 🟡 AMBER: No environment tags, or unclear upgrade process
- 🔴 RED: No evidence of structured upgrade process
- ⬜ UNKNOWN: Cannot determine upgrade history from API alone — suggest user investigate
Investigate manually: Do you have a documented upgrade runbook? Do you test upgrades on a non-prod cluster first? Can more than one person execute an upgrade?
Deployment Practices
Purpose
Assess deployment strategies, CI/CD integration, and graceful shutdown configuration.
Automation Note
CI/CD pipeline details (approval gates, post-deployment tests) are not fully detectable from cluster state. The skill checks for tool presence and configuration; process maturity items are marked UNKNOWN.
Checks to Execute
8.1 — Deployment Strategy & Rollback
What to check:
- Deployment strategies in use (RollingUpdate vs Recreate)
- maxUnavailable and maxSurge settings (defaults of 25% are risky for replicas <= 4 — e.g., 25% of 2 = 0, meaning no controlled rollout. Recommend maxUnavailable: 0, maxSurge: 1 for small deployments)
- Argo Rollouts resources
- Flagger Canary resources
- terminationGracePeriodSeconds and preStop hooks
How to check: 1. List Deployments → inspect spec.strategy.type, rollingUpdate.maxUnavailable, rollingUpdate.maxSurge 2. Flag deployments with replicas <= 4 and default maxUnavailable: 25% 3. List Rollouts (Argo Rollouts CRD, if exists) 4. List Canaries (Flagger CRD, if exists) 5. Inspect Deployments for terminationGracePeriodSeconds and lifecycle.preStop
Rating:
- 🟢 GREEN: Zero-downtime strategy (maxUnavailable: 0), graceful shutdown configured
- 🟡 AMBER: Rolling update but default settings, or no progressive delivery
- 🔴 RED: Deployments cause downtime, no graceful shutdown
- ⬜ UNKNOWN: Cannot determine rollback speed or process — suggest user investigate
---
8.2 — CI/CD Pipeline Integration
What to check:
- ECR repositories: scan-on-push, tag immutability
- Admission webhooks enforcing image policies
- Image registries in use (ECR vs public)
How to check: 1. Describe ECR repositories → scanOnPush, imageTagMutability 2. List ValidatingWebhookConfigurations → filter for image/policy-related names 3. List running pods → aggregate image registries
Rating:
- 🟢 GREEN: Images scanned in CI, private registry, admission enforcement
- 🟡 AMBER: Pipeline exists but no scanning, or no admission enforcement
- 🔴 RED: No CI/CD evidence, images from untrusted public registries
- ⬜ UNKNOWN: Cannot determine full pipeline from cluster state — suggest user investigate
---
8.3 — Graceful Shutdown & Connection Draining
What to check:
- Deployments with preStop hooks vs without
- terminationGracePeriodSeconds (default 30s vs customized)
- Services with AWS Load Balancer annotations (deregistration delay matters)
- Ingress resources with target group attributes
How to check: 1. List Deployments → count those with lifecycle.preStop vs without 2. List Deployments → check terminationGracePeriodSeconds (null = default 30s) 3. List Services → check for service.beta.kubernetes.io/aws-load-balancer-type annotation 4. List Ingresses → check for alb.ingress.kubernetes.io/target-group-attributes annotation (deregistration delay should match or exceed terminationGracePeriodSeconds)
Rating:
- 🟢 GREEN: preStop hooks on all externally-facing deployments, grace period tuned, LB drain aligned
- 🟡 AMBER: Some deployments have preStop but not all
- 🔴 RED: No preStop hooks and experiencing 502s, or grace period too short
- ⬜ UNKNOWN: Cannot determine if 502s occur during deployments — suggest user investigate
Key talking point: There's a race condition during pod termination. The LB still sends traffic for a few seconds after SIGTERM. A preStop sleep of 5-10s fixes it.
Infrastructure as Code & GitOps
Purpose
Assess whether cluster infrastructure and workload deployments are reproducible, auditable, and version-controlled.
Automation Note
This section is only partially automatable. The skill can detect tool presence (ArgoCD, Flux, CloudFormation stacks, cluster tags) but cannot assess process maturity (PR reviews, pipeline enforcement). Process-dependent items are marked UNKNOWN.
Checks to Execute
2.1 — Cluster Provisioned via IaC
What to check:
- Cluster tags for IaC provenance (terraform, eksctl, cdk, aws:cloudformation:stack-name)
- CloudFormation stacks with "eks" or "EKS" in the name
How to check: 1. Describe cluster → inspect tags for IaC indicators (tags were already retrieved in Step 0 pre-flight — reuse that data, do NOT call manage_eks_stacks) 2. Look for tags: terraform, managed-by, aws:cloudformation:stack-name, eksctl.cluster.k8s.io/*, aws:cdk:*
Rating:
- 🟢 GREEN: Clear IaC provenance in tags (CloudFormation stack, Terraform tags, eksctl tags)
- 🟡 AMBER: IaC tags present but unclear if current, or eksctl-created (basic IaC)
- 🔴 RED: No IaC tags — cluster appears console/CLI-created
- ⬜ UNKNOWN: Tags alone cannot confirm if IaC is pipeline-driven or manually applied — suggest user verify
Investigate manually: Is IaC applied via CI/CD pipeline or manually? Could you recreate this cluster from code?
---
2.2 — Workload Deployment via GitOps or CI/CD
What to check:
- ArgoCD namespace and Application resources
- Flux namespace and Kustomization resources
- Other CD tools (Spinnaker, Tekton namespaces)
How to check: 1. List namespaces → check for argocd, flux-system, spinnaker, tekton-pipelines 2. If argocd namespace exists → list applications.argoproj.io resources, check sync status 3. If flux-system exists → list kustomizations.kustomize.toolkit.fluxcd.io resources
Rating:
- 🟢 GREEN: GitOps tool active with apps in-sync
- 🟡 AMBER: GitOps tool installed but apps out-of-sync, or CI/CD present but no GitOps
- 🔴 RED: No GitOps or CI/CD tools detected
- ⬜ UNKNOWN: No GitOps tools found — could still have external CI/CD. Suggest user verify: how do teams deploy workloads?
---
2.3 — Configuration Drift Detection & Remediation
What to check:
- ArgoCD auto-sync and self-heal settings
- Flux reconciliation status
How to check: 1. If ArgoCD present → read Application resources, check spec.syncPolicy.automated for selfHeal: true 2. If Flux present → check kustomization ready status
Rating:
- 🟢 GREEN: GitOps with self-heal enabled, all apps in-sync
- 🟡 AMBER: GitOps present but no self-heal, or some apps out-of-sync
- 🔴 RED: No drift detection mechanism
- ⬜ UNKNOWN: No GitOps tools found
---
2.4 — Access Control & RBAC Defined in Code
What to check:
- Authentication mode (API, CONFIG_MAP, API_AND_CONFIG_MAP)
- EKS Access Entries
- ClusterRoleBindings to cluster-admin
- Whether RBAC resources have GitOps labels
How to check: 1. Describe cluster → accessConfig.authenticationMode 2. List access entries 3. List ClusterRoleBindings → filter for roleRef.name == "cluster-admin" 4. Check ClusterRoles/ClusterRoleBindings for labels indicating Helm/ArgoCD management
Rating:
- 🟢 GREEN: API mode with Access Entries, RBAC managed by GitOps, cluster-admin limited
- 🟡 AMBER: API_AND_CONFIG_MAP (transitional), or RBAC partially in code
- 🔴 RED: CONFIG_MAP only with manual edits, broad cluster-admin access
- ⬜ UNKNOWN: Cannot determine if RBAC changes go through PR review — suggest user verify
Networking
Purpose
Assess VPC CNI configuration, IP capacity, DNS health, and network segmentation.
Checks to Execute
6.1 — VPC and Subnet IP Capacity
What to check:
- Subnets used by the cluster and available IP count
- VPC CNI configuration: prefix delegation, custom networking, WARM_IP_TARGET
- Current pod count vs IP capacity
- VPC CNI add-on version
How to check: 1. Describe cluster → get subnet IDs from resourcesVpcConfig.subnetIds 2. Get VPC config for the cluster (available IPs per subnet) 3. List pods (Running) → count total 4. List nodes → count total 5. Describe addon vpc-cni → check version and configuration 6. Check DaemonSet aws-node in kube-system → inspect env vars for ENABLE_PREFIX_DELEGATION, AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG 7. List ENIConfig resources (custom networking indicator)
Rating:
- 🟢 GREEN: >30% IP headroom, prefix delegation or custom networking enabled
- 🟡 AMBER: Adequate IPs now but no prefix delegation and cluster is growing
- 🔴 RED: <15% IPs available, or past IP exhaustion incidents
- ⬜ UNKNOWN: Cannot determine subnet sharing with other workloads
Key talking point: Prefix delegation assigns a /28 (16 IPs) per ENI slot instead of 1 IP — dramatically increases pod density.
---
6.2 — CoreDNS Health and Scaling
What to check:
- CoreDNS deployment: replica count, resource requests, pod placement
- Node count (to assess CoreDNS ratio — ~1 replica per 8 nodes, minimum 2)
- NodeLocal DNSCache DaemonSet
- CoreDNS HPA
- CoreDNS topology spread constraints
How to check: 1. Read Deployment coredns in kube-system → replicas, resources, topologySpreadConstraints 2. List pods with label k8s-app=kube-dns → check node placement 3. Count nodes 4. List DaemonSets → check for node-local-dns or nodelocaldns (recommended for clusters with 50+ nodes) 5. List HPAs in kube-system with label k8s-app=kube-dns (if HPA present, CoreDNS can auto-scale so fewer static replicas is acceptable)
Rating:
- 🟢 GREEN: CoreDNS scaled to cluster size (~1 replica per 8 nodes, min 2), spread across AZs, NodeLocal DNSCache on clusters with 50+ nodes
- 🟡 AMBER: Adequate replicas but no topology spread, or no NodeLocal DNSCache on 50+ node clusters, or no HPA
- 🔴 RED: CoreDNS under-provisioned (2 replicas for 50+ nodes with no HPA), or past DNS incidents
- ⬜ UNKNOWN: Cannot determine if DNS issues have occurred historically
---
6.3 — Network Policies & Segmentation
What to check:
- VPC CNI Network Policy Controller enabled (
ENABLE_NETWORK_POLICYenv var on aws-node) - Calico pods (alternative enforcement engine)
- NetworkPolicy resources across namespaces
- Default-deny policies (podSelector: {})
- Namespaces without any NetworkPolicy
How to check: 1. Read DaemonSet aws-node in kube-system → check env var ENABLE_NETWORK_POLICY 2. List pods with label k8s-app=calico-node 3. List NetworkPolicies across all namespaces 4. Inspect NetworkPolicies for default-deny (empty podSelector) 5. Compare namespaces with policies vs namespaces without
Rating:
- 🟢 GREEN: Enforcement enabled (VPC CNI controller or Calico), default-deny in production namespaces
- 🟡 AMBER: Policies defined but enforcement not verified, or policies in some namespaces only
- 🔴 RED: No network policies, or policies defined but enforcement not enabled (false security)
- ⬜ UNKNOWN: Cannot determine if policies have been tested
Critical gotcha: VPC CNI requires explicitly enabling the Network Policy Controller. Without it, NetworkPolicy resources are just YAML that does nothing.
Observability
Purpose
Assess observability across three layers: control plane, data plane (nodes), and workloads — covering metrics, logs, and alerting.
Checks to Execute
4.1 — EKS Control Plane Logging
What to check:
- Which of the 5 log types are enabled (api, audit, authenticator, controllerManager, scheduler)
- CloudWatch log group existence and retention policy
How to check: 1. Describe cluster → logging.clusterLogging → check each entry for enabled: true and which types 2. Use CloudWatch tools to check log group /aws/eks/{cluster-name}/cluster retention (recommend >= 30 days for audit logs; no retention policy = logs kept forever at cost)
Rating:
- 🟢 GREEN: All 5 log types enabled with defined retention policy
- 🟡 AMBER: Some log types enabled (especially if audit is on), or no retention policy
- 🔴 RED: Control plane logging completely disabled, or audit logs specifically disabled
- ⬜ UNKNOWN: Should not happen with live access
Key talking point: EKS control plane logging is OFF by default. The audit log is your security camera for every API call.
---
4.2 — Metrics Collection & Dashboards
What to check:
- CloudWatch Container Insights add-on (
amazon-cloudwatch-observability) - Prometheus pods (labels:
app.kubernetes.io/name=prometheusorapp=prometheus) - Grafana pods
- kube-state-metrics deployment (critical for cluster state visibility)
- node-exporter DaemonSet
- ADOT add-on
- Third-party monitoring DaemonSets (Datadog, New Relic, Dynatrace)
How to check: 1. Describe addon amazon-cloudwatch-observability 2. List pods with label app.kubernetes.io/name=prometheus across all namespaces 3. List pods with label app.kubernetes.io/name=grafana 4. List pods with label app.kubernetes.io/name=kube-state-metrics 5. List DaemonSets across all namespaces (catches node-exporter and third-party agents)
Rating:
- 🟢 GREEN: Metrics collection + kube-state-metrics + dashboards (Container Insights or Prometheus+Grafana or third-party)
- 🟡 AMBER: Partial stack (e.g., Container Insights but no kube-state-metrics, or Prometheus without Grafana)
- 🔴 RED: No metrics collection at all
- ⬜ UNKNOWN: Cannot determine if dashboards are actively used
---
4.3 — Centralized Log Aggregation for Workloads
What to check:
- Fluent Bit DaemonSet (labels:
app.kubernetes.io/name=fluent-bitork8s-app=fluent-bit) - Fluentd DaemonSet
- CloudWatch agent DaemonSet in
amazon-cloudwatchnamespace - Application log groups in CloudWatch
How to check: 1. List DaemonSets with Fluent Bit labels across all namespaces 2. List DaemonSets in amazon-cloudwatch namespace 3. Use CloudWatch tools to check for log groups with prefix /aws/eks/{cluster-name}
Rating:
- 🟢 GREEN: Log shipper deployed, logs centralized with retention policy, structured logging
- 🟡 AMBER: Log shipper exists but no retention policy, or unstructured logging
- 🔴 RED: No centralized log collection — teams rely on kubectl logs
- ⬜ UNKNOWN: Cannot determine log format (structured vs unstructured) without sampling
---
4.4 — Alerting Defined and Actionable
What to check:
- CloudWatch Alarms related to EKS/ContainerInsights
- Prometheus Alertmanager pods
- PrometheusRule resources (alert definitions)
How to check: 1. List pods with label app.kubernetes.io/name=alertmanager 2. List PrometheusRule resources. If 404/NotFound (CRD not installed) → Prometheus Operator not deployed, rate alerting based on CloudWatch only. If 403/Forbidden → mark UNKNOWN. 3. Use CloudWatch tools to list alarms with ContainerInsights namespace
Rating:
- 🟢 GREEN: Alerts cover critical scenarios (node, pod, capacity), routed to on-call
- 🟡 AMBER: Some alerts exist but incomplete coverage, or no runbooks linked
- 🔴 RED: No alerting configured
- ⬜ UNKNOWN: Cannot determine if alerts have runbooks or if on-call monitors them — suggest user investigate
Minimum viable alert set: NodeNotReady, PodCrashLooping, PodPendingTooLong, HighAPIServerLatency, IPExhaustion.
Operational Processes
Purpose
Assess operational process maturity: runbooks, on-call, incident response, and disaster recovery.
Automation Note
This section is mostly NOT automatable from cluster state. The skill checks for tool presence (Velero, AWS Backup, AWS Support tier) and current cluster health indicators. Process maturity (runbooks, on-call rotation, PIR process) cannot be detected — these items are marked UNKNOWN with suggestions for what to investigate on your own.
Checks to Execute
9.1 — Runbooks for Common Failure Scenarios
What to check (cluster health indicators that suggest which runbooks should exist):
- Nodes not in Ready state
- Pods not Running (excluding Completed jobs)
- Recent Warning events
- CrashLoopBackOff pods, Pending pods, OOMKilled events, FailedScheduling events
How to check: 1. List nodes → check for any not Ready 2. List pods with field selector status.phase=Pending 3. Get events with type=Warning (recent) 4. Get events with reason=BackOff, OOMKilling, FailedScheduling
Rating:
- ⬜ UNKNOWN: Cannot determine if runbooks exist from cluster state.
Investigate manually:
- Do you have runbooks for node NotReady, CrashLoopBackOff, IP exhaustion, DNS failures?
- Are alerts linked directly to runbooks?
- When was the last time a runbook was updated?
If active issues found: Note them as evidence that runbooks for those scenarios should exist and be tested.
---
9.2 — On-Call Rotation & Escalation
What to check:
- AWS Support plan tier (Business/Enterprise = Support API accessible)
How to check: 1. This check is limited — AWS Support API access indicates Business or Enterprise plan
Rating:
- ⬜ UNKNOWN: Primarily a process question.
Investigate manually:
- Do you have a formal on-call rotation?
- What's the escalation path when on-call can't resolve within 30 minutes?
- What AWS Support plan are you on?
- How many people can handle a critical EKS incident independently?
---
9.3 — Post-Incident Review Process
What to check:
- Recent significant events (NodeNotReady, BackOff, rollbacks) that would warrant a PIR
How to check: 1. Get events with reason=NodeNotReady 2. Get events with reason=DeploymentRollback
Rating:
- ⬜ UNKNOWN: Cannot determine PIR process from cluster state.
Investigate manually:
- Do you conduct blameless post-mortems after incidents?
- Are action items tracked to completion?
- Can you point to a change made as a result of a post-incident review?
---
9.4 — Disaster Recovery & Backup Strategy
What to check:
- Velero pods and backup schedules
- AWS Backup plans
- VolumeSnapshot resources
- StatefulSets and PVCs (data at risk if no backup)
How to check: 1. List pods in velero namespace 2. List Backup resources (backups.velero.io) and Schedule resources (schedules.velero.io) 3. List VolumeSnapshots across all namespaces 4. List StatefulSets across all namespaces 5. List PVCs across all namespaces → count
Rating:
- 🟢 GREEN: Backup tool in place, scheduled backups running, restore tested
- 🟡 AMBER: Backups exist but never tested, or only PV data backed up
- 🔴 RED: Stateful workloads with no backup strategy
- N/A: No stateful workloads and all config is in Git/IaC
- ⬜ UNKNOWN: Cannot determine if restore has been tested — suggest user investigate
Report Generation
Purpose
After all section checks are complete, generate the EKS Operation Review report.
Consistency Checks (MANDATORY before writing)
Before writing the report, validate consistency:
1. Build a master list of all findings with their ratings from sections 01-10 2. For each RED item: confirm it appears in "Critical" or "Important" prioritized actions 3. For each AMBER item: confirm it appears in "Important" or "Quick Wins" 4. For the Executive Summary: only mention ratings that match the master list — do not call something a "critical gap" if it's AMBER, or omit a RED from the summary 5. For Prioritized Actions: every entry must reference the finding ID (e.g., "4.1 — Control Plane Logging")
Workflow
Step 1: Build Master Finding List
| Section | Item ID | Item Name | Rating |Step 2: Calculate Maturity Score
- Count GREEN, AMBER, RED, UNKNOWN
- Calculate percentages (exclude UNKNOWN from denominator)
Step 3: Write Executive Summary
From the master list, identify:
- Top strengths (GREEN items with highest operational impact)
- Top gaps (RED items, ordered by blast radius: security > availability > cost)
- Write 2-3 paragraphs. Every rating mentioned must match the master list.
Step 4: Write Findings Tables
One table per section. Every item from the master list must appear.
Step 5: Write Prioritized Actions
Cross-reference against the master list:
- Critical (30 days): All RED items. Column:
Finding | Action | References - Important (90 days): All AMBER items. Column:
Finding | Action | References - Quick Wins: Items (RED or AMBER) fixable in < 1 hour. Column:
Finding | Action | Effort | Impact | References
Every entry must include the finding ID and name (e.g., "4.1 — Control Plane Logging 🔴").
One row per finding. Never bundle multiple findings into a single row (e.g., "2.2/2.3 — GitOps & Drift Detection"). Each finding has its own context, action, and references — collapsing them hides information and breaks the "every RED must appear in Critical" consistency rule. If two findings genuinely share an action, list them on separate rows that point to the same action.
Ordering within Critical: List RED items by blast radius category: 1. Security first — public API endpoint, hardcoded credentials, no PSA/network policies, overly broad RBAC 2. Availability next — no PDBs, single-replica critical workloads, missing health probes, no alerting 3. Cost last — extended support billing, deprecated storage classes
Within each category, order by scope (cluster-wide before namespace-scoped).
Step 6: Write Investigate Manually
All UNKNOWN items with specific questions the user should answer.
Step 7: Apply AWS Reference Links
Use the pre-verified reference map below. Do NOT call the AWS Documentation MCP server — it adds latency and token cost with minimal benefit.
Section 01 — Cluster Lifecycle & Upgrades
- Version calendar:
https://docs.aws.amazon.com/eks/latest/userguide/kubernetes-versions.html - Upgrade cluster:
https://docs.aws.amazon.com/eks/latest/userguide/update-cluster.html - Best practices for upgrades:
https://docs.aws.amazon.com/eks/latest/best-practices/cluster-upgrades.html - Platform versions:
https://docs.aws.amazon.com/eks/latest/userguide/platform-versions.html - Managed node groups:
https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html - EKS Auto Mode:
https://docs.aws.amazon.com/eks/latest/userguide/automode.html
Section 02 — Infrastructure as Code & GitOps
- EKS User Guide (general):
https://docs.aws.amazon.com/eks/latest/userguide/ - Best practices (general):
https://docs.aws.amazon.com/eks/latest/best-practices/
Section 03 — Access & Identity
- IRSA:
https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html - EKS Pod Identity:
https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html - Access entries:
https://docs.aws.amazon.com/eks/latest/userguide/access-entries.html - Grant K8s access:
https://docs.aws.amazon.com/eks/latest/userguide/grant-k8s-access.html - RBAC hardening:
https://docs.aws.amazon.com/eks/latest/userguide/rbac-hardening.html - API server endpoint:
https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html - Security best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/security.html - Pod Security Standards:
https://docs.aws.amazon.com/eks/latest/best-practices/pod-security.html
Section 04 — Observability
- Control plane logging:
https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html - Observability overview:
https://docs.aws.amazon.com/eks/latest/userguide/eks-observe.html
Section 05 — Workload Configuration
- EBS CSI driver:
https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html - Reliability best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/reliability.html
Section 06 — Networking
- VPC CNI:
https://docs.aws.amazon.com/eks/latest/userguide/managing-vpc-cni.html - Prefix delegation:
https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html - Custom networking:
https://docs.aws.amazon.com/eks/latest/userguide/cni-custom-network.html - CoreDNS:
https://docs.aws.amazon.com/eks/latest/userguide/managing-coredns.html - Networking best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/networking.html
Section 07 — Autoscaling
- Karpenter best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/karpenter.html - Scalability best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/scalability.html - Cost optimization:
https://docs.aws.amazon.com/eks/latest/best-practices/cost-opt.html
Section 08 — Deployment Practices
- Reliability best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/reliability.html
Section 09 — Operational Processes
- Reliability best practices:
https://docs.aws.amazon.com/eks/latest/best-practices/reliability.html
Section 10 — Add-on Management
- Managed add-ons:
https://docs.aws.amazon.com/eks/latest/userguide/managing-add-ons.html - Node health & auto-repair:
https://docs.aws.amazon.com/eks/latest/userguide/node-health.html
Fallback (any topic):
- EKS Best Practices Guide:
https://docs.aws.amazon.com/eks/latest/best-practices/ - EKS User Guide:
https://docs.aws.amazon.com/eks/latest/userguide/
Do NOT fabricate URLs beyond this list. If a finding doesn't match a specific URL above, use the fallback section-level page.
Step 8: Final Consistency Validation
Before outputting, scan the report for:
- Any RED item missing from Prioritized Actions → add it
- Any item mentioned in Executive Summary with wrong rating → fix it
- Any Prioritized Action without a finding ID → add the ID
Step 8b: Append Sample-Code Disclaimer
Add the following footer at the very end of the report, after the AWS Reference Links section, separated by a horizontal rule:
---
*This report was generated by a Claude Code skill provided as sample code for educational and demonstration purposes only. Findings should be reviewed and validated before acting on them. See the project's README and LICENSE for full terms.*Step 9: Write the Report File
Write the report to the workspace directory. The file must be created inside the current workspace.
Filename format: EKS-Operation-Review-<cluster-name>-<YYYY-MM-DD>-<HHMM>.md
Example: EKS-Operation-Review-demo-cluster-2026-03-22-1830.md
The file should be written to the workspace root or a reports/ subfolder within the workspace. Do NOT use absolute paths outside the workspace.
Step 10: Offer HTML Conversion
Ask: "Would you like me to convert the report to HTML?"
If yes, run the conversion script — do NOT generate HTML manually. Execute this command:
python3 report_to_html.py <report-filename>.mdRun this from the workspace root where report_to_html.py is located. If the script is not found in the workspace root, check tools/report_to_html.py.
Do NOT create HTML by hand. Always use the script.
Workload Configuration
Purpose
Assess workload resilience: resource requests/limits, health probes, disruption budgets, image hygiene, and storage configuration.
Checks to Execute
5.1 — Resource Requests and Limits
What to check:
- Running pods missing resource requests or limits
- LimitRange resources in namespaces
- ResourceQuota resources in namespaces
- Recent OOMKilled events
- Admission webhooks enforcing resources (Kyverno, Gatekeeper)
How to check: 1. List pods (Running) across all namespaces → inspect spec.containers[].resources.requests and .limits 2. Count pods with no requests vs total running pods → calculate percentage 3. List LimitRange resources across all namespaces 4. List ResourceQuota resources across all namespaces 5. Get events with reason=OOMKilling (count occurrences — >5 in recent events = AMBER, >20 = RED) 6. List ValidatingWebhookConfigurations and MutatingWebhookConfigurations
Rating:
- 🟢 GREEN: >90% of pods have requests, LimitRange/ResourceQuota in place, admission enforcement
- 🟡 AMBER: Most pods have requests but no enforcement mechanism, or frequent OOMKills
- 🔴 RED: Majority of pods missing requests, no LimitRange, no enforcement
- ⬜ UNKNOWN: Should not happen with live access
Key talking point: Without resource requests, the scheduler is flying blind. Don't set CPU limits equal to requests — causes unnecessary throttling.
---
5.2 — Health Probes Configured
What to check:
- Deployments missing readiness probes
- Deployments missing liveness probes
- Deployments missing startup probes (important for slow-starting apps: JVM/Java, Kotlin, Scala, or apps with long initialization >10s)
- Pods in CrashLoopBackOff (may indicate bad liveness probes)
How to check: 1. List Deployments across all namespaces → inspect containers for readinessProbe, livenessProbe, startupProbe 2. Count deployments missing each probe type 3. List pods not in Running/Succeeded phase → check for CrashLoopBackOff
Rating:
- 🟢 GREEN: >90% of deployments have readiness probes, startup probes on slow-starting apps
- 🟡 AMBER: Readiness probes on most but not all, or no startup probes for JVM apps
- 🔴 RED: Majority of deployments missing readiness probes
- ⬜ UNKNOWN: Cannot determine if apps are slow-starting without more context
---
5.3 — Pod Disruption Budgets (PDBs)
What to check:
- PDB resources and their settings
- Multi-replica deployments without PDBs
- PDBs with disruptionsAllowed=0 (blocks upgrades). If disruptionsAllowed=0 AND replicas=1, mark RED (single point of failure that also blocks node drains)
- Single-replica deployments (inherently not disruption-safe)
How to check: 1. List PodDisruptionBudgets across all namespaces → check minAvailable, maxUnavailable, disruptionsAllowed 2. List Deployments with replicas > 1 → compare against PDB coverage 3. List Deployments with replicas == 1
Rating:
- 🟢 GREEN: PDBs on all multi-replica production deployments with reasonable settings
- 🟡 AMBER: PDBs on some but not all, or some PDBs blocking disruptions
- 🔴 RED: No PDBs at all, or critical workloads running single-replica
- ⬜ UNKNOWN: Cannot determine which deployments are "production" vs "dev"
---
5.4 — Image Tag Hygiene
What to check:
- Running pods using
:latesttag or no tag - ECR repositories: tag immutability and scan-on-push settings
- Image registries in use (ECR vs Docker Hub vs other)
How to check: 1. List running pods → inspect container images for :latest or missing tag 2. Use AWS API to describe ECR repositories → check imageTagMutability and imageScanningConfiguration 3. Aggregate image registries from pod specs
Rating:
- 🟢 GREEN: No
:latestin production, ECR with tag immutability, scan-on-push enabled - 🟡 AMBER: Mostly versioned tags but some
:latest, or ECR without immutability - 🔴 RED:
:latestwidely used, or images from untrusted public registries - ⬜ UNKNOWN: Cannot determine if tags are mutable without ECR access
---
5.5 — Persistent Volume & Stateful Workload Configuration
What to check:
- StorageClasses: provisioner, reclaimPolicy, volumeBindingMode, gp2 vs gp3
- PVCs and their status
- CSI drivers installed
- EBS CSI driver add-on status
- VolumeSnapshotClasses (backup support)
- StatefulSets
- Deprecated in-tree volume plugin usage
How to check: 1. List StorageClasses → check for gp3 default, Retain policy, WaitForFirstConsumer 2. List PVCs across all namespaces 3. List CSIDrivers 4. Describe addon aws-ebs-csi-driver 5. List VolumeSnapshotClasses. If 404/NotFound (CRD not installed) → no snapshot support configured, factor into rating. If 403/Forbidden → mark snapshot capability UNKNOWN. 6. List StatefulSets across all namespaces 7. List PersistentVolumes → check for spec.awsElasticBlockStore (deprecated in-tree)
Rating:
- 🟢 GREEN: gp3, Retain policy, WaitForFirstConsumer, CSI driver managed, snapshots configured
- 🟡 AMBER: gp2 still in use, or Delete policy on production volumes, or no snapshots
- 🔴 RED: Deprecated in-tree plugin, Delete policy on databases, or no backup strategy
- N/A: No stateful workloads on EKS
- ⬜ UNKNOWN: Cannot determine if Delete policy is intentional (dev) vs accidental (prod)
#!/usr/bin/env python3
"""
EKS Operation Review — Markdown to HTML Converter
Converts assessment report markdown files to styled HTML.
No external dependencies required.
Usage:
python3 report_to_html.py <input.md> # outputs <input>.html
python3 report_to_html.py <input.md> -o <output.html> # custom output path
python3 report_to_html.py *.md # batch convert
"""
import re
import sys
import html
import urllib.parse
from pathlib import Path
_ALLOWED_URL_SCHEMES = {'http', 'https', 'mailto'}
CSS = """
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;line-height:1.6;color:#1a1a2e;background:#f0f2f5;padding:2rem}
main{max-width:1100px;margin:0 auto;background:#fff;border-radius:12px;box-shadow:0 4px 24px rgba(0,0,0,.08);padding:3rem}
h1{color:#0f1b61;border-bottom:3px solid #ff9900;padding-bottom:.5rem;margin-bottom:1.5rem;font-size:1.8rem}
h2{color:#232f3e;margin:2rem 0 1rem;padding:.5rem 0;border-bottom:2px solid #e8e8e8;font-size:1.4rem}
h3{color:#37475a;margin:1.5rem 0 .75rem;font-size:1.15rem}
h4{color:#37475a;margin:1.2rem 0 .5rem;font-size:1.05rem}
table{width:100%;border-collapse:collapse;margin:1rem 0 1.5rem;font-size:.9rem}
th{background:#232f3e;color:#fff;padding:10px 14px;text-align:left;font-weight:600}
td{padding:8px 14px;border-bottom:1px solid #e8e8e8;vertical-align:top}
tr:nth-child(even){background:#f8f9fa}
tr:hover{background:#fff3e0}
code{background:#f1f3f5;padding:2px 6px;border-radius:4px;font-size:.85em;color:#c7254e}
pre{background:#1a1a2e;color:#e8e8e8;padding:1rem;border-radius:8px;overflow-x:auto;margin:1rem 0}
pre code{background:none;color:inherit;padding:0}
blockquote{border-left:4px solid #ff9900;background:#fff8e1;padding:.75rem 1rem;margin:1rem 0;border-radius:0 8px 8px 0}
hr{border:none;border-top:2px solid #e8e8e8;margin:2rem 0}
a{color:#0073bb;text-decoration:none}a:hover{text-decoration:underline}
li{margin:.3rem 0 .3rem 1.5rem}
ul,ol{margin:.5rem 0 1rem}
strong{color:#16213e}
p{margin:.5rem 0}
.red{background:#fde8e8;color:#b71c1c;padding:2px 8px;border-radius:4px;font-weight:600;display:inline-block;font-size:.85em;min-width:60px;text-align:center}
.amber{background:#fff3e0;color:#e65100;padding:2px 8px;border-radius:4px;font-weight:600;display:inline-block;font-size:.85em;min-width:60px;text-align:center}
.green{background:#e8f5e9;color:#1b5e20;padding:2px 8px;border-radius:4px;font-weight:600;display:inline-block;font-size:.85em;min-width:60px;text-align:center}
.unknown{background:#e8eaf6;color:#283593;padding:2px 8px;border-radius:4px;font-weight:600;display:inline-block;font-size:.85em;min-width:60px;text-align:center}
.score-bar{display:flex;gap:4px;margin:1rem 0}
.score-bar div{height:28px;border-radius:4px;display:flex;align-items:center;justify-content:center;color:#fff;font-weight:600;font-size:.8rem}
.critical-box{background:#fde8e8;border:2px solid #ef5350;border-radius:8px;padding:1rem 1.25rem;margin:1rem 0}
.quick-win{background:#e8f5e9;border:2px solid #66bb6a;border-radius:8px;padding:1rem 1.25rem;margin:1rem 0}
.internal-banner{background:#fff3e0;border:2px solid #ff9900;border-radius:8px;padding:.75rem 1.25rem;margin:1rem 0;font-weight:600;color:#e65100}
@media print{body{background:#fff;padding:0}main{box-shadow:none;padding:1rem}}
"""
def escape(text):
return html.escape(text, quote=True)
def _safe_href(url):
"""Return the URL if its scheme is allowlisted, otherwise '#'.
Relative links (no scheme) and fragments are treated as safe.
Control characters are stripped because browsers historically ignored
them inside schemes, allowing `java\tscript:` to still execute.
"""
cleaned = ''.join(c for c in url.strip() if c >= ' ' and c != '\x7f')
try:
parsed = urllib.parse.urlparse(cleaned)
except ValueError:
return '#'
scheme = parsed.scheme.lower()
if scheme and scheme not in _ALLOWED_URL_SCHEMES:
return '#'
return cleaned
def _link_sub(match):
text = match.group(1)
url = _safe_href(match.group(2))
return f'<a href="{url}">{text}</a>'
def inline_format(text):
"""Process inline markdown: bold, code, links, emoji badges.
Input must already be HTML-escaped with quote=True by the caller.
Link hrefs are validated against a scheme allowlist; because `"` and
`&` in the URL are already escaped to `"`/`&`, cluster-derived
strings cannot break out of the href attribute.
"""
# Links: [text](url) — scheme-allowlisted, href-escaped
text = re.sub(r'\[([^\]]+)\]\(([^)]+)\)', _link_sub, text)
# Bold: **text**
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text)
# Inline code: `text`
text = re.sub(r'`([^`]+)`', r'<code>\1</code>', text)
# RAG emoji badges
text = text.replace('🔴', '<span class="red">RED</span>')
text = text.replace('🟡', '<span class="amber">AMBER</span>')
text = text.replace('🟢', '<span class="green">GREEN</span>')
text = text.replace('⬜', '<span class="unknown">UNKNOWN</span>')
return text
def parse_table(lines):
"""Convert markdown table lines to HTML table."""
if len(lines) < 2:
return ""
headers = [c.strip() for c in lines[0].strip('|').split('|')]
rows = []
for line in lines[2:]: # skip separator
cells = [c.strip() for c in line.strip('|').split('|')]
rows.append(cells)
out = '<table>\n<thead><tr>'
for h in headers:
out += f'<th>{inline_format(escape(h))}</th>'
out += '</tr></thead>\n<tbody>\n'
for row in rows:
out += '<tr>'
for cell in row:
out += f'<td>{inline_format(escape(cell))}</td>'
out += '</tr>\n'
out += '</tbody></table>\n'
return out
def convert(md_text):
"""Convert markdown to HTML."""
lines = md_text.split('\n')
out = []
i = 0
in_list = None # 'ul' or 'ol'
def close_list():
nonlocal in_list
if in_list:
out.append(f'</{in_list}>')
in_list = None
while i < len(lines):
line = lines[i]
stripped = line.strip()
# Fenced code block
if stripped.startswith('```'):
close_list()
lang_match = re.match(r'^```(\w+)?', stripped)
lang = lang_match.group(1) if lang_match and lang_match.group(1) else None
lang_attr = f' class="language-{lang}"' if lang else ''
i += 1
code_lines = []
while i < len(lines) and not lines[i].strip().startswith('```'):
code_lines.append(escape(lines[i]))
i += 1
i += 1 # skip closing ```
out.append(f'<pre><code{lang_attr}>{chr(10).join(code_lines)}</code></pre>')
continue
# Blank line
if not stripped:
close_list()
i += 1
continue
# Table: collect consecutive | lines
if stripped.startswith('|') and '|' in stripped[1:]:
close_list()
table_lines = []
while i < len(lines) and lines[i].strip().startswith('|'):
table_lines.append(lines[i])
i += 1
out.append(parse_table(table_lines))
continue
# Headings
m = re.match(r'^(#{1,4})\s+(.+)$', stripped)
if m:
close_list()
level = len(m.group(1))
text = inline_format(escape(m.group(2)))
out.append(f'<h{level}>{text}</h{level}>')
i += 1
continue
# Horizontal rule
if re.match(r'^-{3,}$', stripped) or re.match(r'^\*{3,}$', stripped):
close_list()
out.append('<hr>')
i += 1
continue
# Ordered list
m = re.match(r'^(\d+)\.\s+(.+)$', stripped)
if m:
if in_list != 'ol':
close_list()
in_list = 'ol'
out.append('<ol>')
out.append(f'<li>{inline_format(escape(m.group(2)))}</li>')
i += 1
continue
# Unordered list
if stripped.startswith('- ') or stripped.startswith('* '):
if in_list != 'ul':
close_list()
in_list = 'ul'
out.append('<ul>')
out.append(f'<li>{inline_format(escape(stripped[2:]))}</li>')
i += 1
continue
# Blockquote
if stripped.startswith('> '):
close_list()
out.append(f'<blockquote>{inline_format(escape(stripped[2:]))}</blockquote>')
i += 1
continue
# Internal banner detection
if '⚠️' in stripped and 'INTERNAL' in stripped.upper():
close_list()
out.append(f'<div class="internal-banner">{inline_format(escape(stripped))}</div>')
i += 1
continue
# Paragraph
close_list()
out.append(f'<p>{inline_format(escape(stripped))}</p>')
i += 1
close_list()
return '\n'.join(out)
def md_to_html(md_text, title="EKS Operation Review"):
body = convert(md_text)
return f"""<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>{escape(title)}</title>
<style>{CSS}</style></head><body><main>
{body}
</main></body></html>"""
def main():
args = sys.argv[1:]
if not args or args[0] in ('-h', '--help'):
print(__doc__.strip())
sys.exit(0)
output_path = None
files = []
i = 0
while i < len(args):
if args[i] == '-o' and i + 1 < len(args):
output_path = args[i + 1]
i += 2
else:
files.append(args[i])
i += 1
if not files:
print("Error: no input files specified", file=sys.stderr)
sys.exit(1)
for f in files:
p = Path(f)
if not p.exists():
print(f"Error: {f} not found", file=sys.stderr)
continue
md = p.read_text(encoding='utf-8')
# Extract title from first H1
m = re.search(r'^#\s+(.+)$', md, re.MULTILINE)
title = m.group(1) if m else p.stem
result = md_to_html(md, title)
if output_path and len(files) == 1:
out = Path(output_path)
else:
out = p.with_suffix('.html')
out.write_text(result, encoding='utf-8')
print(f"✓ {p.name} → {out.name}")
if __name__ == '__main__':
main()
Upstream Provenance
This skill is vendored from an upstream repo. Do not edit files here directly — your changes will be overwritten by the next sync.
| Field | Value |
|---|---|
| Source repo | https://github.com/aws-samples/sample-eks-operation-review-skill.git |
| Source path | repo root (SKILL.md, steering/, tools/, LICENSE) |
| Refresh command | ./misc/sync-eks-operation-review-skill.sh |
| License | See LICENSE (copied verbatim from upstream — MIT-0) |
Local modifications applied at sync time
The sync script applies four deterministic edits to upstream content:
1. `steering/` -> `references/` rename. Upstream's progressive-disclosure docs live under steering/, but apex already uses a top-level steering/ directory at the repo root for workflow orchestration (different concept). The sync script renames the directory on copy and rewrites all internal cross-refs from steering/ to references/ inside SKILL.md and the 11 progressive-disclosure files. This aligns the layout with the Anthropic skill spec's canonical name for "additional documentation agents read on demand."
2. `SKILL.md` description block-scalar flattening. Upstream uses YAML folded-scalar form (description: > followed by indented lines). Apex's catalog generator reads the same line as the key, so the catalog row would render just >. The sync flattens the block scalar to a single description: <one line> entry. Wording is preserved byte-for-byte; only the YAML representation changes.
3. `SKILL.md` body path fix. Upstream's body says "Read and follow .claude/commands/eks-operation-review.md" — that path doesn't exist in apex (the slash-command content is baked into apex's steering/workflows/eks-operation-review.md instead). The sync rewrites the reference to point at the apex workflow via the ~/.claude/apex-steering symlink convention used by other apex slash commands.
4. `SKILL.md` Prerequisites MCP line. Upstream lists "MCP servers configured in .mcp.json" as a prerequisite. Apex doesn't ship .mcp.json; MCP setup is handled by the eks-mcp-server skill. The sync rewrites the line to point users at that skill instead.
The upstream description wording is NOT rewritten — skill-creator's restrained-style content is already apex-compatible. Only the YAML representation is flattened (Edit #2).
Everything else is byte-for-byte from upstream.
To propose changes
Open a PR against the upstream repo: https://github.com/aws-samples/sample-eks-operation-review-skill.git
Then re-run the sync script here.
Related skills
FAQ
What does the output look like?
A rated report giving each of 10 areas a GREEN, AMBER, or RED rating with prioritized recommendations.
Can I scope it to one area?
Yes; it supports section-scoped reviews such as checking only networking or RBAC.