<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Calico &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/tag/calico/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Tue, 06 Jun 2023 21:05:46 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>AWS: EKS running out of IPs, increase pod density</title>
		<link>https://blog.andreev.it/2023/06/aws-eks-and-running-out-of-ips/</link>
					<comments>https://blog.andreev.it/2023/06/aws-eks-and-running-out-of-ips/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 06 Jun 2023 21:04:16 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Calico]]></category>
		<category><![CDATA[CNI]]></category>
		<category><![CDATA[EKS]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9358</guid>

					<description><![CDATA[When I was doing some proof of concept using EKS, I&#8217;ve noticed that there&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>When I was doing some proof of concept using EKS, I&#8217;ve noticed that there is a limitation on how many pods can a node run and how EKS CNI assigns the subnet IPs. Apparently, a node can run a <a href="https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt" rel="noopener" target="_blank">maximum of XX pods </a>(based on type) which is very low, e.g. a t3.large instance with 2 CPUs and 8GB RAM can run only 35 pods. On top of that, the IPs that are assigned for the pods are used from the existing pool of IPs and if you have a /24 subnet for the EKS cluster, you&#8217;ll run out of IPs in no time. Here is an example of what I did and options how to solve this issue.</p>
<h1>Option 1 &#8211; Calico 3rd party CNI</h1>
<p>I&#8217;ll provision an EKS cluster in a /24 subnet with 3 public and 3 private subnets.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksECIC --region us-east-2 --instance-types t3.large \
    --managed --vpc-cidr 192.168.100.0/24 --node-private-networking --version 1.24 --without-nodegroup
</pre>
<p>I am using the Ohio region which has 3 availability zones. The public and private subnets (total of 6) have /27 subnet which is 32 IPs. Not all of them are usable of course. Let&#8217;s create a node group with 3 nodes, and because <em>t3.large</em> is a Nitro instance, we can set the <a href="https://docs.aws.amazon.com/eks/latest/userguide/cni-increase-ip-addresses.html" rel="noopener" target="_blank">max number of pods</a> to be 110.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create nodegroup --cluster eksECIC --name old-nodegroup --nodes 3 \
    --node-type t3.large --node-private-networking --managed --max-pods-per-node 200
</pre>
<p>These are the available 31 IPs that I have in the private subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-01.png"><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-1024x201.png" alt="" width="1024" height="201" class="aligncenter size-large wp-image-9371" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-1024x201.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-300x59.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-768x151.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-1170x230.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01-585x115.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-01.png 1211w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s provision a small deployment with a single pod and 10 replicas and a network load balancer.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>&#8230;and this is the deployment file <strong>demo.yaml</strong>.</p>
<pre class="brush: yaml; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo
spec:
  replicas: 10
  selector:
    matchLabels:
      run: demo
  template:
    metadata:
      labels:
        run: demo
    spec:
      containers:
      - name: demo
        image: klimenta/serverip
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
spec:
  ports:
    - port: 80
      targetPort: 3000
      protocol: TCP
  type: LoadBalancer
  selector:
    run: demo
</pre>
<p>If you look at the available address now, there are 7. So from 31 to 7 with just 10 pods. Check this guy&#8217;s <a href="https://medium.com/codex/kubernetes-cluster-running-out-of-ip-addresses-on-aws-eks-c7b8e5dd8606" rel="noopener" target="_blank">blog </a>to see what&#8217;s going on.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-02.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-1024x116.png" alt="" width="1024" height="116" class="aligncenter size-large wp-image-9372" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-1024x116.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-300x34.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-768x87.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-1170x133.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02-585x66.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-02.png 1173w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s scale up the cluster to 100 pods.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl scale --replicas=100 deployment/demo
</pre>
<p>After a minute or two, check the running pods.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods --field-selector=status.phase=Running | wc -l
69
</pre>
<p>The console will show 0 available subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-03.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-1024x109.png" alt="" width="1024" height="109" class="aligncenter size-large wp-image-9373" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-1024x109.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-300x32.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-768x82.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-1170x125.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03-585x62.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-03.png 1212w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
If you check the pods with <strong>kubectl get pods</strong>, you&#8217;ll see the remaining containers have a <em>ContainerCreating </em>status.<br />
And if you check one of them, you&#8217;ll see this message.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl describe pod demo-698f6fc958-z8smb
....
d9b65ccfeccb7e9e7433ec61fef78faf83d1bae&quot;: plugin type=&quot;aws-cni&quot; name=&quot;aws-cni&quot; failed (add): add cmd: failed to assign an IP address to container
  Warning  FailedCreatePodSandBox  2m29s (x17 over 5m59s)  kubelet            (combined from similar events): Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox &quot;edfa987c3eddf415068691f914d740fa9ca9ca6ffdfb8db44d1340d913ece0b0&quot;: plugin type=&quot;aws-cni&quot; name=&quot;aws-cni&quot; failed (add): add cmd: failed to assign an IP address to container
</pre>
<p>Get the load balancer service. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get svc
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP                                                                     PORT(S)        AGE
kubernetes     ClusterIP      10.100.0.1      &lt;none&gt;                                                                          443/TCP        37m
loadbalancer   LoadBalancer   10.100.25.241   a105789cd3a264ac596c367fce463640-80d2b88acdc17771.elb.us-east-2.amazonaws.com   80:32396/TCP   17m
</pre>
<p>If you go to <em>a105789cd3a264&#8230;amazonaws.com</em> URL, you&#8217;ll see the load balancer hitting different pods. Wait for 2-3 minutes if you see that the page can&#8217;t be opened. It takes time for DNS to propagate.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/05/P163-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-1024x355.png" alt="" width="1024" height="355" class="aligncenter size-large wp-image-9374" srcset="https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-1024x355.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-300x104.png 300w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-768x266.png 768w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-1170x406.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04-585x203.png 585w, https://blog.andreev.it/wp-content/uploads/2023/05/P163-04.png 1470w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s delete the deployment and the old node group.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl delete -f demo.yaml
eksctl delete nodegroup --name old-nodegroup --cluster eksECIC
</pre>
<p>At this point, we still have the EKS cluster, the etcd database, networking etc, we just don&#8217;t have the nodes. Now it&#8217;s time to remove the EKS CNI, install Calico and add nodes that will use Calico.<br />
Installation is very simple. Remove the AWS CNI.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl delete daemonset -n kube-system aws-node
</pre>
<p>Check the pods in all namespaces.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods --all-namespaces
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
kube-system   coredns-5c5677bc78-2k88b   0/1     Pending   0          4m
kube-system   coredns-5c5677bc78-2zz2n   0/1     Pending   0          4m
</pre>
<p>You will see <strong>coredns </strong>in pending state, it can&#8217;t be deployed anywhere.<br />
Deploy Calico.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.25.1/manifests/tigera-operator.yaml
</pre>
<p>&#8230;keep going&#8230;</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f - &lt;&lt;EOF
kind: Installation
apiVersion: operator.tigera.io/v1
metadata:
  name: default
spec:
  kubernetesProvider: EKS
  cni:
    type: Calico
  calicoNetwork:
    bgp: Disabled
EOF
</pre>
<p>Deploy the new node group.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create nodegroup --cluster eksECIC --name new-nodegroup --nodes 3 \
    --node-type t3.large --node-private-networking --managed --max-pods-per-node 200
</pre>
<p>Let&#8217;s deploy the same demo deployment now.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>&#8230;scale it up to 601 pods.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl scale --replicas=601 deployment/demo
</pre>
<p>Wait 2-3 mins and check the running ones.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get pods --field-selector=status.phase=Running | wc -l
</pre>
<p>There will be 585 pods running, 195 per node. Much, much better. This time the remaining containers will be in <em>Pending </em>state.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl describe pod demo-698f6fc958-smlbq
....
Events:
  Type     Reason            Age                  From               Message
  ----     ------            ----                 ----               -------
  Warning  FailedScheduling  3m27s (x3 over 13m)  default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.
</pre>
<h1>Option 2 &#8211; CNI Custom networking and prefix assignment mode</h1>
<p>In this case, we&#8217;ll keep the AWS CNI network plugin, but we&#8217;ll use what is called CNI Custom networking, meaning we&#8217;ll use another CIDR attached to the VPC where pods will run. NOTE: The nodes will still run in the 192.168/24 subnets, it&#8217;s just the pods that will run in a much bigger IP space. In addition, this CNI Custom networking won&#8217;t solve the density of the pods that we can run on the nodes. We&#8217;ll need something called prefix assignment mode that will allow us to run much more pods on a node than usual. See <a href="https://aws.amazon.com/blogs/containers/leveraging-cni-custom-networking-alongside-security-groups-for-pods-in-amazon-eks/" rel="noopener" target="_blank">this </a>and <a href="https://docs.aws.amazon.com/eks/latest/userguide/cni-custom-network.html" rel="noopener" target="_blank">this </a>links for more information. For more info regarding prefix assignment mode check out <a href="https://aws.amazon.com/blogs/containers/amazon-vpc-cni-increases-pods-per-node-limits/" rel="noopener" target="_blank">this </a>link. The solution described below is much more complicated than if you just use Calico.<br />
Let&#8217;s provision a cluster with the same VPC CIDR and no nodegroups.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksECIC --region us-east-2 --instance-types t3.large \
    --managed --vpc-cidr 192.168.100.0/24 --node-private-networking --version 1.24 --without-nodegroup
</pre>
<p>Add a new CIDR and associate it with the VPC. I am using the console but you can use <em>aws ec2 associate-vpc-cidr-block</em> command.Go to the VPC where the EKS cluster resides. From the <strong>Actions </strong>button, select <strong>Edit CIDRs</strong> and then click the <strong>Add new IPv4 CIDR</strong> button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-05.png" alt="" width="982" height="511" class="aligncenter size-full wp-image-9386" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-05.png 982w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-05-300x156.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-05-768x400.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-05-585x304.png 585w" sizes="(max-width: 982px) 100vw, 982px" /></a><br />
I&#8217;ll add <strong>100.64.0.0/16</strong> CIDR and then I&#8217;ll create 3 /19 subnets.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1024x124.png" alt="" width="1024" height="124" class="aligncenter size-large wp-image-9387" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1024x124.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-300x36.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-768x93.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1536x185.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-1170x141.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06-585x71.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-06.png 1789w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
These 3 subnets will be where the pods will run. I also want them to initiate connections to Internet, so I&#8217;ll modify the route table and add the Internet Gateway. The three new subnets 100.64/19 that we created have the same route table.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-1024x287.png" alt="" width="1024" height="287" class="aligncenter size-large wp-image-9388" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-1024x287.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-768x216.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-1170x328.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07-585x164.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-07.png 1350w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Just add the route to 0.0.0.0/0 to go over Internet Gateway.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-1024x261.png" alt="" width="1024" height="261" class="aligncenter size-large wp-image-9389" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-1024x261.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-300x77.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-768x196.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-1170x299.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08-585x149.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-08.png 1528w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Let&#8217;s configure the custom networking on the cluster.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
</pre>
<p>Get the EKS cluster security group.</p>
<pre class="brush: bash; title: ; notranslate">
sec_grp=$(aws eks describe-cluster --name eksECIC --query cluster.resourcesVpcConfig.clusterSecurityGroupId --output text)
echo $sec_grp
</pre>
<p>For each of the subnets create a custom resource. Define the three 100.64 subnet-ids as variables.</p>
<pre class="brush: bash; title: ; notranslate">
export sub1=subnet-0f3ec5d75caf1b981
export sub2=subnet-0fc39edc5a1ce91c0
export sub3=subnet-0bd6bdfd5543aa086
</pre>
<pre class="brush: xml; title: ; notranslate">
cat &gt;us-east-2a.yaml &lt;&lt;EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: us-east-2a
spec: 
  securityGroups: 
    - $sec_grp
  subnet: $sub1
EOF
</pre>
<p>2nd subnet.</p>
<pre class="brush: xml; title: ; notranslate">
cat &gt;us-east-2b.yaml &lt;&lt;EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: us-east-2b
spec: 
  securityGroups: 
    - $sec_grp
  subnet: $sub2
EOF
</pre>
<p>3rd subnet.</p>
<pre class="brush: xml; title: ; notranslate">
cat &gt;us-east-2c.yaml &lt;&lt;EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata: 
  name: us-east-2c
spec: 
  securityGroups: 
    - $sec_grp
  subnet: $sub3
EOF
</pre>
<p><strong>NOTE:</strong>Make sure you name the metadata in line 5 as I did based on your region and AZ.<br />
Deploy the custom resources.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f us-east-2a.yaml
kubectl apply -f us-east-2b.yaml
kubectl apply -f us-east-2c.yaml
</pre>
<p>Confirm it looks good.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get ENIConfigs
NAME         AGE
us-east-2a   16s
us-east-2b   12s
us-east-2c   8s
</pre>
<p>Update your aws-node DaemonSet to automatically apply the ENIConfig for an Availability Zone to any new Amazon EC2 nodes created in your cluster.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone
</pre>
<p>Enable prefix assignment mode. This will allow much more pods per node.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
</pre>
<p>Another change.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set env ds aws-node -n kube-system WARM_PREFIX_TARGET=1
</pre>
<p>With the default setting, WARM_PREFIX_TARGET will allocate one additional complete (/28) prefix even if the existing prefix is used by only one pod. If the ENI does not have enough space to assign a prefix, a new ENI is generated.<br />
Provision the node group.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create nodegroup --cluster eksECIC --name my-nodegroup --nodes 3 \
    --node-type t3.large --node-private-networking --managed --max-pods-per-node 200
</pre>
<p>Check the nodes, they are running in the 192.168/24 original VPC CIDR. See the IP in the names of the nodes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                                            STATUS   ROLES    AGE     VERSION
ip-192-168-100-122.us-east-2.compute.internal   Ready    &lt;none&gt;   7m39s   v1.24.13-eks-0a21954
ip-192-168-100-155.us-east-2.compute.internal   Ready    &lt;none&gt;   7m39s   v1.24.13-eks-0a21954
ip-192-168-100-167.us-east-2.compute.internal   Ready    &lt;none&gt;   7m37s   v1.24.13-eks-0a21954
</pre>
<p>Check the pods.</p>
<pre class="brush: bash; highlight: [1,6,7]; title: ; notranslate">
kubectl get pods -A -o wide
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE    IP                NODE                                            NOMINATED NODE   READINESS GATES
kube-system   aws-node-txwrd             1/1     Running   0          9m4s   192.168.100.167   ip-192-168-100-167.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   aws-node-vdmv7             1/1     Running   0          9m6s   192.168.100.155   ip-192-168-100-155.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   aws-node-xss7c             1/1     Running   0          9m6s   192.168.100.122   ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   coredns-5c5677bc78-2lkcx   1/1     Running   0          80m    100.64.76.225     ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   coredns-5c5677bc78-mj6wr   1/1     Running   0          80m    100.64.76.224     ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-lt7v4           1/1     Running   0          9m6s   192.168.100.122   ip-192-168-100-122.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-m8pf6           1/1     Running   0          9m4s   192.168.100.167   ip-192-168-100-167.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
kube-system   kube-proxy-nlvw7           1/1     Running   0          9m6s   192.168.100.155   ip-192-168-100-155.us-east-2.compute.internal   &lt;none&gt;           &lt;none&gt;
</pre>
<p>Look at <strong>coredns </strong>pods. They are running in the new CIDR.<br />
Deploy the demo.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f demo.yaml
</pre>
<p>Get the load balancer service. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get svc
NAME           TYPE           CLUSTER-IP       EXTERNAL-IP                                                                     PORT(S)        AGE
kubernetes     ClusterIP      10.100.0.1       &lt;none&gt;                                                                          443/TCP        83m
loadbalancer   LoadBalancer   10.100.206.187   a7ad3ecf1d52e4fc58dbbdc550237009-ac4778e11af2c0ad.elb.us-east-2.amazonaws.com   80:30230/TCP   45s
</pre>
<p>If you go to <em>a7ad3ecf1d&#8230;amazonaws.com</em> URL, you&#8217;ll see the load balancer hitting different pods. Wait for 2-3 minutes if you see that the page can&#8217;t be opened. It takes time for DNS to propagate.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/06/P163-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-1024x460.png" alt="" width="1024" height="460" class="aligncenter size-large wp-image-9401" srcset="https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-1024x460.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-300x135.png 300w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-768x345.png 768w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-1170x526.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09-585x263.png 585w, https://blog.andreev.it/wp-content/uploads/2023/06/P163-09.png 1531w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
The pods are running in the 100.64 subnet.<br />
Scale up to 601 pods.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl scale --replicas=601 deployment/demo
</pre>
<p>Wait 2-3 mins and check the running ones.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get pods --field-selector=status.phase=Running | wc -l
</pre>
<p>I got 593 total pods. Slightly better than 585 with Calico. The ones that are in pending state show up an error that there are no more resources.</p>
<pre class="brush: bash; title: ; notranslate">
Events:
  Type     Reason            Age    From               Message
  ----     ------            ----   ----               -------
  Warning  FailedScheduling  3m49s  default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/06/aws-eks-and-running-out-of-ips/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>CentOS: Kubernetes, Flannel and Calico in a single master configuration</title>
		<link>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/</link>
					<comments>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 09 Apr 2019 15:46:01 +0000</pubDate>
				<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Calico]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[docker]]></category>
		<category><![CDATA[Flannel]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5127</guid>

					<description><![CDATA[Kubernetes (k8s) is getting a lot of attention and it&#8217;s becoming more and more&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Kubernetes (k8s) is getting a lot of attention and it&#8217;s becoming more and more popular even in enterprises that are quite IT conservative. In this post I&#8217;ll explain how to install Kubernetes on a single master and one node on CentOS. Then, we&#8217;ll install the networking plugins (CNI), Flannel or Calico. At the end I&#8217;ll show an example of how to deploy a simple Node.js app and do rollout update and undoing the rollout. </p>
<div style="border:1px solid red; padding:16px;">
<p style="text-align:center;"><strong><span style="color:#800000;">NOTE ABOUT VERSIONS</span> </strong></p>
<p><center>Kubernetes and the surrounding components are changed on a daily basis. What works today, might not work tomorrow.</center></p>
<p><center>This tutorial assumes that you use CentOS 7, Docker 18.x and Kubernetes 1.14.</center></p>
</div>
<p>For this post, there are some pre-requisites. You will need 2 servers with 2 CPUs and at least 2GB RAM. It is also recommended to have a working DNS. If you don&#8217;t have DNS in your lab, make sure you use <strong>/etc/hosts</strong> for hostname resolution, but you can get away if you use IPs only (not recommended). </p>
<h1>Pre-requisites</h1>
<p>On a fresh installed CentOS 7, do these pre-req commands on both the master and the node at the same time. You need to be logged as root.<br />
Make sure SELinux is disabled.</p>
<pre class="brush: bash; title: ; notranslate">
setenforce 0
sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
</pre>
<p>Kubernetes doesn&#8217;t like swap, so if you have it in <strong>/etc/fstab</strong>, disable the swap.</p>
<pre class="brush: bash; title: ; notranslate">
swapoff -a
sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
</pre>
<p>Enable the bridge network module.</p>
<pre class="brush: bash; title: ; notranslate">
modprobe br_netfilter
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-iptables
echo '1' &gt; /proc/sys/net/bridge/bridge-nf-call-ip6tables
</pre>
<p>Install Docker and change the cgroup from cfsgroup to systemd.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install yum-utils device-mapper-persistent-data lvm2
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum -y install docker-ce
mkdir /etc/docker
cat &lt;&lt;EOF &gt; /etc/docker/daemon.json
{
  &quot;exec-opts&quot;: &#x5B;&quot;native.cgroupdriver=systemd&quot;],
  &quot;log-driver&quot;: &quot;json-file&quot;,
  &quot;log-opts&quot;: {
    &quot;max-size&quot;: &quot;100m&quot;
  },
  &quot;storage-driver&quot;: &quot;overlay2&quot;,
  &quot;storage-opts&quot;: &#x5B;
    &quot;overlay2.override_kernel_check=true&quot;
  ]
}
EOF
mkdir -p /etc/systemd/system/docker.service.d
systemctl daemon-reload
systemctl enable docker &amp;&amp; systemctl start docker
</pre>
<p>Try this line and make sure the output says <strong>systemd</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker info | grep -i cgroup
Cgroup Driver: systemd
</pre>
<p>Add the Kubernetes repo. </p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt;EOF &gt; /etc/yum.repos.d/kubernetes.repo
&#x5B;kubernetes]
name=Kubernetes
baseurl=https://packages.cloud.google.com/yum/repos/kubernetes-el7-x86_64
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=https://packages.cloud.google.com/yum/doc/yum-key.gpg https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg
EOF
</pre>
<h1>Master Node</h1>
<p>Open the firewall <a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports" rel="noopener noreferrer" target="_blank">ports</a>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=6443/tcp --permanent
firewall-cmd --add-port=2379-2380/tcp --permanent
firewall-cmd --add-port=10250-10252/tcp --permanent
firewall-cmd --reload
</pre>
<p>From the repo install kubelet, kubectl and kubeadm and make sure Kubernetes starts on boot.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install kubelet kubectl kubeadm
systemctl enable kubelet
</pre>
<p>Don&#8217;t start Kubernetes yet. It will fail with a message that it can&#8217;t find a config yaml file. Just initialize the cluster. This will also start the kubelet service. Pick one choice (Flannel or Calico).<br />
<strong>NOTE: This line initializes the cluster to be used for Flannel.</strong> </p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=10.244.0.0/16
</pre>
<p><strong>NOTE: This line initializes the cluster to be used for Calico.</strong> </p>
<pre class="brush: bash; title: ; notranslate">
kubeadm init --pod-network-cidr=192.168.0.0/16
</pre>
<p>Look at the bottom of the output. You should see something like this. Lines 5,6,7 and 15 and 16 are important. </p>
<pre class="brush: plain; highlight: [5,6,7,15,16]; title: ; notranslate">
Your Kubernetes control-plane has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run &quot;kubectl apply -f &#x5B;podnetwork].yaml&quot; with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

Then you can join any number of worker nodes by running the following on each as root:

kubeadm join 192.168.1.175:6443 --token 0z3iov.28rz29hxw9ft4jmg \
    --discovery-token-ca-cert-hash sha256:782d37b5c870ebebd2f17cc3ba1424f305e6a3e293afc04fc2030edfab6bf4b0
</pre>
<p>This means that the cluster initialized OK.<br />
Check the status of both Docker and Kubernetes.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl status docker | grep Active
systemctl status kubelet | grep Active
</pre>
<p>Make sure they are both running. Check <strong>/var/log/messages</strong> if you have any issues.</p>
<h1>Nodes (workers)</h1>
<p>On the worker nodes, make sure you do the same as you did on the master (swap, SELinux, Docker) except that you don&#8217;t have to install kubectl. </p>
<pre class="brush: bash; title: ; notranslate">
yum -y install kubelet kubeadm
systemctl enable kubelet
</pre>
<p>Open the firewall <a href="https://kubernetes.io/docs/setup/independent/install-kubeadm/#check-required-ports" rel="noopener noreferrer" target="_blank">ports</a>. </p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=10250/tcp --permanent
firewall-cmd --add-port=30000-32767/tcp --permanent
firewall-cmd --reload
</pre>
<p>Now, you can join the cluster. Use the command that was the output from the <strong>kubeadm init</strong> on the master (see above &#8211; lines 15 and 16).</p>
<pre class="brush: bash; title: ; notranslate">
kubeadm join 192.168.1.175:6443 --token 0z3iov.28rz29hxw9ft4jmg \
    --discovery-token-ca-cert-hash sha256:782d37b5c870ebebd2f17cc3ba1424f305e6a3e293afc04fc2030edfab6bf4b0
</pre>
<p>That&#8217;s how you join nodes to the master. Replace <strong>192.168.1.175</strong> with the IP or hostname of your master node. If everything is OK, you&#8217;ll see something like this.</p>
<pre class="brush: bash; title: ; notranslate">
This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the control-plane to see this node join the cluster.
</pre>
<h1>Kubernetes user</h1>
<p>While still logged as root on the master, create the Kubernetes user that you will use for managing the k8s cluster. In my case, I&#8217;ll create a user called k8s with <strong>secret </strong>as password.</p>
<pre class="brush: bash; title: ; notranslate">
useradd k8s -g docker
usermod -aG wheel k8s
echo -e &quot;secret\nsecret&quot; | passwd k8s
</pre>
<p>Log as this user (k8s) and execute these commands. These lines were also an output of the <strong>kubeadm init</strong> command above (5,6 and 7). </p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
</pre>
<p>If you want to add another user to manage the Kubernetes cluster, make sure you execute these 3 lines above for that user. Check if everything looks good.</p>
<pre class="brush: bash; title: ; notranslate">
docker ps
</pre>
<p>You should see a bunch of Kubernetes system containers running (etcd, scheduler, API server).<br />
Then check the nodes.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                      STATUS     ROLES    AGE   VERSION
k8smaster.andreev.local   NotReady   master   12m   v1.14.0
k8snode1.andreev.local    NotReady   &lt;none&gt;   10m   v1.14.0
</pre>
<p>The reason the master and the node are not ready is because we don&#8217;t have a network for the cluster. </p>
<h1>Network CNI</h1>
<p>Depending on how you&#8217;ve initialized the cluster, pick one of the network plugins (Flannel or Calico). </p>
<h2>Flannel</h2>
<p>For the network to work, we&#8217;ll have to use one of the CNI plugins. There are many, Flannel, Weave Net, Calico etc.<br />
Let&#8217;s install Flannel. Do this on the master only logged as k8s user. The master will take care of the nodes.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml
</pre>
<p>Start this little infinite loop and you&#8217;ll see that after 20-30 seconds, both the master and the node will change their status to <strong>Ready</strong>. Hit Ctrl-C to end.</p>
<pre class="brush: bash; title: ; notranslate">
while true
do
kubectl get nodes
sleep 3
done
</pre>
<p>Now, you have a fully working cluster ready. </p>
<h2>Calico</h2>
<p>For the network to work, we&#8217;ll have to use one of the CNI plugins. There are many, Flannel, Weave Net, Calico etc.<br />
Let&#8217;s install Flannel. Do this on the master only logged as k8s user. The master will take care of the nodes.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/rbac-kdd.yaml
kubectl apply -f https://docs.projectcalico.org/v3.3/getting-started/kubernetes/installation/hosted/kubernetes-datastore/calico-networking/1.7/calico.yaml
</pre>
<p>Start this little infinite loop and you&#8217;ll see that after 20-30 seconds, both the master and the node will change their status to <strong>Ready</strong>. Hit Ctrl-C to end.</p>
<pre class="brush: bash; title: ; notranslate">
while true
do
kubectl get nodes
sleep 3
done
</pre>
<p>Now, you have a fully working cluster ready. </p>
<h1>Deployment</h1>
<p>In this example, I&#8217;ll create a small container that runs a Node.js app that when run it will display &#8220;Hello from &#8221; the hostname of the container. On top of that we&#8217;ll create a load balancer, so we can see how that works.<br />
First, let&#8217;s create the container based on Node.js image. Create a file named <strong>Dockerfile </strong>with this content. </p>
<pre class="brush: bash; title: ; notranslate">
FROM node:latest
LABEL maintainer &quot;kliment@andreev.it&quot;
ADD appv1.js /app.js
ENTRYPOINT &#x5B;&quot;node&quot;, &quot;app.js&quot;]
</pre>
<p>This is our application. Save it as <strong>appv1.js</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
const http = require('http');
const os = require('os');
const port = 3000;

const server = http.createServer((req, res) =&gt; {
  res.statusCode = 200;
  res.end('Hello from ' + os.hostname() + '\n');
});

server.listen(port);
</pre>
<p>Create the container. You&#8217;ll need a valid Docker Hub login. In my case, my username is klimenta. Replace it with yours.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t klimenta/appv1:latest .
</pre>
<p>It&#8217;s time to login to Docker Hub and upload the image there. You&#8217;ll be prompted for a username and password.</p>
<pre class="brush: bash; title: ; notranslate">
docker login
</pre>
<p>Upload the image.</p>
<pre class="brush: bash; title: ; notranslate">
docker push klimenta/appv1:latest
</pre>
<p>Create a Kubernetes deployment file named <strong>deployment.yaml</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: appv1
spec:
  replicas: 3
  template:
    metadata:
      name: appv1
      labels:
        app: appv1
    spec:
      containers:
      - image: klimenta/appv1:latest
        name: nodejs
---
apiVersion: v1
kind: Service
metadata:
  name: loadbalancer
spec:
  type: LoadBalancer
  selector:
    app: appv1
  ports:
  - port: 80
    targetPort: 3000
</pre>
<p>We are creating a deployment with 3 replicas and a load balancer that listens on port 80 and sends the traffic to port 3000 on the pods with our application.<br />
Create the deployment and the load balanced service.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create -f deployment.yaml
</pre>
<p>After about 30 seconds, you&#8217;ll see that your pods are ready. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
appv1-596dd64666-4k7qn   1/1     Running   0          93m
appv1-596dd64666-gn5gr   1/1     Running   0          93m
appv1-596dd64666-vv9h5   1/1     Running   0          93m
</pre>
<p>The load balancer is also ready. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get svc
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
kubernetes     ClusterIP      10.96.0.1       &lt;none&gt;        443/TCP        102m
loadbalancer   LoadBalancer   10.111.212.41   &lt;pending&gt;     80:30310/TCP   94m
</pre>
<p>If you hit the load balancer, you&#8217;ll see a response. Replace the IP with yours.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl http://10.111.212.41
Hello from appv1-596dd64666-gn5gr
</pre>
<p>Now, let&#8217;s say that we created a new version of our application. Copy appv1.js as appv2.js and change  <strong>appv2.js</strong> a little bit.</p>
<pre class="brush: bash; title: ; notranslate">
cp appv1.js appv2.js
</pre>
<p>The appv2.js should look like this.</p>
<pre class="brush: xml; highlight: [7]; title: ; notranslate">
const http = require('http');
const os = require('os');
const port = 3000;

const server = http.createServer((req, res) =&gt; {
  res.statusCode = 200;
  res.end('Greetings from ' + os.hostname() + '\n');
});

server.listen(port);
</pre>
<p>Change the <strong>Dockerfile </strong>to look like this.</p>
<pre class="brush: bash; title: ; notranslate">
FROM node:latest
LABEL maintainer &quot;kliment@andreev.it&quot;
ADD appv2.js /app.js
ENTRYPOINT &#x5B;&quot;node&quot;, &quot;app.js&quot;]
</pre>
<p>Build the new image and upload it to Docker Hub.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t klimenta/appv2:latest .
docker push klimenta/appv2:latest
</pre>
<p>Deploy the new application.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl set image deployment appv1 nodejs=klimenta/appv2:latest
</pre>
<p>If you check the app now, you&#8217;ll see that it reflects the new version.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
curl http://10.111.212.41
Greetings from appv1-5d949774f5-b794k
</pre>
<p>But what if there is a bug in our application and we want to revert it back to the initial one? Easy.</p>
<pre class="brush: bash; highlight: [1,3]; title: ; notranslate">
kubectl rollout undo deployment appv1
deployment.extensions/appv1 rolled back
curl http://10.111.212.41
Hello from appv1-596dd64666-94gqh
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/04/centos-kubernetes-flannel-and-calico-in-a-single-master-configuration/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
