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) andeks:DescribeAddonVersions(latest version at the current Kubernetes version →update_available). Requires the read-only permissions inreferences/iam-policy.json. - Kubernetes API (via the Agent Space EKS access entry) —
platform_components,core_componentsin-cluster delivery/managed_by,helm_releases, and CRDs. RequiresauthenticationModeto includeAPIand theAmazonAIOpsAssistantPolicyaccess 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 ... | jqpipelines. 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/versionv1(core), label selectorowner=helm,status=deployed, all namespaces. - Fields to extract: label
name→ release name;metadata.namespace→ namespace; labelversion→ release revision (a counter, NOT the chart or app version). - RBAC verbs:
get,listonsecrets.
Chart / app_version note. The
versionlabel is the RELEASE REVISION only. Chart version andapp_versionare NOT available from labels alone — they live inside the gzipped release blob (.data.release). When only the secret labels are read, populaterevisionand leavechart/app_versionnull rather than guessing. Keepchart(chart name+version, e.g.external-dns-1.13.1),app_version(packaged app version, e.g.0.13.6), andrevision(release revision counter) distinct.
Coverage honesty: report
helm_releases.count: 0ONLY when the secret read ran and returned noowner=helmsecrets. If the Kubernetes API is unreachable and the secret read could not run, emithelm_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/versionapps/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,listondeployments.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/versionapiextensions.k8s.io/v1. - Fields to extract:
metadata.namefor each CRD. - RBAC verbs:
get,listoncustomresourcedefinitions.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_featuresresolved: the oldauto_mode_featuresblock (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 viacore_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/versionv1(core), namespacekube-system, label selectorapp.kubernetes.io/name=<addon-name>. - Fields to extract:
status.phaseper pod (a pod not inRunningindicates the add-on is degraded — a fact, not a verdict). - RBAC verbs:
get,listonpods.
Status interpretation:
ACTIVE- Add-on is healthyCREATING- Add-on is being installedUPDATING- Add-on is being updatedDEGRADED- 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-nodeDaemonSet and avpc-cniResourceNotFound 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/versionapps/v1, namespacekube-system, nameaws-node(VPC CNI example). - Fields to extract:
metadata.labels— the labelapp.kubernetes.io/managed-by=eksindicates EKS-managed; its absence with a bareapp/k8s-applabel indicates self-managed. - RBAC verbs:
get,listondaemonsets.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/versionv1(core), label selectorowner=helm, all namespaces. - Fields to extract: label
status(values other thandeployed— e.g.pending-install,pending-upgrade,failed); labelname;metadata.namespace; labelversion(revision). - RBAC verbs:
get,listonsecrets.
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.