Skip to main content

Subnet - Network Segmentation

Create subnets within a VPC to organize and isolate your AWS resources.

Prerequisite: AWSProvider Configuration​

Before creating any AWS resource, you need to configure an AWSProvider that manages credentials and authentication with AWS.

IRSA:

apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: AWSProvider
metadata:
name: production-aws
namespace: default
spec:
region: us-east-1
roleARN: arn:aws:iam::123456789012:role/infra-operator-role
defaultTags:
managed-by: infra-operator
environment: production

Static Credentials:

apiVersion: v1
kind: Secret
metadata:
name: aws-credentials
namespace: default
type: Opaque
stringData:
access-key-id: test
secret-access-key: test
---
apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: AWSProvider
metadata:
name: localstack
namespace: default
spec:
region: us-east-1
accessKeyIDRef:
name: aws-credentials
key: access-key-id
secretAccessKeyRef:
name: aws-credentials
key: secret-access-key
defaultTags:
managed-by: infra-operator
environment: test

Verify Status:

kubectl get awsprovider
kubectl describe awsprovider production-aws
warning

For production, always use IRSA (IAM Roles for Service Accounts) instead of static credentials.

Create IAM Role for IRSA​

To use IRSA in production, you need to create an IAM Role with the necessary permissions:

Trust Policy (trust-policy.json):

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:infra-operator-system:infra-operator-controller-manager",
"oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}

IAM Policy - Subnet (subnet-policy.json):

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:CreateSubnet",
"ec2:DeleteSubnet",
"ec2:DescribeSubnets",
"ec2:ModifySubnetAttribute",
"ec2:CreateTags",
"ec2:DeleteTags",
"ec2:DescribeTags"
],
"Resource": "*"
}
]
}

Create Role with AWS CLI:

# 1. Get OIDC Provider from EKS cluster
export CLUSTER_NAME=my-cluster
export AWS_REGION=us-east-1
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

OIDC_PROVIDER=$(aws eks describe-cluster \
--name $CLUSTER_NAME \
--region $AWS_REGION \
--query "cluster.identity.oidc.issuer" \
--output text | sed -e "s/^https:\/\///")

# 2. Update trust-policy.json with correct values
cat > trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${AWS_ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${OIDC_PROVIDER}:sub": "system:serviceaccount:infra-operator-system:infra-operator-controller-manager",
"${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF

# 3. Create IAM Role
aws iam create-role \
--role-name infra-operator-subnet-role \
--assume-role-policy-document file://trust-policy.json \
--description "Role for Infra Operator Subnet management"

# 4. Create and attach policy
aws iam put-role-policy \
--role-name infra-operator-subnet-role \
--policy-name SubnetManagement \
--policy-document file://subnet-policy.json

# 5. Get Role ARN
aws iam get-role \
--role-name infra-operator-subnet-role \
--query 'Role.Arn' \
--output text

Annotate Operator ServiceAccount:

# Add annotation to operator ServiceAccount
kubectl annotate serviceaccount infra-operator-controller-manager \
-n infra-operator-system \
eks.amazonaws.com/role-arn=arn:aws:iam::123456789012:role/infra-operator-subnet-role
note

Replace 123456789012 with your AWS Account ID and EXAMPLED539D4633E53DE1B71EXAMPLE with your OIDC provider ID.

Overview​

A Subnet is a division of a VPC that allows you to segment your network into smaller IP address ranges. With Subnets, you can:

  • Organize resources into tiers (public, private, data)
  • Distribute resources across Availability Zones for high availability
  • Implement security isolation
  • Control network traffic routing

Quick Start​

Public Subnet:

apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: e2e-public-subnet
namespace: default
spec:
providerRef:
name: localstack
vpcID: REPLACE_WITH_VPC_ID
cidrBlock: "10.0.1.0/24"
availabilityZone: "us-east-1a"
mapPublicIpOnLaunch: true
tags:
Name: e2e-public-subnet
Type: public
ManagedBy: infra-operator
deletionPolicy: Delete

Private Subnet:

apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: e2e-private-subnet
namespace: default
spec:
providerRef:
name: localstack
vpcID: REPLACE_WITH_VPC_ID
cidrBlock: "10.0.2.0/24"
availabilityZone: "us-east-1b"
mapPublicIpOnLaunch: false
tags:
Name: e2e-private-subnet
Type: private
ManagedBy: infra-operator
deletionPolicy: Delete

Apply and Verify:

# Apply the subnet
kubectl apply -f subnet.yaml

# Verify status
kubectl get subnets
kubectl describe subnet e2e-public-subnet

Configuration Reference​

Required Fields​

Reference to AWSProvider resource

Name of AWSProvider resource

ID of the VPC where the subnet will be created (example: vpc-xxx)

note

The VPC must exist before creating the subnet

IPv4 CIDR block for the subnet (example: "10.0.1.0/24")

Requirements:

  • Must be within the VPC CIDR
  • Cannot overlap with other subnets in the VPC
  • Allowed netmask: /16 to /28

Optional Fields​

Availability Zone where the subnet will be created (example: us-east-1a)

tip

Distribute subnets across multiple AZs for high availability

If true, instances launched in this subnet receive public IP automatically

Use cases:

  • true: Public subnets (web servers, load balancers)
  • false: Private subnets (databases, app servers)

Tags for organization and billing

Example:

tags:
Name: public-subnet-1a
Type: public
kubernetes.io/role/elb: "1" # For EKS load balancers

Deletion policy when the CR is removed

Options:

  • Delete: Subnet is deleted from AWS
  • Retain: Subnet remains in AWS
  • Orphan: Removes management but keeps in AWS

Status Fields​

AWS identifier for the subnet (example: subnet-12853af5337079de5)

Parent VPC ID

CIDR block assigned to the subnet

AZ where the subnet is located

Current state of the subnet

  • pending: Being created
  • available: Ready for use

Number of available IP addresses in the subnet

true when the subnet is available

Examples​

Multi-AZ Architecture with Public and Private Subnets​

Example:

---
# Public Subnet - AZ 1a
apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: public-1a
spec:
providerRef:
name: production-aws
vpcID: vpc-f3ea9b1b36fce09cd
cidrBlock: "10.0.1.0/24"
availabilityZone: us-east-1a
mapPublicIpOnLaunch: true
tags:
Name: public-subnet-1a
Type: public
kubernetes.io/role/elb: "1"
deletionPolicy: Delete

---
# Public Subnet - AZ 1b
apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: public-1b
spec:
providerRef:
name: production-aws
vpcID: vpc-f3ea9b1b36fce09cd
cidrBlock: "10.0.2.0/24"
availabilityZone: us-east-1b
mapPublicIpOnLaunch: true
tags:
Name: public-subnet-1b
Type: public
kubernetes.io/role/elb: "1"
deletionPolicy: Delete

---
# Private Subnet - AZ 1a
apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: private-1a
spec:
providerRef:
name: production-aws
vpcID: vpc-f3ea9b1b36fce09cd
cidrBlock: "10.0.10.0/24"
availabilityZone: us-east-1a
mapPublicIpOnLaunch: false
tags:
Name: private-subnet-1a
Type: private
kubernetes.io/role/internal-elb: "1"
deletionPolicy: Delete

---
# Private Subnet - AZ 1b
apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: private-1b
spec:
providerRef:
name: production-aws
vpcID: vpc-f3ea9b1b36fce09cd
cidrBlock: "10.0.11.0/24"
availabilityZone: us-east-1b
mapPublicIpOnLaunch: false
tags:
Name: private-subnet-1b
Type: private
kubernetes.io/role/internal-elb: "1"
deletionPolicy: Delete

Database Subnet (Isolated)​

Example:

apiVersion: aws-infra-operator.runner.codes/v1alpha1
kind: Subnet
metadata:
name: database-subnet-1a
spec:
providerRef:
name: production-aws
vpcID: vpc-f3ea9b1b36fce09cd
cidrBlock: "10.0.20.0/24"
availabilityZone: us-east-1a
mapPublicIpOnLaunch: false
tags:
Name: database-subnet-1a
Type: database
Tier: data
Encryption: required
deletionPolicy: Retain # Keep data subnet

Verification​

Verify Subnet Status​

Command:

# List all subnets
kubectl get subnets

# View details with available IPs
kubectl get subnet public-1a -o wide

# Full YAML
kubectl get subnet public-1a -o yaml

Verify in AWS​

AWS CLI:

# List subnets in a VPC
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=vpc-xxx"

# Details of a specific subnet
aws ec2 describe-subnets \
--subnet-ids subnet-xxx \
--query 'Subnets[0]' \
--output table

LocalStack:

export AWS_ENDPOINT_URL=http://localhost:4566

# List subnets
aws ec2 describe-subnets \
--filters "Name=vpc-id,Values=vpc-xxx" \
--query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone,State]' \
--output table

Troubleshooting​

Error: VPC ID does not exist​

Error: VpcID vpc-xxx does not exist

Causes:

  1. VPC has not been created yet
  2. Incorrect VPC ID in spec
  3. VPC was deleted

Solutions:

# Check if VPC exists
kubectl get vpcs
kubectl get vpc <vpc-name> -o jsonpath='{.status.vpcID}'

# Update subnet with correct VPC ID
kubectl patch subnet <name> --type='json' \
-p='[{"op": "replace", "path": "/spec/vpcID", "value":"vpc-xxx"}]'

CIDR Conflict​

Error: CIDR block 10.0.1.0/24 conflicts with existing subnet

Cause: A subnet with overlapping CIDR already exists

Solutions:

  1. Use a different CIDR
  2. Check existing subnets: bash aws ec2 describe-subnets \ --filters "Name=vpc-id,Values=vpc-xxx" \ --query 'Subnets[*].CidrBlock'

Subnet not becoming ready​

Symptoms: ready: false for more than 2 minutes

Debug:

# View events
kubectl describe subnet <name>

# Controller logs
kubectl logs -n infra-operator-system \
deploy/infra-operator-controller-manager \
| grep subnet

# Detailed status
kubectl get subnet <name> -o yaml

No available IPs​

Symptoms: availableIpAddressCount: 0

Causes:

  • Subnet too small (/28 = only 11 usable IPs)
  • Too many ENIs created

Solutions:

  1. Create larger subnet (/24 = 251 IPs)
  2. Clean up unused resources
  3. Delete orphaned ENIs

Best Practices​

Best Practices
  • Size subnets appropriately — /24 for public (251 IPs), /22 for private (1019 IPs)
  • Use multiple AZs — Spread subnets across 2-3 AZs for high availability
  • Separate public and private — Public for load balancers, private for applications
  • Plan CIDR allocation — Leave room for additional subnets in each AZ
  • Tag with AZ and tier — Include availability zone and subnet type (public/private/database)

Architecture Patterns​

3-Tier Architecture​

Classic three-tier architecture with subnets organized by function and distributed across multiple Availability Zones for high availability:

3-Tier Architecture

EKS-Ready Subnets​

Example:

# Subnets for EKS need specific tags
tags:
kubernetes.io/cluster/my-cluster: shared
kubernetes.io/role/elb: "1" # Public
kubernetes.io/role/internal-elb: "1" # Private