{"id":741,"date":"2023-12-17T11:25:10","date_gmt":"2023-12-17T11:25:10","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=741"},"modified":"2023-12-17T11:25:10","modified_gmt":"2023-12-17T11:25:10","slug":"secondary-network-eks-cluster","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=741","title":{"rendered":"How to Setup VPC Secondary Network For EKS Cluster"},"content":{"rendered":"<p>In EKS IPV4 clusters, IP exhaustion problem can be solved by setting up a VPC secondary network. In this blog we will look at step by step guide for setting up EKS cluster using secondary IP ranges.<\/p>\n<h2 id=\"need-for-vpc-secondary-network\">Need for VPC Secondary Network<\/h2>\n<p>Before we jump to the hands on section, lets first understand the need for <a href=\"https:\/\/devopscube.com\/aws-vpc-design\/\">VPC<\/a> secondary network configuration for IPV4 clusters.<\/p>\n<p>When you launch a EKS cluster, by default it uses the <strong>VPC subnet IP addresses<\/strong> for pods and services. Meaning, if the VPC CIDR is <strong><code>10.0.0.0\/20<\/code><\/strong> (<strong>4,096<\/strong> IPs), the node IPs, pod IPs and service IPs are assigned from CIDR range.<\/p>\n<p>It is not an issue for small clusters with less workloads. However, for large organizations using IPV4 with hybrid cloud networks this could be an issue.<\/p>\n<p>In such organizations when you request for large IP ranges for <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\">Kubernetes clusters<\/a>., network teams often express concerns. Because they need to allocate IP address for multiple clusters in multiple environments and for multiple teams.<\/p>\n<p>You can mitigate the IPv4 exhaustion issue by using AWS VPC secondary IP ranges such as <a href=\"https:\/\/devopscube.com\/ip-address-tutorial\/\">shared address space<\/a> <strong><code>100.64.0.0\/16<\/code><\/strong> or 172.x series.<\/p>\n<p>If you look at the following image, the <strong>Primary ENI of worker nodes<\/strong> use the primary subnet CIDR and pods uses the ENI from the secondary CIDR range.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-95-7.png\" class=\"kg-image\" alt=\"AWS EKS VPC Secondary Network\" loading=\"lazy\" width=\"1011\" height=\"661\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-95-7.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-95-7.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-95-7.png 1011w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Image src: aws.github.io<\/p>\n<p>A good thing is, you can <strong>re-use the secondary ranges<\/strong> in other clusters and the primary ENI does the source network translation and the traffic going outside the cluster uses the primary ENI as source address. So you will not face any routing issues.<\/p>\n<blockquote><p><strong>Note<\/strong>: You can use CNI plugins like calico to mitigate this issue. But for external CNI plugins you will not get any support from AWS.<\/p><\/blockquote>\n<p>Secondary Network setup is not required for the following.<\/p>\n<ol>\n<li>If you are implementing IPv6 based clusters.<\/li>\n<li>Private <a href=\"https:\/\/devopscube.com\/what-is-nat-how-does-nat-work\/\">NAT<\/a> Gateway implementation where VPCs can have overlapping IP addresses.<\/li>\n<\/ol>\n<h2 id=\"eks-secondary-network-setup\">EKS Secondary Network Setup<\/h2>\n<p>Let&#8217;s have a look at what is the secondary network in the Kubernetes cluster.<\/p>\n<p>Here we use a single dedicated VPC with two different CIDR ranges, which I am considering <code>192.168.0.0\/16<\/code> as a primary range for nodes and <code>100.64.0.0\/16<\/code> as the secondary range for the pods.<\/p>\n<h3 id=\"step-1-create-a-vpc-and-associate-a-secondary-cidr\">Step 1: Create a VPC and Associate a Secondary CIDR<\/h3>\n<p>We are using <a href=\"https:\/\/devopscube.com\/install-configure-aws-cli-linux\/\">AWS CLI<\/a> commands to <a href=\"https:\/\/devopscube.com\/terraform-aws-vpc\/\">create the VPC<\/a>. So some utilities required are given below.<\/p>\n<ol>\n<li>AWS CLI<\/li>\n<li>jq<\/li>\n<\/ol>\n<p>Create a VPC with a CIDR range <code>192.168.0.0\/16<\/code>. We use this range to create the nodes.<\/p>\n<pre><code>export VPC_ID=$(aws ec2 create-vpc --cidr-block 192.168.0.0\/16 --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=EKS-VPC}]' --query 'Vpc.VpcId' --output text)\n<\/code><\/pre>\n<p>Add secondary CIDR <code>100.64.0.0\/16<\/code> to the VPC. This range is for creating pods.<\/p>\n<pre><code>aws ec2 associate-vpc-cidr-block --vpc-id $VPC_ID --cidr-block 100.64.0.0\/16<\/code><\/pre>\n<p>Create an Internet Gateway for the VPC to get internet access.<\/p>\n<pre><code>export IGW_ID=$(aws ec2 create-internet-gateway --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=EKS-IGW}]' --query 'InternetGateway.InternetGatewayId' --output text)<\/code><\/pre>\n<p>Attach Internet Gateway to the intended VPC<\/p>\n<pre><code>aws ec2 attach-internet-gateway --vpc-id ${VPC_ID} --internet-gateway-id ${IGW_ID}<\/code><\/pre>\n<p>Create a route table for our subnets.<\/p>\n<pre><code>export RTB_ID=$(aws ec2 create-route-table --vpc-id $VPC_ID --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=EKS-RouteTable}]' --query 'RouteTable.RouteTableId' --output text)<\/code><\/pre>\n<p>Add route to the internet by attaching IGW in the route table.<\/p>\n<pre><code>aws ec2 create-route --route-table-id ${RTB_ID} --destination-cidr-block 0.0.0.0\/0 --gateway-id ${RTB_ID}<\/code><\/pre>\n<p>Set up environment variables for availability zones.<\/p>\n<pre><code>AZ_1=\"us-west-2a\"\nAZ_2=\"us-west-2b\"\nAZ_3=\"us-west-2c\"<\/code><\/pre>\n<p>Create subnets for nodes from range <code>192.168.0.0\/16<\/code>, each in a different availability zone.<\/p>\n<pre><code>SUBNET_ID_NODE_1=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 192.168.0.0\/19 --availability-zone $AZ_1 --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Node-Subnet-1}]' | jq -r '.Subnet.SubnetId')\n\nSUBNET_ID_NODE_2=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 192.168.32.0\/19 --availability-zone $AZ_2 --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Node-Subnet-2}]' | jq -r '.Subnet.SubnetId')\n\nSUBNET_ID_NODE_3=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 192.168.64.0\/19 --availability-zone $AZ_3 --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Node-Subnet-3}]' | jq -r '.Subnet.SubnetId')<\/code><\/pre>\n<p>Create three subnets for pods from CIDR <code>100.64.0.0\/16<\/code>.<\/p>\n<pre><code>SUBNET_ID_POD_1=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 100.64.0.0\/19 --availability-zone $AZ_1 --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Pod-Subnet-1}]' | jq -r '.Subnet.SubnetId')\n\n\nSUBNET_ID_POD_2=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 100.64.32.0\/19 --availability-zone $AZ_2 --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Pod-Subnet-2}]' | jq -r '.Subnet.SubnetId')\n\n\nSUBNET_ID_POD_3=$(aws ec2 create-subnet --vpc-id $VPC_ID --cidr-block 100.64.64.0\/19 --availability-zone $AZ_3 --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Pod-Subnet-3}]' | jq -r '.Subnet.SubnetId')<\/code><\/pre>\n<p>Associate each subnet with the routing table.<\/p>\n<pre><code>\naws ec2 associate-route-table --subnet-id $SUBNET_ID_NODE_1 --route-table-id $RTB_ID\naws ec2 associate-route-table --subnet-id $SUBNET_ID_NODE_2 --route-table-id $RTB_ID\naws ec2 associate-route-table --subnet-id $SUBNET_ID_NODE_3 --route-table-id $RTB_ID\n\naws ec2 associate-route-table --subnet-id $SUBNET_ID_POD_1 --route-table-id $RTB_ID\naws ec2 associate-route-table --subnet-id $SUBNET_ID_POD_2 --route-table-id $RTB_ID\naws ec2 associate-route-table --subnet-id $SUBNET_ID_POD_3 --route-table-id $RTB_ID\n<\/code><\/pre>\n<p><code>Enable auto-assign public IPv4 address<\/code> to all subnets. this is required to automatically assign a public IP address to all nodes.<\/p>\n<pre><code>aws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID_NODE_1 --map-public-ip-on-launch\naws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID_NODE_2 --map-public-ip-on-launch\naws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID_NODE_3 --map-public-ip-on-launch\n\naws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID_POD_1 --map-public-ip-on-launch\naws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID_POD_2 --map-public-ip-on-launch\naws ec2 modify-subnet-attribute --subnet-id $SUBNET_ID_POD_3 --map-public-ip-on-launch<\/code><\/pre>\n<blockquote><p>If you are dont enable the <code>Enable auto-assign public IPv4 address<\/code> option, the nodes won&#8217;t be attached with the EKS cluster.<\/p><\/blockquote>\n<h3 id=\"step-2-create-an-eks-cluster\">Step 2: Create an EKS Cluster<\/h3>\n<p>We use <code>eksctl<\/code> command line utility to <a href=\"https:\/\/devopscube.com\/create-aws-eks-cluster-eksctl\/\">create a cluster in EKS<\/a>, make sure you have already installed this in your local system.<\/p>\n<p>Create a cluster config file to build an EKS cluster<\/p>\n<pre><code>vim eks-cluster.yml<\/code><\/pre>\n<p>Modify the details as per your requirements.<\/p>\n<p>Here the EKS version we use is <code>v1.28<\/code>, the latest one, and subnets, created from <code>192.168.0.0\/16<\/code> CIDR for nodes.<\/p>\n<p>In the <code>nodeGroups<\/code> section, choose your instance type, desired capacity, and your public key name. For this setup, we use <code>t3.medium<\/code> instance type with two nodes.<\/p>\n<pre><code>apiVersion: eksctl.io\/v1alpha5\nkind: ClusterConfig\n\nmetadata:\n  name: custom-cluster\n  region: us-west-2\n  version: \"1.28\"\n\nvpc:\n  subnets:\n    public:\n      us-west-2a: $SUBNET_ID_NODE_1\n      us-west-2b: $SUBNET_ID_NODE_2\n      us-west-2c: $SUBNET_ID_NODE_3\n\nnodeGroups:\n  - name: ng-spot\n    instanceType: t3.medium\n    labels: { role: builders }\n    minSize: 2\n    maxSize: 4\n    volumeSize: 30\n    desiredCapacity: 2\n    ssh:\n      allow: true\n      publicKeyName: techiescamp\n    tags:\n      Name: ng-spot\n<\/code><\/pre>\n<h3 id=\"step-3-configure-aws-vpc-cni-plugin\">Step 3: Configure AWS VPC CNI Plugin<\/h3>\n<p>The VPC CNI Plugin is an add-on that will be available in each node of the clusters. basically, it is a <strong>deamonset<\/strong> named <code>aws-node<\/code>, and will be present in each node. This plugin attaches the Elastic Network Interface (ENI) to the nodes and assigns private IPs for pods from the VPC.<\/p>\n<p>We have to make some modifications in the CNI configuration for pods to use the secondary CIDR range.<\/p>\n<p><strong>Prerequisites:<\/strong><\/p>\n<ul>\n<li>VPC-CNI v1.15.3<\/li>\n<\/ul>\n<p>The EKS cluster will already have <code>vpc-cni<\/code> add-on in the cluster, but the version might not be suitable. so to verify the existing EKS CNI version, use the following command.<\/p>\n<pre><code>kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d \"\/\" -f 2<\/code><\/pre>\n<p>Ensure the CNI plugin version is suitable for the EKS cluster version. To know more about the recommended version, please visit the <a href=\"https:\/\/docs.aws.amazon.com\/eks\/latest\/userguide\/managing-vpc-cni.html?ref=devopscube.com\">official documentation<\/a>.<\/p>\n<blockquote><p>If you are using the latest version of EKS (v1.28), the VPC CNI version should be (v1.15.3), otherwise, the pods take the IPs from the primary network.<\/p><\/blockquote>\n<p>In multiple ways, we can install or update the VPC CNI plugin. here we show one method to install the latest CNI plugin using <code>eksctl<\/code> utility.<\/p>\n<p>Verify the add-ons, which are available for the EKS cluster.<\/p>\n<pre><code>eksctl utils describe-addon-versions --kubernetes-version 1.28 | grep AddonName<\/code><\/pre>\n<p>To install the <code>vpc-cni<\/code> add-on, use the following command.<\/p>\n<pre><code>eksctl create addon --cluster custom-cluster --name vpc-cni --version v1.15.3-eksbuild.1 \\\n    --service-account-role-arn arn:aws:iam::814200988517:role\/eksctl-custom-cluster-cluster-ServiceRole-48dmfzxgMzcx --force<\/code><\/pre>\n<p>Change the <code>custom-cluster<\/code> to your cluster name and also modify the add-on name <code>vpc-cni<\/code> and its version <code>v1.15.3<\/code>, if necessary.<\/p>\n<p>The cluster service account IAM role ARN is necessary for this configuration. you can get this from IAM roles by searching <code>cluster-ServiceRole<\/code>.<\/p>\n<p>To enable the custom network configurations, set up env for AWS VPC CNI daemon.<\/p>\n<pre><code>kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true<\/code><\/pre>\n<p>To set up the env for the AWS VPC CNI config label, use the following command.<\/p>\n<pre><code>kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=failure-domain.beta.kubernetes.io\/zone<\/code><\/pre>\n<p>Create and apply EKS Custom Resource (CR) for ENIConfig resources.<\/p>\n<pre><code>cat &lt;&lt;EOF  | kubectl apply -f -\napiVersion: crd.k8s.amazonaws.com\/v1alpha1\nkind: ENIConfig\nmetadata:\n name: $AZ_1\nspec:\n  securityGroups: \n    - sg-0b2df31a3a4aa9b25\n  subnet: $SUBNET_ID_POD_1\nEOF\n\ncat &lt;&lt;EOF | kubectl apply -f -\napiVersion: crd.k8s.amazonaws.com\/v1alpha1\nkind: ENIConfig\nmetadata:\n name: $AZ_2\nspec:\n  securityGroups: \n    - sg-0b2df31a3a4aa9b25\n  subnet: $SUBNET_ID_POD_2\nEOF\n\ncat &lt;&lt;EOF | kubectl apply -f -\napiVersion: crd.k8s.amazonaws.com\/v1alpha1\nkind: ENIConfig\nmetadata:\n name: $AZ_3\nspec:\n  securityGroups: \n    - sg-0b2df31a3a4aa9b25\n  subnet: $SUBNET_ID_POD_3\nEOF<\/code><\/pre>\n<p>Replace the security group ID <strong>sg-0b2df31a3a4aa9b25<\/strong> with your EKS clusters&#8217; security group ID. you can find this from the EKS console, networking section.<\/p>\n<h3 id=\"step-4-recreate-nodes\">Step 4: Recreate Nodes<\/h3>\n<p>The existing nodes should be terminated and create new nodes. This is required to <strong>reflect the CNI configuration changes<\/strong> to the nodes, until then your pods won&#8217;t get the IPs from the secondary range.<\/p>\n<p>To get the node name and details, use the following command.<\/p>\n<pre><code>kubectl get nodes<\/code><\/pre>\n<p>You will get the node name from this command output.<\/p>\n<p>To Cordon, the nodes, use the following command.<\/p>\n<pre><code>kubectl cordon ip-192-168-0-102.us-west-2.compute.internal<\/code><\/pre>\n<p>This command will stop scheduling pods to this node.<\/p>\n<p>Replace it <code>ip-192-168-0-102.us-west-2.compute.internal<\/code> with your node name.<\/p>\n<p>Drain the node to reschedule all the pods from this node to another node.<\/p>\n<pre><code>kubectl drain ip-192-168-0-102.us-west-2.compute.internal --ignore-daemonsets<\/code><\/pre>\n<p>Find the instance ID, which you want to terminate.<\/p>\n<pre><code>INSTANCE_ID=$(kubectl get node ip-192-168-0-102.us-west-2.compute.internal -o jsonpath='{.spec.providerID}' | awk -F '\/' '{print $NF}')<\/code><\/pre>\n<p>To terminate the instance use the following command.<\/p>\n<pre><code>aws ec2 terminate-instances --instance-ids $INSTANCE_ID --region us-west-2<\/code><\/pre>\n<p>Now this instance is deleted and a new instance will be created automatically. because nodes are bound with an Auto Scaling Group.<\/p>\n<p>To view the list of nodes and their details.<\/p>\n<pre><code>kubectl get nodes<\/code><\/pre>\n<p>To test the setup, deploy the nginx web server in the nodes.<\/p>\n<pre><code>kubectl create deployment nginx-test --image=nginx --replicas=3<\/code><\/pre>\n<p>To view the list of pods, use the following command.<\/p>\n<pre><code>kubectl get pods -o wide<\/code><\/pre>\n<p>You will get an output similar to this and ensure all the pods are working fine.<\/p>\n<pre><code>NAME                                READY   STATUS    RESTARTS   AGE     IP              NODE                                           NOMINATED NODE   READINESS GATES\nnginx-deployment-7c79c4bf97-4bwbw   1\/1     Running   0          6m52s   100.64.74.80    ip-192-168-70-175.us-west-2.compute.internal   &lt;none&gt;           &lt;none&gt;\nnginx-deployment-7c79c4bf97-bnxfw   1\/1     Running   0          6m52s   100.64.80.90    ip-192-168-70-175.us-west-2.compute.internal   &lt;none&gt;           &lt;none&gt;\nnginx-deployment-7c79c4bf97-v25hp   1\/1     Running   0          21m     100.64.80.140   ip-192-168-70-175.us-west-2.compute.internal   &lt;none&gt;           &lt;none&gt;<\/code><\/pre>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>I have tried out this setup to understand how to <strong>attach the multiple CIDR networks to a VPC<\/strong> to manage a EKS cluster.<\/p>\n<p>You can further improve the setup for your requirements and also keep in mind that there is a calculation in how many pods you can create in a node depending on how many ENI support with the instance type.<\/p>\n<p>In this setup, I have explained a few things about the <code>vpc-cni<\/code>, but there are many functionalities available there, so if you are interested to know more about that, please refer to this <a href=\"https:\/\/aws.github.io\/aws-eks-best-practices\/networking\/vpc-cni\/?ref=devopscube.com\">document<\/a>.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/secondary-network-eks-cluster\/\" target=\"_blank\" rel=\"noopener noreferrer\">How to Setup VPC Secondary Network For EKS Cluster \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/secondary-network-eks-cluster\/<\/p>\n","protected":false},"author":1,"featured_media":742,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-741","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/741","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=741"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/741\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/742"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=741"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=741"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=741"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}