Before you start
You need:
- a Kubernetes cluster, 1.28 or newer (EKS is the usual home, but any cluster that can reach the AWS APIs works);
- Helm 3.8 or newer and
kubectl; - an IAM role the operator can use, with
ec2:DescribeVpcs,ec2:DescribeSubnetsandec2:DescribeRouteTables; addsts:AssumeRolewhen you discover more than one account; - VPCs tagged so the operator can find them,
hs/managed=trueby default.
Describe calls.
Automatic subnet management is a separate, opt-in layer that is not part of this release.
Quick start
Add the chart repository
helm repo add hypersurgery https://charts.hypersurgery.dev helm repo update hypersurgery helm search repo hypersurgery/aws-subnet-operator
Give the operator AWS credentials
With EKS Pod Identity, associate your role with the service account that the release creates:
# after step 3, the service account is # aws-subnet-operator-system/subnet-operator-aws-subnet-operator aws eks create-pod-identity-association \ --cluster-name my-cluster \ --namespace aws-subnet-operator-system \ --service-account subnet-operator-aws-subnet-operator \ --role-arn arn:aws:iam::111111111111:role/aws-subnet-operator
With IRSA, annotate the service account instead (step 3 shows where).
For other accounts, deploy the read-only role from
deploy/iam/spoke-readonly-role.cfn.yaml with a CloudFormation StackSet.
Install the chart
The image lives in a registry that needs a login, so create a pull secret first:
kubectl create namespace aws-subnet-operator-system kubectl create secret docker-registry registry -n aws-subnet-operator-system \ --docker-server=registry.hypersurgery.dev \ --docker-username=<user> --docker-password=<token>
helm install subnet-operator hypersurgery/aws-subnet-operator \ --namespace aws-subnet-operator-system \ --set imagePullSecrets[0].name=registry \ --set aws.region=eu-central-1 \ --set serviceAccount.annotations."eks\.amazonaws\.com/role-arn"=arn:aws:iam::111111111111:role/aws-subnet-operator
The chart installs the CRDs, RBAC, the manager and a metrics service. Drop the
serviceAccount.annotations line if you use Pod Identity.
Tell it what to discover
kubectl apply -f - <<'EOF' apiVersion: aws.hypersurgery/v1alpha1 kind: NetworkScope metadata: name: organization spec: accounts: - id: "111111111111" # the operator's own account - id: "222222222222" roleARN: arn:aws:iam::222222222222:role/aws-subnet-operator-readonly regions: [eu-central-1, eu-west-1] vpcTagSelector: hs/managed: "true" requiredSubnetTags: [hs/owner, hs/env, hs/tier] resyncInterval: 10m EOF
Leave vpcTagSelector out to take every VPC in the listed regions.
Verify
kubectl get networkscopes NAME VPCS SUBNETS READY LAST SYNC organization 42 318 True 12s kubectl get subnets -o wide kubectl get vpcs -o wide kubectl get subnets -l aws.hypersurgery/account=222222222222
If READY stays False, the scope status names the account and region that failed:
kubectl get networkscope organization -o jsonpath='{.status.targets}' | jq
Change events (optional)
Without events, changes appear at the next resync. With them, a changed account and region is re-read about ten seconds after the API call.
- Make sure CloudTrail records management events in every account, for example with an organization trail.
- Deploy
deploy/events/hub-events.cfn.yamlin the operator's account, anddeploy/events/spoke-events.cfn.yamlto every other account and region with a StackSet. - Point the release at the queue:
helm upgrade subnet-operator hypersurgery/aws-subnet-operator \
--namespace aws-subnet-operator-system --reuse-values \
--set events.queueUrl=https://sqs.eu-central-1.amazonaws.com/111111111111/aws-subnet-operator-events
Dashboard and alerts
With kube-prometheus-stack in the cluster:
helm upgrade subnet-operator hypersurgery/aws-subnet-operator \ --namespace aws-subnet-operator-system --reuse-values \ --set metrics.serviceMonitor.enabled=true \ --set metrics.serviceMonitor.labels.release=kube-prometheus-stack \ --set prometheusRule.enabled=true \ --set prometheusRule.labels.release=kube-prometheus-stack \ --set grafanaDashboard.enabled=true
The dashboard ConfigMap is labelled grafana_dashboard: "1", which the Grafana sidecar
imports on its own. You get headline counts, utilization by environment, free addresses per account,
the fullest subnets, tag compliance, target health and the age of the last full sync — the same panels
as the live demo.
The alerts are: a subnet at 85% or more, a subnet with no free addresses, an account that cannot be discovered, overlapping VPC CIDRs, and an inventory that has not fully resynced for an hour.
Google Sheet mirror (optional)
For people who still want a table. The operator writes the sheet and never reads it back.
# share the spreadsheet with the service account address first kubectl create secret generic google-sheets -n aws-subnet-operator-system \ --from-file=credentials.json=./service-account.json kubectl apply -f - <<'EOF' apiVersion: aws.hypersurgery/v1alpha1 kind: SheetExport metadata: name: organization spec: scopeRef: organization spreadsheetID: 1AbCdEfGhIjKlMnOpQrStUvWxYz0123456789 credentialsSecretRef: name: google-sheets namespace: aws-subnet-operator-system refreshInterval: 5m EOF
Only service account keys are accepted: other credential configurations can make the Google client run a local command, which a Secret should never be able to do.
Common values
| Value | Default | What it does |
|---|---|---|
image.repository | ghcr.io/example/aws-subnet-operator | Manager image; set it to your registry. |
aws.region | empty | Region for the operator's own calls. |
discovery.concurrency | 4 | Account/region pairs discovered in parallel. |
events.queueUrl | empty | SQS queue with EC2 change events. |
metrics.secure | true | HTTPS with authn/authz on the metrics endpoint. |
metrics.serviceMonitor.enabled | false | ServiceMonitor for the Prometheus operator. |
prometheusRule.enabled | false | Alerting rules. |
grafanaDashboard.enabled | false | Dashboard ConfigMap for the Grafana sidecar. |
networkScope.create | false | Create a scope with the release instead of by hand. |
resources | 500m / 256Mi | Container resources. |
Everything else: helm show values hypersurgery/aws-subnet-operator.
Upgrade and uninstall
helm repo update hypersurgery helm upgrade subnet-operator hypersurgery/aws-subnet-operator \ --namespace aws-subnet-operator-system --reuse-values
helm pull hypersurgery/aws-subnet-operator --untar && kubectl apply -f aws-subnet-operator/crds/
helm uninstall subnet-operator -n aws-subnet-operator-system
Uninstalling removes the operator and, with it, the mirrored VPC and Subnet
objects. Nothing happens to your AWS networks: they were never touched.
The dashboard app
hypersurgery.dev/dashboard/ is the same set of panels as an installable web app: open it in Chrome or Edge and use the install icon in the address bar, or the Install app button in its header; on iOS and macOS Safari use Share → Add to Home Screen or Add to Dock. It keeps working offline and carries four themes (terminal, cyberpunk, midnight and daylight).
kubectl get subnets.
Troubleshooting
The scope says Ready=False
Read status.targets: each account and region carries its own error.
AccessDenied usually means the spoke role's trust policy does not list the operator's
role, or the externalID does not match. The operator also refuses a roleARN
whose account does not match the declared account ID, so a typo shows up here rather than as
someone else's networks.
No subnets appear
The tag selector matches VPCs, not subnets: a VPC without hs/managed=true is skipped
together with everything inside it. Check with
aws ec2 describe-vpcs --filters Name=tag:hs/managed,Values=true.
Utilization looks stale
Free-address counts change with every ENI and are refreshed by the full resync, not by events.
Lower resyncInterval if you need them fresher.
Metrics are not scraped
With metrics.secure=true Prometheus needs a token and must accept the self-signed
serving certificate; the chart's ServiceMonitor sets both. Check the labels your Prometheus
selects ServiceMonitors on.