Skip to main content
Source

This page is generated from devops-agent/eks-recon/references/addons.md. Edit the source, not this page.

Module: Add-ons

Part of: eks-recon Purpose: Detect add-on inventory — EKS-managed add-ons, Helm releases, manifest-installed components

Table of Contents


Access Model

This module reads facts from two sources, both read-only:

  • AWS control-plane APIs (EKS) — the EKS-managed add-on inventory and per-add-on detail: eks:ListAddons, eks:DescribeAddon (installed version, status, serviceAccountRoleArn, health.issues, configurationValues) and eks:DescribeAddonVersions (latest version at the current Kubernetes version → update_available). Requires the read-only permissions in references/iam-policy.json.
  • Kubernetes API (via the Agent Space EKS access entry) — platform_components, core_components in-cluster delivery/managed_by, helm_releases, and CRDs. Requires authenticationMode to include API and the AmazonAIOpsAssistantPolicy access entry to be present. RBAC verbs needed: get, list.

If the Kubernetes API is unreachable (access entry absent), report the AWS-API facts (eks_managed, and core_components entries confirmed via the add-on API) and mark every K8s-dependent sub-fact (platform_components.*, helm_releases, self-managed core_components, crds) as unconfirmed in the report's Coverage section — never as false/count: 0.

Reference pseudocode note. Code blocks labeled reference pseudocode (kubernetes client) below illustrate the resource, fields, and RBAC verbs for each K8s-API read. They are not executable in the Agent Space and are not an operational path — do not emit kubectl ... | jq pipelines. The agent reads these resources through its Kubernetes-API capability.


Detection Strategy

Add-ons can be installed via multiple mechanisms:

1. EKS-managed add-ons -> AWS manages lifecycle, uses EKS add-on API (AWS API)
2. Helm releases -> Installed via Helm, tracked in Kubernetes secrets (K8s API)
3. Manifest-installed -> Raw YAML applied, no tracking metadata (K8s API)
4. Operator-managed -> CRDs and controllers, e.g. cert-manager (K8s API)

Detection Capabilities

1. EKS-Managed Add-ons

Via AWS API — list EKS-managed add-ons, then describe each one:

# List all EKS-managed add-ons
aws eks list-addons --cluster-name <cluster-name> --query 'addons'

# Get details for each add-on (includes serviceAccountRoleArn + health.issues)
aws eks describe-addon --cluster-name <cluster-name> --addon-name <addon-name> \
--query 'addon.{
name:addonName,
version:addonVersion,
status:status,
serviceAccountRoleArn:serviceAccountRoleArn,
configurationValues:configurationValues,
healthIssues:health.issues
}'
  • serviceAccountRoleArn = the Pod Identity / IRSA role bound to the add-on (null when none).
  • health.issues = AWS-reported add-on health issues, a list of {code, message, resourceIds}. Capture verbatim as a raw fact (empty list = no reported issues); draw no conclusion.

Core EKS add-ons to check:

  • vpc-cni (Amazon VPC CNI)
  • coredns (DNS resolution)
  • kube-proxy (Service networking)
  • aws-ebs-csi-driver (EBS volumes)
  • aws-efs-csi-driver (EFS volumes)
  • eks-pod-identity-agent (Pod Identity)
  • amazon-cloudwatch-observability (Container Insights)
  • aws-mountpoint-s3-csi-driver (S3 mounts)
  • snapshot-controller (Volume snapshots)

2. Helm Releases

Inventory third-party components installed via Helm charts. Many platform teams use Helm for add-ons not available as EKS-managed add-ons.

Helm stores each release's metadata as a Kubernetes Secret labeled owner=helm. In the Agent Space there is no helm CLI, so the Kubernetes-API read of these secrets IS the primary Helm-detection path (not a fallback).

Via Kubernetes API — list Helm release secrets:

  • Resource: Secret, group/version v1 (core), label selector owner=helm,status=deployed, all namespaces.
  • Fields to extract: label name → release name; metadata.namespace → namespace; label version → release revision (a counter, NOT the chart or app version).
  • RBAC verbs: get, list on secrets.

Chart / app_version note. The version label is the RELEASE REVISION only. Chart version and app_version are NOT available from labels alone — they live inside the gzipped release blob (.data.release). When only the secret labels are read, populate revision and leave chart/app_version null rather than guessing. Keep chart (chart name+version, e.g. external-dns-1.13.1), app_version (packaged app version, e.g. 0.13.6), and revision (release revision counter) distinct.

Coverage honesty: report helm_releases.count: 0 ONLY when the secret read ran and returned no owner=helm secrets. If the Kubernetes API is unreachable and the secret read could not run, emit helm_releases: {unconfirmed: true, reason: "Kubernetes API unreachable; helm release secrets not read"} rather than a count of 0 — that would report "no releases" when the truth is "not checked".

Reference pseudocode (kubernetes client), not executable:

v1 = client.CoreV1Api()
secrets = v1.list_secret_for_all_namespaces(
label_selector="owner=helm,status=deployed")
releases = [{
"name": s.metadata.labels.get("name"),
"namespace": s.metadata.namespace,
"revision": s.metadata.labels.get("version"), # release revision counter, NOT chart/app version
} for s in secrets.items]

3. Common Platform Components

Detect common components that may be manifest-installed (they would not appear under the EKS add-on API).

Via Kubernetes API — list Deployments and read their identifying labels:

  • Resource: Deployment, group/version apps/v1, all namespaces.
  • Fields to extract: metadata.labels["app.kubernetes.io/name"] → component name; metadata.namespace; metadata.labels["app.kubernetes.io/component"]; the container image tag → version.
  • RBAC verbs: get, list on deployments.apps (cluster-wide).

Common components to detect:

  • AWS Load Balancer Controller
  • External DNS
  • Cert-Manager
  • Metrics Server
  • Cluster Autoscaler
  • Karpenter (covered in compute module)
  • External Secrets Operator
  • Secrets Store CSI Driver

Reference pseudocode (kubernetes client), not executable:

apps = client.AppsV1Api()
components = []
for d in apps.list_deployment_for_all_namespaces().items:
labels = d.metadata.labels or {}
name = labels.get("app.kubernetes.io/name")
if not name:
continue
components.append({
"name": name,
"namespace": d.metadata.namespace,
"component": labels.get("app.kubernetes.io/component"),
})

4. Custom Resource Definitions (CRDs)

CRDs identify operators and controllers that extend Kubernetes. They reveal what platform capabilities are present even when the component was installed via raw manifests without Helm tracking.

Via Kubernetes API — list CRDs:

  • Resource: CustomResourceDefinition, group/version apiextensions.k8s.io/v1.
  • Fields to extract: metadata.name for each CRD.
  • RBAC verbs: get, list on customresourcedefinitions.apiextensions.k8s.io.

Example CRD names observed:

certificates.cert-manager.io
clusterissuers.cert-manager.io
externalsecrets.external-secrets.io
issuers.cert-manager.io
nodepools.karpenter.sh
provisioners.karpenter.sh
secretstores.external-secrets.io

Common CRD patterns:

  • *.cert-manager.io -> cert-manager
  • *.argoproj.io -> ArgoCD
  • *.karpenter.sh -> Karpenter
  • *.external-secrets.io -> External Secrets
  • *.kyverno.io -> Kyverno
  • *.gatekeeper.sh -> OPA Gatekeeper
  • *.istio.io -> Istio

Output Schema

This is the single canonical schema for the add-ons module — it carries every add-on fact. The addons-recon agent emits exactly this shape (plus the shared cluster: block from references/cluster-basics.md). Use null where a fact was not detected; never omit a key. All fields are raw facts — no compatibility assessment, no upgrade verdicts.

addons:
eks_managed:
count: int
list:
- name: string
version: string # addonVersion (installed)
status: string # AWS-reported: ACTIVE, CREATING, UPDATING, DEGRADED, DELETING (fact)
service_account_role: string # serviceAccountRoleArn — Pod Identity/IRSA role ARN, null if none
configuration: object # configurationValues, null if none
health_issues: list # health.issues: [{code, message, resource_ids}], [] if none (fact)
latest_version: string # first entry from describe-addon-versions at CURRENT k8s version (fact)
update_available: bool # latest_version != installed version (raw fact only; NOT an upgrade verdict)

# Delivery mechanism for the core trio (vpc-cni, coredns, kube-proxy) so a self-managed
# core component is not silently absent (it would not appear under eks_managed).
core_components:
vpc_cni:
detected: bool
version: string
managed_by: string # eks-addon | self-managed | auto-mode
coredns:
detected: bool
version: string
managed_by: string # eks-addon | self-managed | auto-mode
kube_proxy:
detected: bool
version: string
managed_by: string # eks-addon | self-managed | auto-mode

helm_releases:
count: int
list:
- name: string
namespace: string
chart: string # chart name+version, e.g. external-dns-1.13.1
app_version: string # packaged app version, null if only the release-secret label is available
revision: string # release revision counter (from the release-secret "version" label) — NOT chart/app version
status: string # deployed, failed, pending-*

platform_components:
aws_load_balancer_controller:
detected: bool
version: string
namespace: string
external_dns:
detected: bool
version: string
namespace: string
cert_manager:
detected: bool
version: string
namespace: string
metrics_server:
detected: bool
version: string
namespace: string
external_secrets:
detected: bool
version: string
namespace: string
secrets_store_csi:
detected: bool
version: string
namespace: string
cluster_autoscaler:
detected: bool
version: string
namespace: string

crds:
count: int
notable: list # CRDs that indicate specific tools

auto_mode_features resolved: the old auto_mode_features block (elb/block_storage/compute booleans) was an unbacked orphan in the agent file — no detection command ever populated it, and those facts belong to compute (auto_mode.node_pools) and storage. It is DROPPED here rather than kept as an always-null field. Auto Mode delivery of vendored CNI/DNS is captured via core_components[].managed_by: auto-mode.


Add-on Status

Capture add-on status as a raw fact. The AWS-reported add-on status field (ACTIVE/DEGRADED/etc.) and health.issues are AWS's own fields — record them verbatim. Draw no conclusion about whether action is needed.

Per-add-on pod health can be corroborated via the Kubernetes API:

Via Kubernetes API — list the add-on's pods and read their phase:

  • Resource: Pod, group/version v1 (core), namespace kube-system, label selector app.kubernetes.io/name=<addon-name>.
  • Fields to extract: status.phase per pod (a pod not in Running indicates the add-on is degraded — a fact, not a verdict).
  • RBAC verbs: get, list on pods.

Status interpretation:

  • ACTIVE - Add-on is healthy
  • CREATING - Add-on is being installed
  • UPDATING - Add-on is being updated
  • DEGRADED - Add-on has issues (check pod status)
  • DELETING - Add-on is being removed

Edge Cases

Self-Managed vs EKS-Managed

Some add-ons can be installed both ways. The core trio (vpc-cni, coredns, kube-proxy) can run as an EKS-managed add-on, self-managed (raw manifest/Helm), or be replaced by Auto Mode. Record the delivery mechanism per core component as a managed_by fact so a self-managed core component is not silently absent from the report (it would not appear in aws eks list-addons).

  • Check the EKS add-on API first (aws eks list-addons). If the component appears → managed_by: eks-addon.
  • If not in the add-on list but the workload exists in-cluster → managed_by: self-managed.
  • On Auto Mode clusters the vendored CNI/DNS are EKS-operated; no aws-node DaemonSet and a vpc-cni ResourceNotFound from describe-addon is expected → managed_by: auto-mode.

Via Kubernetes API — distinguish self-managed from EKS-managed for a core component:

  • Resource: DaemonSet, group/version apps/v1, namespace kube-system, name aws-node (VPC CNI example).
  • Fields to extract: metadata.labels — the label app.kubernetes.io/managed-by=eks indicates EKS-managed; its absence with a bare app/k8s-app label indicates self-managed.
  • RBAC verbs: get, list on daemonsets.apps.

Example (EKS-managed labels):

{"app.kubernetes.io/managed-by":"eks","app.kubernetes.io/name":"aws-node"}

Example (self-managed — no eks label):

{"app":"aws-node","k8s-app":"aws-node"}

Add-on Update Availability (raw fact)

Capture whether a newer add-on version exists as a RAW FACT ONLY. Query the versions available for the add-on at the cluster's CURRENT Kubernetes version and compare the installed version against the latest returned. Do NOT compute compatibility with any target/upgrade Kubernetes version, and do NOT state whether an upgrade should happen — that assessment belongs to the eks-upgrade-check skill.

Via AWS API — list add-on versions at the current Kubernetes version:

# List versions available for the add-on at the cluster's CURRENT k8s version.
# The first entry is the latest available version for that k8s version.
aws eks describe-addon-versions \
--addon-name vpc-cni \
--kubernetes-version <cluster-current-version> \
--query 'addons[0].addonVersions[].addonVersion'

Example output:

[
"v1.15.1-eksbuild.1",
"v1.15.0-eksbuild.2",
"v1.14.1-eksbuild.1"
]

Record latest_version = the first element, and update_available = (latest_version != the installed addonVersion). These are facts about version strings only — NOT an "should upgrade" verdict.

Helm Release in Pending State

Detect stuck Helm releases as a fact. Pending releases indicate failed or interrupted installations. A release secret with a status label other than deployed (e.g. pending-install, pending-upgrade, failed) surfaces this.

Via Kubernetes API — list Helm release secrets in a non-deployed state:

  • Resource: Secret, group/version v1 (core), label selector owner=helm, all namespaces.
  • Fields to extract: label status (values other than deployed — e.g. pending-install, pending-upgrade, failed); label name; metadata.namespace; label version (revision).
  • RBAC verbs: get, list on secrets.

Record the release name, namespace, and status verbatim; draw no conclusion.

Orphaned Resources

Check for orphaned CRDs when an add-on uninstall may have been incomplete. Orphaned CRDs can carry stale custom resources. Record CRDs whose group has no matching running controller Deployment as a fact (cross-reference the CRD list from capability 4 against the platform-component Deployments from capability 3); draw no conclusion.

Via Kubernetes API — the CRD list (capability 4, customresourcedefinitions.apiextensions.k8s.io) and the Deployment list (capability 3, deployments.apps) together provide the cross-reference. Group each CRD by its API group suffix and note groups with no controller Deployment. RBAC: get, list on both resources.