<?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>EKS &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/tag/eks/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Sun, 21 Jan 2024 13:41:42 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>AWS: Install EKS cluster and Ingress Controller using Terraform and GitLab using OIDC and web identity</title>
		<link>https://blog.andreev.it/2023/11/aws-install-eks-cluster-and-ingress-controller-using-terraform-and-gitlab-using-oidc-and-web-identity/</link>
					<comments>https://blog.andreev.it/2023/11/aws-install-eks-cluster-and-ingress-controller-using-terraform-and-gitlab-using-oidc-and-web-identity/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 21 Nov 2023 18:39:16 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[GitLab]]></category>
		<category><![CDATA[ingress]]></category>
		<category><![CDATA[OIDC]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9581</guid>

					<description><![CDATA[In this post, I&#8217;ll explain how to create an EKS cluster in AWS and&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post, I&#8217;ll explain how to create an EKS cluster in AWS and add an Ingress Controller. We&#8217;ll use GitLab as a repo and execute the CI/CD pipeline from there.<br />
For this you&#8217;ll need:</p>
<ul>
<li>GitLab and AWS account</li>
<li>Terraform, AWS CLI, helm installed</li>
</ul>
<h1>AWS</h1>
<p>From the IAM console, create a new Identity Provider in AWS and use <strong>https://gitlab.com</strong> for both <em>Provider URL</em> and <em>Audience</em>. For self hosted GitLab use <em>gitlab.domain.com</em> or whatever is your GitLab URL.<br />
Click on <strong>Get thumbprint</strong> before clicking on <strong>Add provider</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-02.png"><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-1020x1024.png" alt="" width="1020" height="1024" class="aligncenter size-large wp-image-9585" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-1020x1024.png 1020w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-300x300.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-150x150.png 150w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-768x771.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-1170x1175.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02-585x587.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-02.png 1288w" sizes="(max-width: 1020px) 100vw, 1020px" /></a><br />
In the IAM console, create a role. I&#8217;ll name the role <strong>rolGitLab</strong>, name it whatever you like. When you create the policy, choose <strong>Custom trust policy</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-04.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1024x647.png" alt="" width="1024" height="647" class="aligncenter size-large wp-image-9587" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1024x647.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-300x190.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-768x485.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1536x971.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-1170x739.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04-585x370.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-04.png 1725w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now, here is what you have to decide. If you want to manage the cluster from GitLab only, you can ommit lines 16-23 where I allow the IAM user called gitlab to assume this role. But if you want some IAM user to manage the cluster using aws cli or kubectl, add the ARN of that IAM user in line 20. Add <em>AdministratorAccess </em>to that gitlab user but you can fine-tune those policies later. If you want to manage the cluster from GitLab, you have to register an agent and provide a config file. You can read how to do that <a href="https://docs.gitlab.com/ee/user/clusters/agent/" rel="noopener" target="_blank">here</a>.<br />
Replace the default custom trust policy with this JSON.</p>
<pre class="brush: xml; highlight: [7,12,20]; title: ; notranslate">
{
	&quot;Version&quot;: &quot;2012-10-17&quot;,
	&quot;Statement&quot;: &#x5B;
		{
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: {
				&quot;Federated&quot;: &quot;arn:aws:iam::123456789012:oidc-provider/gitlab.com&quot;
			},
			&quot;Action&quot;: &quot;sts:AssumeRoleWithWebIdentity&quot;,
			&quot;Condition&quot;: {
				&quot;StringLike&quot;: {
					&quot;gitlab.com:sub&quot;: &quot;project_path:*/eks-gitlab:ref_type:branch:ref:main&quot;
				}
			}
		},
		{
			&quot;Sid&quot;: &quot;&quot;,
			&quot;Effect&quot;: &quot;Allow&quot;,
			&quot;Principal&quot;: {
				&quot;AWS&quot;: &quot;arn:aws:iam::123456789012:user/gitlab&quot;
			},
			&quot;Action&quot;: &quot;sts:AssumeRole&quot;
		}
	]
}
</pre>
<p>In line 7 and 20, change the <em>123456789012 </em>with your account number and in line 7 change <em>gitlab.com</em> with however you named the Identity Provider (IdP). In line 12 replace <em>eks-gitlab </em>with how you plan to name your GitLab project. In line 20 change the <em>gitlab </em>user to however you named your IAM user to access the EKS cluster. Click <strong>Next </strong>and when asked for a permission policy, choose <strong>AdministratorAccess</strong>. Best practice is to limit access to only what&#8217;s needed, but EKS has so many policies, it will take a lot to list them all.<br />
In case you created the gitlab IAM user, attach the AdministratorPolicy (fine tune later) and this inline policy. Change the ARN in line 7. Name this inline policy something like <em>polAssumeGitLabRole</em>. </p>
<pre class="brush: bash; highlight: [7]; title: ; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &quot;sts:AssumeRole&quot;,
            &quot;Resource&quot;: &quot;arn:aws:iam::123456789012:role/rolGitLab&quot;
        }
    ]
}
</pre>
<h1>GitLab</h1>
<p>Create a new blank project in Gitlab with the default settings. In my case the project name is called <strong>eks-gitlab</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-01.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-1024x737.png" alt="" width="1024" height="737" class="aligncenter size-large wp-image-9615" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-1024x737.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-300x216.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-768x553.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-1170x843.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01-585x421.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-01.png 1497w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Go to <strong>Settings </strong>on the left, then <strong>CI/CD</strong> and then <strong>Variables</strong>. Add these two variables. </p>
<ul>
<li>AWS_PROFILE and a value of <strong>oidc</strong>. Yes, the value is <strong>oidc</strong>. Just type these 4 characters for the value.</li>
<li>ROLE_ARN and a value of the role ARN from the role you just created for <strong>rolGitLab</strong></li>
</ul>
<p>Create a <strong>.gitlab-ci.yml</strong> file with the following content. Change the domain for <em>gitlab.com</em> at line 7 if you use self-hosted GitLab.</p>
<pre class="brush: yaml; highlight: [7]; title: ; notranslate">
assume role:
  image: 
    name: amazon/aws-cli:latest
    entrypoint: &#x5B;&quot;&quot;]
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.com
  before_script:
    - mkdir -p ~/.aws
    - echo &quot;${GITLAB_OIDC_TOKEN}&quot; &gt; /tmp/web_identity_token
    - echo -e &quot;&#x5B;profile oidc]\nrole_arn=${ROLE_ARN}\nweb_identity_token_file=/tmp/web_identity_token&quot; &gt; ~/.aws/config
  script:
      - aws sts get-caller-identity
      - aws s3 ls
</pre>
<p>By the time you commit changes, the pipeline will start and if everything is OK, you&#8217;ll see your S3 buckets in the logs of the job if you go to <strong>Build | Jobs</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-878x1024.png" alt="" width="878" height="1024" class="aligncenter size-large wp-image-9617" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-878x1024.png 878w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-257x300.png 257w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-768x895.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05-585x682.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-05.png 1012w" sizes="(max-width: 878px) 100vw, 878px" /></a><br />
One more thing related to Terraform state as we&#8217;ll keep the state in GitLab is that we have to create an <strong>Access Token</strong>. If you have a self-hosted or paid GitLab account, create an <em>Access Token</em> from the <em>Project | Settings | Access Tokens</em>.<br />
If you have a free GitLab account, you have to create a <strong>Personal Access Token</strong> and you do that if you click on your avatar in the upper left corner, then select <strong>Edit Profile</strong> and then click <strong>Access Tokens</strong> on the left. Click <strong>Add new token</strong>, name it <strong>GITLAB_PAT</strong> and select API only. Click <strong>Create personal access token</strong>. Once the token has been created click on the eye icon where it says <em>Your new personal access token</em> and copy &#038; paste somewhere safe.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P167-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-852x1024.png" alt="" width="852" height="1024" class="aligncenter size-large wp-image-9620" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-852x1024.png 852w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-250x300.png 250w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-768x923.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03-585x703.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P167-03.png 964w" sizes="(max-width: 852px) 100vw, 852px" /></a><br />
Go to the project settings and add these 2 variables.</p>
<ul>
<li>TF_USERNAME which is your username, click on the avatar, look at the @ under your name, your username is without the @ sign</li>
<li>TF_PASSWORD which is the value of your personal access token you just created</li>
</ul>
<h1>Local machine</h1>
<p>Now that we know the pipeline is working properly, let&#8217;s clone the project on our local machine.<br />
If you plan to manage the cluster from your local machine, now it&#8217;s time to configure the <strong>gitlab </strong>IAM user on your local machine using access keys.<br />
You need to have a valid <a href="https://docs.gitlab.com/ee/user/ssh.html" rel="noopener" target="_blank">key </a>in order to clone your private repo.<br />
Upload your .pub key to GitLab by clicking your avatar, then <strong>Edit Profile</strong>, click <strong>SSH Keys</strong> and then <strong>Add new key</strong>.<br />
Finally, check the connection with the following command. It doesn&#8217;t matter what your local Linux user is, just use git@gitlab.com as the username@hostname. Type as is. </p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
ssh -T git@gitlab.com
Welcome to GitLab, @&#x5B;your_gitlab_username_here]
</pre>
<p>Once you clone the repo add the following files locally.<br />
<strong>FILE: .gitignore</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
# Local .terraform directories
**/.terraform/*

# .tfstate files
*.tfstate
*.tfstate.*

# Crash log files
crash.log
crash.*.log

# Exclude all .tfvars files, which are likely to contain sensitive data, such as
# password, private keys, and other secrets. These should not be part of version 
# control as they are data points which are potentially sensitive and subject 
# to change depending on the environment.
*.tfvars
*.tfvars.json

# Ignore override files as they are usually used to override resources locally and so
# are not checked in
override.tf
override.tf.json
*_override.tf
*_override.tf.json

# Include override files you do wish to add to version control using negated pattern
# !example_override.tf

# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan
# example: *tfplan*

# Ignore CLI configuration files
.terraformrc
terraform.rc
</pre>
<p><strong>FILE: LICENSE</strong></p>
<pre class="brush: plain; collapse: true; light: false; title: ; toolbar: true; notranslate">
This is free and unencumbered software released into the public domain.

Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.

In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.

THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

For more information, please refer to &lt;https://unlicense.org&gt;
</pre>
<p><strong>FILE: alb_ingress_iam_policy.json</strong></p>
<pre class="brush: xml; collapse: true; light: false; title: ; toolbar: true; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;iam:CreateServiceLinkedRole&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;iam:AWSServiceName&quot;: &quot;elasticloadbalancing.amazonaws.com&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:DescribeAccountAttributes&quot;,
                &quot;ec2:DescribeAddresses&quot;,
                &quot;ec2:DescribeAvailabilityZones&quot;,
                &quot;ec2:DescribeInternetGateways&quot;,
                &quot;ec2:DescribeVpcs&quot;,
                &quot;ec2:DescribeVpcPeeringConnections&quot;,
                &quot;ec2:DescribeSubnets&quot;,
                &quot;ec2:DescribeSecurityGroups&quot;,
                &quot;ec2:DescribeInstances&quot;,
                &quot;ec2:DescribeNetworkInterfaces&quot;,
                &quot;ec2:DescribeTags&quot;,
                &quot;ec2:GetCoipPoolUsage&quot;,
                &quot;ec2:DescribeCoipPools&quot;,
                &quot;elasticloadbalancing:DescribeLoadBalancers&quot;,
                &quot;elasticloadbalancing:DescribeLoadBalancerAttributes&quot;,
                &quot;elasticloadbalancing:DescribeListeners&quot;,
                &quot;elasticloadbalancing:DescribeListenerCertificates&quot;,
                &quot;elasticloadbalancing:DescribeSSLPolicies&quot;,
                &quot;elasticloadbalancing:DescribeRules&quot;,
                &quot;elasticloadbalancing:DescribeTargetGroups&quot;,
                &quot;elasticloadbalancing:DescribeTargetGroupAttributes&quot;,
                &quot;elasticloadbalancing:DescribeTargetHealth&quot;,
                &quot;elasticloadbalancing:DescribeTags&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;cognito-idp:DescribeUserPoolClient&quot;,
                &quot;acm:ListCertificates&quot;,
                &quot;acm:DescribeCertificate&quot;,
                &quot;iam:ListServerCertificates&quot;,
                &quot;iam:GetServerCertificate&quot;,
                &quot;waf-regional:GetWebACL&quot;,
                &quot;waf-regional:GetWebACLForResource&quot;,
                &quot;waf-regional:AssociateWebACL&quot;,
                &quot;waf-regional:DisassociateWebACL&quot;,
                &quot;wafv2:GetWebACL&quot;,
                &quot;wafv2:GetWebACLForResource&quot;,
                &quot;wafv2:AssociateWebACL&quot;,
                &quot;wafv2:DisassociateWebACL&quot;,
                &quot;shield:GetSubscriptionState&quot;,
                &quot;shield:DescribeProtection&quot;,
                &quot;shield:CreateProtection&quot;,
                &quot;shield:DeleteProtection&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:AuthorizeSecurityGroupIngress&quot;,
                &quot;ec2:RevokeSecurityGroupIngress&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:CreateSecurityGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:CreateTags&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:security-group/*&quot;,
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;ec2:CreateAction&quot;: &quot;CreateSecurityGroup&quot;
                },
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:CreateTags&quot;,
                &quot;ec2:DeleteTags&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:ec2:*:*:security-group/*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;true&quot;,
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;ec2:AuthorizeSecurityGroupIngress&quot;,
                &quot;ec2:RevokeSecurityGroupIngress&quot;,
                &quot;ec2:DeleteSecurityGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:CreateLoadBalancer&quot;,
                &quot;elasticloadbalancing:CreateTargetGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:CreateListener&quot;,
                &quot;elasticloadbalancing:DeleteListener&quot;,
                &quot;elasticloadbalancing:CreateRule&quot;,
                &quot;elasticloadbalancing:DeleteRule&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:AddTags&quot;,
                &quot;elasticloadbalancing:RemoveTags&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:elasticloadbalancing:*:*:targetgroup/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*&quot;
            ],
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;true&quot;,
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:AddTags&quot;,
                &quot;elasticloadbalancing:RemoveTags&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:elasticloadbalancing:*:*:listener/net/*/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:listener/app/*/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:listener-rule/net/*/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:listener-rule/app/*/*/*&quot;
            ]
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:ModifyLoadBalancerAttributes&quot;,
                &quot;elasticloadbalancing:SetIpAddressType&quot;,
                &quot;elasticloadbalancing:SetSecurityGroups&quot;,
                &quot;elasticloadbalancing:SetSubnets&quot;,
                &quot;elasticloadbalancing:DeleteLoadBalancer&quot;,
                &quot;elasticloadbalancing:ModifyTargetGroup&quot;,
                &quot;elasticloadbalancing:ModifyTargetGroupAttributes&quot;,
                &quot;elasticloadbalancing:DeleteTargetGroup&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;,
            &quot;Condition&quot;: {
                &quot;Null&quot;: {
                    &quot;aws:ResourceTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:AddTags&quot;
            ],
            &quot;Resource&quot;: &#x5B;
                &quot;arn:aws:elasticloadbalancing:*:*:targetgroup/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/net/*/*&quot;,
                &quot;arn:aws:elasticloadbalancing:*:*:loadbalancer/app/*/*&quot;
            ],
            &quot;Condition&quot;: {
                &quot;StringEquals&quot;: {
                    &quot;elasticloadbalancing:CreateAction&quot;: &#x5B;
                        &quot;CreateTargetGroup&quot;,
                        &quot;CreateLoadBalancer&quot;
                    ]
                },
                &quot;Null&quot;: {
                    &quot;aws:RequestTag/elbv2.k8s.aws/cluster&quot;: &quot;false&quot;
                }
            }
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:RegisterTargets&quot;,
                &quot;elasticloadbalancing:DeregisterTargets&quot;
            ],
            &quot;Resource&quot;: &quot;arn:aws:elasticloadbalancing:*:*:targetgroup/*/*&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: &#x5B;
                &quot;elasticloadbalancing:SetWebAcl&quot;,
                &quot;elasticloadbalancing:ModifyListener&quot;,
                &quot;elasticloadbalancing:AddListenerCertificates&quot;,
                &quot;elasticloadbalancing:RemoveListenerCertificates&quot;,
                &quot;elasticloadbalancing:ModifyRule&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
</pre>
<p><strong>FILE: backend.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
terraform {
  backend &quot;http&quot; {
  }
}
</pre>
<p><strong>FILE: data.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
data &quot;aws_availability_zones&quot; &quot;available&quot; {
}

data &quot;aws_eks_cluster&quot; &quot;cluster&quot; {
  name = module.eks.cluster_name
  depends_on = &#x5B;module.eks.cluster_name]
}

data &quot;aws_eks_cluster_auth&quot; &quot;cluster&quot; {
  name = module.eks.cluster_name
  depends_on = &#x5B;module.eks.cluster_name]
}
</pre>
<p><strong>FILE: main.tf NOTE:</strong> Make sure you add lines 40-41 and specify the <strong>rolGitlab</strong> role created earlier. If you don&#8217;t do this when you destroy the cluster, the KMS key for EKS will stay there and there is no way you can delete it unless you call AWS support.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
# Create a VPC
module &quot;vpc&quot; {
  source  = &quot;terraform-aws-modules/vpc/aws&quot;
  version = &quot;5.0.0&quot;

  name = var.vpcName

  cidr = var.vpcCIDR
  azs  = slice(data.aws_availability_zones.available.names, 0, 3)

  private_subnets = var.privateSubnets
  public_subnets  = var.publicSubnets

  enable_nat_gateway   = var.enable_nat_gateway
  single_nat_gateway   = var.single_nat_gateway
  enable_dns_hostnames = var.enable_dns_hostnames

  public_subnet_tags = {
    &quot;kubernetes.io/cluster/${var.cluster_name}&quot; = &quot;shared&quot;
    &quot;kubernetes.io/role/elb&quot;                    = 1
  }

  private_subnet_tags = {
    &quot;kubernetes.io/cluster/${var.cluster_name}&quot; = &quot;shared&quot;
    &quot;kubernetes.io/role/internal-elb&quot;           = 1
  }
}

# Create an EKS cluster
module &quot;eks&quot; {
  source  = &quot;terraform-aws-modules/eks/aws&quot;
  version = &quot;19.15.3&quot;

  cluster_name    = var.cluster_name
  cluster_version = var.cluster_version

  vpc_id                         = module.vpc.vpc_id
  subnet_ids                     = module.vpc.private_subnets
  cluster_endpoint_public_access = var.cluster_endpoint_public_access
  kms_key_administrators         = &#x5B;&quot;arn:aws:iam::123456789012:role/rolGitLab&quot;]
  kms_key_enable_default_policy  = true

  eks_managed_node_group_defaults = {
    ami_type = var.ami_type
  }

  eks_managed_node_groups = {
    one = {
      name = var.nodeGroup

      instance_types = var.instanceTypes

      min_size     = var.minSize
      max_size     = var.maxSize
      desired_size = var.desiredSize
    }
  }
}

# Create an IAM policy for the ALB ingress
resource &quot;aws_iam_policy&quot; &quot;worker_policy&quot; {
  name        = &quot;worker-policy&quot;
  description = &quot;Worker policy for the ALB Ingress&quot;

  policy = file(var.iamALBIngressPolicyFileName)
}

resource &quot;aws_iam_role_policy_attachment&quot; &quot;additional&quot; {
  for_each = module.eks.eks_managed_node_groups

  policy_arn = aws_iam_policy.worker_policy.arn
  role       = each.value.iam_role_name
}

# Crete the Ingress using Helm chart from AWS
resource &quot;helm_release&quot; &quot;ingress&quot; {
  name       = &quot;ingress&quot;
  chart      = &quot;aws-load-balancer-controller&quot;
  repository = &quot;https://aws.github.io/eks-charts&quot;
  version    = &quot;1.6.1&quot;

  set {
    name  = &quot;autoDiscoverAwsRegion&quot;
    value = &quot;true&quot;
  }

  set {
    name  = &quot;autoDiscoverAwsVpcID&quot;
    value = &quot;true&quot;
  }

  set {
    name  = &quot;clusterName&quot;
    value = module.eks.cluster_name
  }
}
</pre>
<p><strong>FILE: outputs.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
output &quot;cluster_endpoint&quot; {
  description = &quot;Endpoint for EKS control plane&quot;
  value       = module.eks.cluster_endpoint
}

output &quot;cluster_security_group_id&quot; {
  description = &quot;Security group ids attached to the cluster control plane&quot;
  value       = module.eks.cluster_security_group_id
}

output &quot;region&quot; {
  description = &quot;AWS region&quot;
  value       = var.region
}

output &quot;cluster_name&quot; {
  description = &quot;Kubernetes Cluster Name&quot;
  value       = module.eks.cluster_name
}
</pre>
<p><strong>FILE: providers.tf</strong></p>
<pre class="brush: bash; title: ; notranslate">
provider &quot;aws&quot; {
  region = var.region

  shared_config_files      = &#x5B;&quot;~/.aws/config&quot;]  
  profile                  = &quot;oidc&quot;
}

provider &quot;helm&quot; {
  kubernetes {
    host                   = data.aws_eks_cluster.cluster.endpoint
    token                  = data.aws_eks_cluster_auth.cluster.token
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.cluster.certificate_authority.0.data)
  }
}
</pre>
<p><strong>FILE: variables.tf</strong></p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
variable &quot;region&quot; {
  type    = string
  default = &quot;us-east-2&quot;
}

variable &quot;vpcName&quot; {
  type    = string
  default = &quot;vpcFromGitLab&quot;
}

variable &quot;vpcCIDR&quot; {
  type    = string
  default = &quot;10.0.0.0/16&quot;
}

variable &quot;privateSubnets&quot; {
  type    = list(string)
  default = &#x5B;&quot;10.0.1.0/24&quot;, &quot;10.0.2.0/24&quot;, &quot;10.0.3.0/24&quot;]
}

variable &quot;publicSubnets&quot; {
  type    = list(string)
  default = &#x5B;&quot;10.0.4.0/24&quot;, &quot;10.0.5.0/24&quot;, &quot;10.0.6.0/24&quot;]
}

variable &quot;enable_nat_gateway&quot; {
  type    = bool
  default = true
}

variable &quot;single_nat_gateway&quot; {
  type    = bool
  default = false
}

variable &quot;enable_dns_hostnames&quot; {
  type    = bool
  default = true
}

variable &quot;cluster_name&quot; {
  type    = string
  default = &quot;eksFromGitLab&quot;
}

variable &quot;cluster_version&quot; {
  type    = string
  default = &quot;1.27&quot;
}

variable &quot;cluster_endpoint_public_access&quot; {
  type    = bool
  default = true
}

variable &quot;ami_type&quot; {
  type    = string
  default = &quot;AL2_x86_64&quot;
}

variable &quot;nodeGroup&quot; {
  type    = string
  default = &quot;ngFromGitLab&quot;
}

variable &quot;instanceTypes&quot; {
  type    = list(string)
  default = &#x5B;&quot;t3.small&quot;]
}

variable &quot;minSize&quot; {
  type    = number
  default = 1
}

variable &quot;maxSize&quot; {
  type    = number
  default = 3
}

variable &quot;desiredSize&quot; {
  type    = number
  default = 3
}


variable &quot;iamALBIngressPolicyFileName&quot; {
  type    = string
  default = &quot;alb_ingress_iam_policy.json&quot;
}
</pre>
<p>&#8230;and finally your .gitlab-ci.yml file.<br />
<strong>FILE: .gitlab-ci.yml. NOTE: </strong>Remove line 38 if you don&#8217;t use it, don&#8217;t keep it with a comment or you&#8217;ll get an error in the pipeline.</p>
<pre class="brush: bash; highlight: [38]; title: ; notranslate">
stages:          
  - validate
  - plan
  - apply
  - destroy

image:
  name: hashicorp/terraform:1.6
  entrypoint:
    - &quot;/usr/bin/env&quot;
    - &quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;

variables:
  TF_DIR: ${CI_PROJECT_DIR}
  TF_STATE_NAME: &quot;eks-tf-state&quot;
  TF_ADDRESS: &quot;https://gitlab.com/api/v4/projects/${CI_PROJECT_ID}/terraform/state/${TF_STATE_NAME}&quot;

default:
  id_tokens:
    GITLAB_OIDC_TOKEN:
      aud: https://gitlab.com

  before_script:
    - terraform --version
    - cd ${TF_DIR}
    - terraform init
      -reconfigure 
      -backend-config=&quot;address=${TF_ADDRESS}&quot; 
      -backend-config=&quot;lock_address=${TF_ADDRESS}/lock&quot; 
      -backend-config=&quot;unlock_address=${TF_ADDRESS}/lock&quot; 
      -backend-config=&quot;username=${TF_USERNAME}&quot; 
      -backend-config=&quot;password=${TF_PASSWORD}&quot; 
      -backend-config=&quot;lock_method=POST&quot; 
      -backend-config=&quot;unlock_method=DELETE&quot; 
      -backend-config=&quot;retry_wait_min=5&quot;
    - mkdir -p ~/.aws
    - echo &quot;${GITLAB_OIDC_TOKEN}&quot; &gt; /tmp/web_identity_token
    - # echo &quot;${GITLAB_OIDC_TOKEN}&quot; | base64 - If you want to see the token, reveal it this way and then use base64 --decode to get the real one
    - echo -e &quot;&#x5B;profile oidc]\nrole_arn=${ROLE_ARN}\nweb_identity_token_file=/tmp/web_identity_token&quot; &gt; ~/.aws/config

job-validate:
  stage: validate
  script:
    - terraform validate

job-plan:
  stage: plan
  script:
    - terraform plan -out &quot;tf_plan&quot;
  dependencies:
    - job-validate
  artifacts:
    paths:
      - tf_plan

job-apply:
  stage: apply
  script:
    - terraform apply &quot;tf_plan&quot;
  dependencies:
    - job-plan
  allow_failure: true

job-destroy:
  stage: destroy
  script:
    - terraform destroy  -auto-approve
  dependencies:
    #- job-plan
    #- job-apply
  when: manual
</pre>
<p>Push the code back to Gitlab (<em>git add, git commit, git push</em>) and once everything completes successfully, you&#8217;ll have a working EKS cluster after 10-15 mins.<br />
If you choose to manage the EKS from GitLab, you can skip to the next section. If you decide to manage the cluster outside GitLab and you already have another IAM user added to the role <strong>rolGitLab </strong>as described above, then do the following. Replace account # in line 1.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
aws eks update-kubeconfig --region us-east-2 --name eksFromGitLab --role-arn arn:aws:iam::123456789012:role/rolGitLab
aws eks list-clusters
eksctl get cluster --name=eksFromGitLab --region us-east-2
kubectl get svc
</pre>
<h1>Manage EKS from GitLab</h1>
<p>Generally, you would want this to be in another project. Our first project was to build the cluster, but our second project builds on top of that and manages the cluster. In addition, the second project also uses the CI/CD pipeline file <strong>.gitlab-ci.yml</strong> so in order to avoid a single complex project &#8211; just create a new project. I&#8217;ll name my new project &#8211; <strong>project1</strong>. Clone the project on your local machine using <em>git clone</em>.<br />
Create an empty config file for the agent on your local machine. It has to be under this folder structure.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p .gitlab/agents/eks-agent &amp;&amp; touch .gitlab/agents/eks-agent/config.yaml
</pre>
<p>In my case the name of the agent is <strong>eks-agent</strong>. Use whatever name you want but follow DNS naming <a href="https://docs.gitlab.com/ee/user/clusters/agent/install/index.html" rel="noopener" target="_blank">convention</a>.<br />
Push the local changes to GitLab.<br />
On the left side in GitLab, go to <strong>Operate | Kubernetes clusters</strong> and then click <strong>Connect a cluster</strong> from the upper right corner.<br />
Select the agent from the drop down and click <strong>Register</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P169-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-1024x304.png" alt="" width="1024" height="304" class="aligncenter size-large wp-image-9767" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-1024x304.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-300x89.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-768x228.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-1170x347.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02-585x174.png 585w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-02.png 1522w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
You&#8217;ll get an access token and a bunch of helm commands to install the agent on the cluster. Copy and paste and run those commands.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add gitlab https://charts.gitlab.io
helm repo update
helm upgrade --install eks-agent gitlab/gitlab-agent \
    --namespace gitlab-agent-eks-agent \
    --create-namespace \
    --set image.tag=v16.9.0-rc2 \
    --set config.token=glagent-2r_uW63e1w_mkNzR_keU2hmaNhhcKauSpujg8OdLQG74wrF73g \
    --set config.kasAddress=wss://kas.gitlab.com
</pre>
<p>Refresh the GitLab screen after the helm chart has been installed and you&#8217;ll see your cluster connected.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/11/P169-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1024x174.png" alt="" width="1024" height="174" class="aligncenter size-large wp-image-9769" srcset="https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1024x174.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-300x51.png 300w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-768x131.png 768w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1536x261.png 1536w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-2048x348.png 2048w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1920x326.png 1920w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-1170x199.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/11/P169-03-585x99.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Now you need to <a href="https://docs.gitlab.com/ee/user/clusters/agent/ci_cd_workflow.html#authorize-the-agent" rel="noopener" target="_blank">authorize</a> the agent to access our project.<br />
In the blank <strong>config.yaml</strong> file on the local machine under <em>.gitlab/agents/eks-agent/</em> directory, add these lines. Change the path the project in line 3 (look at the URL, that&#8217;s your project name after gitlab.com) and change the manifest file if you want in line 6. You can also change the namespace in line 5 as well.</p>
<pre class="brush: yaml; highlight: [3,6]; title: ; notranslate">
gitops:
  manifest_projects:
  - id: andreevkliment/project1
    default_namespace: default
    paths:
      - glob: &#039;manifest.yaml&#039;
</pre>
<p>Then create the manifest file under the root of the project. Save it as <em>manifest.yaml</em> or however you referenced it in line 6 in the previous file.</p>
<pre class="brush: yaml; title: ; notranslate">
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
</pre>
<p>If you push this code back to GitLab, the agent will execute it and you&#8217;ll (with <em>kubect get pods</em>) see the nginx pods running on your cluster.<br />
Another way of managing the EKS cluster is using <strong>.gitlab-ci.yml</strong> file.<br />
Create <strong>.gitlab-ci.yml</strong> with these lines. Replace your username, project and the agent name in line 7.</p>
<pre class="brush: yaml; highlight: [7]; title: ; notranslate">
deploy:
  image:
    name: bitnami/kubectl:latest
    entrypoint: &#x5B;&#039;&#039;]
  script:
    - kubectl config get-contexts
    - kubectl config use-context andreevkliment/project1:eks-agent
    - kubectl get pods
</pre>
<p>If you push this back to GitLab, the pipeline will start and you&#8217;ll see the pods from the cluster in the logs. This example doesn&#8217;t execute <strong>manifest.yaml</strong>. You can have both ways of interacting with the cluster or you can use them separately.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/11/aws-install-eks-cluster-and-ingress-controller-using-terraform-and-gitlab-using-oidc-and-web-identity/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<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 loading="lazy" 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 loading="lazy" 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 loading="lazy" 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>Kubernetes: Monitoring Node.js application with Prometheus and Grafana, Helm charts in AWS EKS</title>
		<link>https://blog.andreev.it/2023/01/kubernetes-monitoring-node-js-application-with-prometheus-and-grafana-helm-charts/</link>
					<comments>https://blog.andreev.it/2023/01/kubernetes-monitoring-node-js-application-with-prometheus-and-grafana-helm-charts/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Tue, 24 Jan 2023 16:01:05 +0000</pubDate>
				<category><![CDATA[Containers]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[AWS]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[helm]]></category>
		<category><![CDATA[nodejs]]></category>
		<category><![CDATA[prom-client]]></category>
		<category><![CDATA[prometheus]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9312</guid>

					<description><![CDATA[Recently, we were migrating some Node.js applications to a different AWS account and we&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>Recently, we were migrating some Node.js applications to a different AWS account and we had a need to monitor the application with Prometheus and Grafana. In this post, I&#8217;ll explain how to install both Prometheus and Grafana using Helm charts, then we&#8217;ll create a simple Express.js web app, create a Helm chart for it and deploy it on an EKS cluster in AWS. I am using an EKS cluster, but you can use any Kubernetes cluster that you can manage it with kubectl. You might have to make some changes though in how you access the Load Balancer for Grafana.<br />
<strong>Requirements</strong>:</p>
<ul>
&#8211; eksctl<br />
&#8211; kubectl<br />
&#8211; helm</ul>
<h1>Create EKS cluster</h1>
<p>This is optional, if you already have a running Kubernetes cluster, you can skip this step.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksTest \
--region us-east-2 \
--instance-types t3.medium \
--nodes 2 \
--managed \
--version 1.22
</pre>
<p>This command will create a 2 node cluster, with t3.medium instances in Ohio region using Amazon Linux 2 image. It will take about 15 minutes.<br />
Verify that everything looks OK.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes
NAME                                           STATUS   ROLES    AGE     VERSION
ip-192-168-10-4.us-east-2.compute.internal     Ready    &lt;none&gt;   8m54s   v1.22.15-eks-fb459a0
ip-192-168-74-160.us-east-2.compute.internal   Ready    &lt;none&gt;   8m51s   v1.22.15-eks-fb459a0
</pre>
<h1>Install Prometheus and Grafana</h1>
<p>Add the Helm repos for Prometheus, Grafana and update any changes.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
</pre>
<p>We&#8217;ll create a separate namespace for Prometheus and install the chart there.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace prometheus
helm install prometheus prometheus-community/prometheus \
    --namespace prometheus \
    --set alertmanager.persistentVolume.storageClass=&quot;gp2&quot; \
    --set server.persistentVolume.storageClass=&quot;gp2&quot;
</pre>
<p>Create this file. It tells Grafana what to use as a source (url). It&#8217;s the Prometheus endpoint service that we just installed.</p>
<pre class="brush: plain; title: ; notranslate">
cat &lt;&lt; EOF &gt; grafana.yaml
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server.prometheus.svc.cluster.local
      access: proxy
      isDefault: true
EOF
</pre>
<p>Now, let&#8217;s create a namespace for Grafana and then install Grafana in that namespace. Change the admin password. In my case it&#8217;s Password1$.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace grafana
helm install grafana grafana/grafana \
    --namespace grafana \
    --set persistence.storageClassName=&quot;gp2&quot; \
    --set persistence.enabled=true \
    --set adminPassword=&#039;Password1$&#039; \
    --values grafana.yaml \
    --set service.type=LoadBalancer
</pre>
<p>You&#8217;ll get something like this on the screen when the install ends.</p>
<pre class="brush: bash; title: ; notranslate">
export SERVICE_IP=$(kubectl get svc --namespace grafana grafana -o jsonpath=&#039;{.status.loadBalancer.ingress&#x5B;0].ip}&#039;)
</pre>
<p>That&#8217;s your load balancer IP so you can access Grafana. If you use EKS, your IP is different.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get services -n grafana
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP                                                               PORT(S)        AGE
grafana   LoadBalancer   10.100.20.53   a074996f790b74793a74bec584c04460-2034633908.us-east-2.elb.amazonaws.com   80:31916/TCP   3m21s
</pre>
<p>The IP is the gibberish URL that ends with amazonaws.com. If you go to that URL, you can log as <strong>admin </strong>and your password.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-1024x929.png" alt="" width="1024" height="929" class="aligncenter size-large wp-image-9316" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-1024x929.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-300x272.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-768x696.png 768w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01-585x531.png 585w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-01.png 1149w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Go to <strong>Dashboards</strong>, click on the <strong>New </strong>button on the right and click <strong>Import</strong>.<br />
Add the following dashboard IDs (one by one then click <strong>Load</strong>) and choose Prometheus as a source. The IDs are 3119 (Kubernetes cluster), 6417 (pods) and 11159 (Node.js). For the first dashboard, you&#8217;ll have some metrics. The Node.js dashboard is empty and our goal is to get the metrics from the application.</p>
<h1>Node.js application</h1>
<p>Create an empty folder and add this file.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir mynodeapp
cd mynodeapp
cat &lt;&lt; EOF &gt; package.json
{
  &quot;name&quot;: &quot;mynodeapp&quot;,
  &quot;version&quot;: &quot;1.0.0&quot;,
  &quot;description&quot;: &quot;Node.js on Docker&quot;,
  &quot;author&quot;: &quot;Kliment Andreev &lt;kliment@andreev.it&gt;&quot;,
  &quot;main&quot;: &quot;server.js&quot;,
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;node server.js&quot;
  },
  &quot;dependencies&quot;: {
    &quot;express&quot;: &quot;^4.16.1&quot;,
    &quot;prom-client&quot;: &quot;^14.1.1&quot;
  }
}
EOF
</pre>
<p>I use Node v18.12. Install the dependencies.</p>
<pre class="brush: bash; title: ; notranslate">
npm install
</pre>
<p>This is the simple web application. It listens on port 3000.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; &quot;EOF&quot; &gt; server.js
&#039;use strict&#039;
const express = require(&#039;express&#039;)
const prom = require(&#039;prom-client&#039;)

const collectDefaultMetrics = prom.collectDefaultMetrics();

const app = express()
const port = process.env.PORT || 3000

app.get(&#039;/&#039;, (req, res, next) =&gt; {
  res.send(&#039;Hello World!&#039;);
});

app.get(&#039;/metrics&#039;, async (req, res) =&gt; {
  try {
	  res.set(&#039;Content-Type&#039;, prom.register.contentType);
	  res.end(await prom.register.metrics());
  } 
  catch (ex) {
	  res.status(500).end(ex);
  }
});

const server = app.listen(port, () =&gt; {
  console.log(`mynodeapp listening on port ${port}!`)
})
EOF
</pre>
<p>The app is a super simple web app that prints Hello World! The important part is the <strong>/metrics</strong> router. That&#8217;s how you get the metrics from the app. When we created the package.json file, we specify the <strong>prom-client</strong> as dependency. That&#8217;s the part that collects the metrics. Go to this <a href="https://github.com/siimon/prom-client" rel="noopener" target="_blank">link </a>for more info.<br />
Start the app with <strong>node server.js</strong>.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
node server.js
mynodeapp listening on port 3000!
</pre>
<p>Go to the localhost or the server IP where this app is running and you&#8217;ll see <em>Hello World!</em> printed out.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-02.png" alt="" width="634" height="177" class="aligncenter size-full wp-image-9321" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-02.png 634w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-02-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-02-585x163.png 585w" sizes="(max-width: 634px) 100vw, 634px" /></a><br />
And if you go to the /metrics URL, you&#8217;ll see this text. That&#8217;s what Prometheus expects.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-03.png" alt="" width="874" height="325" class="aligncenter size-full wp-image-9322" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-03.png 874w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-03-300x112.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-03-768x286.png 768w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-03-585x218.png 585w" sizes="(max-width: 874px) 100vw, 874px" /></a><br />
Now that we know the app is working, let&#8217;s create a Docker image.</p>
<h1>Docker image and container</h1>
<p>Create this Dockerfile.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; Dockerfile
FROM node:18-alpine

# Create app directory
WORKDIR /app

# Install app dependencies
# A wildcard is used to ensure both package.json AND package-lock.json are copied
# where available (npm@5+)
COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm install --only=production

# Bundle app source
COPY server.js /app

EXPOSE 3000
CMD &#x5B; &quot;npm&quot;, &quot;start&quot;, &quot;server.js&quot; ]
EOF
</pre>
<p>Create a Docker image based on the Dockerfile above. Replace the username with your Docker Hub username.</p>
<pre class="brush: bash; title: ; notranslate">
docker build -t &lt;username&gt;/mynodeapp:latest .
</pre>
<p>Run the container from the image.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
docker run -p 3000:3000 &lt;username&gt;/mynodeapp:latest

&gt; mynodeapp@1.0.0 start
&gt; node server.js server.js

mynodeapp listening on port 3000!
</pre>
<p>Use the same test as before by going to the localhost or IP URL on port 3000.<br />
If everything is OK, publish the image on Dockerhub.</p>
<pre class="brush: bash; title: ; notranslate">
docker login
docker push &lt;username&gt;/mynodeapp
</pre>
<h1>Helm chart</h1>
<p>Now that we have the Docker image, we&#8217;ll create a Helm chart so we can deploy the web app to the Kubernetes cluster.<br />
Create the necessary structure.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
helm create mynodeapp
Creating mynodeapp
</pre>
<p>Go to the sub-directory <strong>mynodeapp </strong>and edit the <strong>values.yaml</strong> file first. Look for the image key first and replace it so it looks like this. Make sure you replace the <strong><username></strong> with your Docker Hub username.</p>
<pre class="brush: bash; title: ; notranslate">
image:
  repository: &lt;username&gt;/mynodeapp
  pullPolicy: IfNotPresent
  # Overrides the image tag whose default is the chart appVersion.
  tag: &quot;latest&quot;
</pre>
<p>Then look for <strong>podAnnotations: {}</strong> line and replace it so it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
podAnnotations:
  prometheus.io/scrape: &quot;true&quot;
  prometheus.io/path: &quot;/metrics&quot;
  prometheus.io/port: &quot;3000&quot;
</pre>
<p>Look for the <strong>service </strong>parameter and change it so it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
service:
  type: LoadBalancer
  port: 80
  targetPort: 3000
  name: mynodeapp-service
</pre>
<p>Look for the resources key and uncomment the defaults.</p>
<pre class="brush: bash; title: ; notranslate">
resources: {}
  # We usually recommend not to specify default resources and to leave this as a conscious
  # choice for the user. This also increases chances charts run on environments with little
  # resources, such as Minikube. If you do want to specify resources, uncomment the following
  # lines, adjust them as necessary, and remove the curly braces after &#039;resources:&#039;.
limits:
  cpu: 100m
  memory: 128Mi
requests:
  cpu: 100m
  memory: 128Mi
</pre>
<p>Edit the <strong>templates/deployment.yaml</strong> file and find these lines.</p>
<pre class="brush: bash; title: ; notranslate">
ports:
  - name: http
    containerPort: {{ .Values.service.port }}
</pre>
<p>Replace the containerPort to look like this.</p>
<pre class="brush: bash; title: ; notranslate">
containerPort: 3000
</pre>
<p>Go back to the root of the <strong>mynodeapp </strong>folder and check the chart.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
helm install mynodeapp --generate-name
NAME: mynodeapp-1674575241
LAST DEPLOYED: Tue Jan 24 10:47:22 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch the status of by running &#039;kubectl get --namespace default svc -w mynodeapp-1674575241&#039;
  export SERVICE_IP=$(kubectl get svc --namespace default mynodeapp-1674575241 --template &quot;{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}&quot;)
  echo http://$SERVICE_IP:80
</pre>
<p>Execute the <strong>export SERVICE_IP</strong> command above and echo the IP. That&#8217;s your URL. If you go to that URL you&#8217;ll see the <em>Hello World!</em> greetings.<br />
What we care is the pod. Get the pod running. It&#8217;s in the default namespace.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods
NAME                                    READY   STATUS    RESTARTS   AGE
mynodeapp-1674575241-5ff9d7469d-pddcz   1/1     Running   0          3m
</pre>
<p>Get the annotations. Replace with your pod name.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pod mynodeapp-1674575241-5ff9d7469d-pddcz -o jsonpath=&#039;{.metadata.annotations}&#039;
{&quot;kubernetes.io/psp&quot;:&quot;eks.privileged&quot;,&quot;prometheus.io/path&quot;:&quot;/metrics&quot;,&quot;prometheus.io/port&quot;:&quot;3000&quot;,&quot;prometheus.io/scrape&quot;:&quot;true&quot;}
</pre>
<p>You can see the prometheus annotations.<br />
And if you go to Grafana and open up the NodeJS dash you&#8217;ll see the metrics.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2023/01/P161-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-1024x740.png" alt="" width="1024" height="740" class="aligncenter size-large wp-image-9327" srcset="https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-1024x740.png 1024w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-300x217.png 300w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-768x555.png 768w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-1170x845.png 1170w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04-585x423.png 585w, https://blog.andreev.it/wp-content/uploads/2023/01/P161-04.png 1251w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2023/01/kubernetes-monitoring-node-js-application-with-prometheus-and-grafana-helm-charts/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>AWS: EKS monitoring and logging</title>
		<link>https://blog.andreev.it/2022/10/aws-eks-monitoring-and-logging/</link>
					<comments>https://blog.andreev.it/2022/10/aws-eks-monitoring-and-logging/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Mon, 03 Oct 2022 23:09:44 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[DevOps]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[CloudWatch]]></category>
		<category><![CDATA[EKS]]></category>
		<category><![CDATA[fluentbit]]></category>
		<category><![CDATA[grafana]]></category>
		<category><![CDATA[logging]]></category>
		<category><![CDATA[monitoring]]></category>
		<category><![CDATA[prometheus]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=9214</guid>

					<description><![CDATA[In this post I&#8217;ll explain several things related to EKS monitoring and logging. &#8211;&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain several things related to EKS monitoring and logging. </p>
<ol>
&#8211; How to create an EKS cluster<br />
&#8211; Enable the EKS control plane logs and send them to CloudWatch<br />
&#8211; Send these logs from CloudWatch to OpenSearch cluster<br />
&#8211; Install Container Insights and FluentBit and send logs to CloudWatch<br />
&#8211; Install Prometheus and Grafana to monitor and visualize EKS cluster metrics<br />
&#8211; WordPress and CloudWatch log group
</ol>
<p>In order to do that, we&#8217;ll need the following CLI tools. </p>
<ol>
&#8211; eksctl<br />
&#8211; kubectl<br />
&#8211; helm<br />
&#8211; aws cli</ol>
<p>You can easily find how to install and configure these tools for various OSes. </p>
<h1>Create the EKS cluster</h1>
<p>I&#8217;ll create a managed cluster called <strong>eksWordPress </strong>in <strong>us-east-2</strong> region with <strong>two </strong><strong>t3.medium</strong> nodes. </p>
<pre class="brush: bash; title: ; notranslate">
eksctl create cluster --name eksWordPress --region us-east-2 --instance-types t3.medium --nodes 2 --managed --version 1.22
</pre>
<p>If you get an error that the last supported version is 1.21, update eksctl tool. The cluster creation took about 20 mins for me. </p>
<h1>Send EKS logs to CloudWatch</h1>
<p>Once the cluster was created and up and running, enabling the EKS control plane logs is easy. There are 5 log types (API server, Audit, Authenticator, Controller Manager and Scheduler logs). The logged info is a lot, so it&#8217;s up to you what you want to log. There is no way to choose what to log, e.g. info, warning, errors. Everything is logged. To enable logging from the console, go to the EKS cluster, select it, click the <strong>Logging</strong> tab and then click on the <strong>Manage logging</strong> button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1024x278.png" alt="" width="1024" height="278" class="aligncenter size-large wp-image-9230" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1024x278.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-300x81.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-768x208.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1536x416.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-2048x555.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1920x521.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-1170x317.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-01-585x159.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
If you want to do it from a CLI, type this command and specify what types of logs you want sent to CloudWatch. Specify the region and the cluster name.</p>
<pre class="brush: bash; title: ; notranslate">
aws eks update-cluster-config \
    --region &lt;REGION&gt; \
    --name &lt;CLUSTER_NAME&gt; \
    --logging &#039;{&quot;clusterLogging&quot;:&#x5B;{&quot;types&quot;:&#x5B;&quot;api&quot;,&quot;audit&quot;,&quot;authenticator&quot;,&quot;controllerManager&quot;,&quot;scheduler&quot;],&quot;enabled&quot;:true}]}&#039;
</pre>
<p>In addition, because we provisioned the cluster with the <strong>eksctl</strong> tool, we can also enable the logs using <strong>eksctl</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl utils update-cluster-logging --enable-types all --cluster &lt;CLUSTER_NAME&gt; --approve
</pre>
<p>Or for certain types, use&#8230; </p>
<pre class="brush: bash; title: ; notranslate">
eksctl utils update-cluster-logging --enable-types &lt;LOG_TYPE&gt; --cluster &lt;CLUSTER_NAME&gt; --approve
</pre>
<p>You can use the same types as in the AWS CLI command above (api, audit, scheduler&#8230;).<br />
If you go to CloudWatch and then the <strong>Log groups</strong>, you&#8217;ll see the log group with the name of the EKS cluster (<em>/aws/eks/eksWordPress/cluster</em>).<br />
Make sure you change the retention from <strong>Never</strong> to some value. You probably don&#8217;t want to keep these logs indefinitely. If you look at the logs streams, you&#8217;ll see that there is a lot of info there. Most of the stuff is useless. </p>
<h1>Send the logs to OpenSearch cluster</h1>
<p>Let&#8217;s create a public OpenSearch cluster with anonymous access that only you can access. Replace the IP at the end of the statement with your IP address. It takes less than 10 mins for the OpenSearch cluster to be provisioned. </p>
<pre class="brush: bash; title: ; notranslate">
aws opensearch create-domain --domain-name oswordpress --engine-version OpenSearch_1.3 \
    --auto-tune-options DesiredState=&quot;ENABLED&quot; --cluster-config InstanceType=t3.small.search,InstanceCount=2 \
    --ebs-options EBSEnabled=true,VolumeType=gp3,VolumeSize=10,Iops=3000 \
    --access-policies &#039;{&quot;Version&quot;: &quot;2012-10-17&quot;, &quot;Statement&quot;: &#x5B;{&quot;Action&quot;: &quot;es:*&quot;, &quot;Principal&quot;:&quot;*&quot;,&quot;Effect&quot;: &quot;Allow&quot;, &quot;Condition&quot;: {&quot;IpAddress&quot;:{&quot;aws:SourceIp&quot;:&#x5B;&quot;2.18.2.19/32&quot;]}}}]}&#039;
</pre>
<p>Once the cluster is provisioned you can get the public URL with:</p>
<pre class="brush: bash; title: ; notranslate">
echo `aws es describe-elasticsearch-domain --domain-name oswordpress --output text --query &quot;DomainStatus.Endpoint&quot;`\\_dashboards
</pre>
<p>You can also get the dashboard URL from the cluster settings as well.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1024x269.png" alt="" width="1024" height="269" class="aligncenter size-large wp-image-9240" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1024x269.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-300x79.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-768x201.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1536x403.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-2048x537.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1920x504.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-1170x307.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-02-585x153.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Create the following IAM policy. Save the policy below as <strong>policy-file.json</strong> but change the <strong>account_no</strong> in line 9 to match your AWS account.</p>
<pre class="brush: xml; highlight: [9]; title: ; notranslate">
{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: &#x5B;
        {
            &quot;Action&quot;: &#x5B;
                &quot;es:*&quot;
            ],
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Resource&quot;: &quot;arn:aws:es:us-east-2:&lt;account_no&gt;:domain/oswordpress/*&quot;
        }
    ]
}
</pre>
<p>And then create the IAM policy.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-policy --policy-name polOpenSearch --policy-document file://policy-file.json
</pre>
<p>Create the role. Save the policy below as <strong>policy-trust.json</strong>.</p>
<pre class="brush: xml; title: ; notranslate">
{
  &quot;Version&quot;: &quot;2012-10-17&quot;,
  &quot;Statement&quot;: &#x5B;
    {
      &quot;Effect&quot;: &quot;Allow&quot;,
      &quot;Principal&quot;: {
        &quot;Service&quot;: &quot;lambda.amazonaws.com&quot;
      },
      &quot;Action&quot;: &quot;sts:AssumeRole&quot;
    }
  ]
}
</pre>
<p>And then create the role.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam create-role --role-name rolOpenSearch --assume-role-policy file://policy-trust.json
</pre>
<p>Finally, attach the policy to the role.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam put-role-policy --role-name rolOpenSearch --policy-name polOpenSearch --policy-document file://policy-file.json 
</pre>
<p>Go to <strong>CloudWatch</strong>, select the <strong>Log group</strong>, from the <strong>Actions</strong> button select <strong>Subscription filters</strong> and then <strong>Create Amazon OpenSearch service subscription filter</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1024x355.png" alt="" width="1024" height="355" class="aligncenter size-large wp-image-9242" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1024x355.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-300x104.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-768x266.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1536x533.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-2048x711.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1920x666.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-1170x406.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-03-585x203.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Choose the OpenSearch cluster that we created, the log format is <strong>JSON</strong>, subscription filter pattern is <strong>&#8221; &#8220;</strong> for all events and type <strong>all</strong> subscription filter name or whatever you want to name this pattern. Click <strong>Start streaming</strong> button.<br />
Now, go to OpenSearch dashboard and from the hamburger menu in the upper left corner, click on <strong>Visualize</strong>. In the middle of the screen, you&#8217;ll be prompted to create an index pattern. Type <strong>cwl*</strong> and click <strong>Next step</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1024x438.png" alt="" width="1024" height="438" class="aligncenter size-large wp-image-9249" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1024x438.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-300x128.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-768x329.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1536x657.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-2048x877.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1920x822.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-1170x501.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-04-585x250.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Select <strong>@timestamp</strong> from the drop down menu and click on <strong>Create index pattern</strong>. You&#8217;ll see the fields and the index.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1024x659.png" alt="" width="1024" height="659" class="aligncenter size-large wp-image-9251" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1024x659.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-300x193.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-768x494.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1536x989.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-2048x1319.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1920x1236.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-1170x753.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-05-585x377.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
Click on the <strong>Discover</strong> from the hamburger menu and you&#8217;ll see your data there. You can use queries to search your data, but that&#8217;s out of the scope of this post.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/09/P160-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1024x400.png" alt="" width="1024" height="400" class="aligncenter size-large wp-image-9252" srcset="https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1024x400.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-300x117.png 300w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-768x300.png 768w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1536x600.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-2048x800.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1920x750.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-1170x457.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/09/P160-06-585x228.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Container Insights and FluentBit</h1>
<p>Container Insights is a CloudWatch agent that we&#8217;ll install on the EKS cluster. It will collect all kinds of metrics and then FluentBit as a log forwarder will ship those logs to CloudWatch. See this link for more <a href="https://aws.amazon.com/blogs/containers/fluent-bit-integration-in-cloudwatch-container-insights-for-eks/" rel="noopener" target="_blank">info</a>.<br />
Let&#8217;s do the prerequisites work first.<br />
Get the nodes. Type the first line only, the rest is my output.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get nodes                                                                                                                                                               
NAME                                           STATUS   ROLES    AGE     VERSION
ip-192-168-56-250.us-east-2.compute.internal   Ready    &lt;none&gt;   3h39m   v1.22.12-eks-ba74326
ip-192-168-66-251.us-east-2.compute.internal   Ready    &lt;none&gt;   3h39m   v1.22.12-eks-ba74326
</pre>
<p>Get the instance ID from any of the instances, it doesn&#8217;t matter. Replace the name accordingly after <strong>Values=</strong>.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
aws ec2 describe-instances --filters &#039;Name=private-dns-name,Values=ip-192-168-56-250.us-east-2.compute.internal&#039; \
    --output text --query &#039;Reservations&#x5B;*].Instances&#x5B;*].InstanceId&#039;
i-05adaa0a823a8549a
</pre>
<p>Once you have the instance ID of any node, get the Arn of the IAM role that&#8217;s the attached to that node.</p>
<pre class="brush: bash; highlight: [1,5]; title: ; notranslate">
aws ec2 describe-instances --region us-east-2 --instance-ids i-05adaa0a823a8549a --query &#039;Reservations&#x5B;*].Instances&#x5B;*].IamInstanceProfile.Id&#039;
&#x5B;
    &#x5B;
        &quot;AIPAXFRN6SYD75I5N4BUN&quot;
    ]
]
</pre>
<p>Get the role name. Replace the ID (<strong>AIPAX</strong>&#8230;) with your value above.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
aws iam list-instance-profiles --query &#039;InstanceProfiles&#x5B;?InstanceProfileId==`AIPAXFRN6SYD75I5N4BUN`].Roles&#x5B;*].RoleName&#039; 
&#x5B;
    &#x5B;
        &quot;eksctl-eksWordPress-nodegroup-ng-NodeInstanceRole-LWJZESQ468Y2&quot;
    ]
]
</pre>
<p>We want to add a policy that allows nodes to write to CloudWatch logs group. Replace the <strong>&#8211;role-name </strong>with the value that you got above.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --role-name eksctl-eksWordPress-nodegroup-ng-NodeInstanceRole-LWJZESQ468Y2
</pre>
<pre class="brush: bash; title: ; notranslate">
oidc_id=$(aws eks describe-cluster --name eksWordPress --query &quot;cluster.identity.oidc.issuer&quot; --output text | cut -d &#039;/&#039; -f 5)
</pre>
<p>Type this line and see if there is any output. If no output, execute the command after. If yes-output, then do nothing.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam list-open-id-connect-providers | grep $oidc_id
</pre>
<p>If no output, type this.</p>
<pre class="brush: bash; title: ; notranslate">
eksctl utils associate-iam-oidc-provider --cluster eksWordPress --approve
</pre>
<p>Then install CloudWatch Container Insights and FluentBit. Change the <strong>CLUSTER_NAME</strong> and <strong>REGION</strong> in lines 1 and 2.</p>
<pre class="brush: bash; highlight: [1,2]; title: ; notranslate">
ClusterName=&lt;CLUSTER_NAME&gt;
RegionName=&lt;REGION&gt;
FluentBitHttpPort=&#039;2020&#039;
FluentBitReadFromHead=&#039;Off&#039;
&#x5B;&#x5B; ${FluentBitReadFromHead} = &#039;On&#039; ]] &amp;&amp; FluentBitReadFromTail=&#039;Off&#039;|| FluentBitReadFromTail=&#039;On&#039;
&#x5B;&#x5B; -z ${FluentBitHttpPort} ]] &amp;&amp; FluentBitHttpServer=&#039;Off&#039; || FluentBitHttpServer=&#039;On&#039;
curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed &#039;s/{{cluster_name}}/&#039;${ClusterName}&#039;/;s/{{region_name}}/&#039;${RegionName}&#039;/;s/{{http_server_toggle}}/&quot;&#039;${FluentBitHttpServer}&#039;&quot;/;s/{{http_server_port}}/&quot;&#039;${FluentBitHttpPort}&#039;&quot;/;s/{{read_from_head}}/&quot;&#039;${FluentBitReadFromHead}&#039;&quot;/;s/{{read_from_tail}}/&quot;&#039;${FluentBitReadFromTail}&#039;&quot;/&#039; | kubectl apply -f - 
</pre>
<p>Download a config map and edit it the file called <em>cwagent-configmap.yaml</em>.</p>
<pre class="brush: bash; title: ; notranslate">
curl -O https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/cwagent/cwagent-configmap.yaml
</pre>
<p>In line 11, change the variable so it points to your cluster. In my case it looks like this.</p>
<pre class="brush: bash; title: ; notranslate">
&quot;cluster_name&quot;: &quot;{{eksWordPress}}&quot;,
</pre>
<p>Save the changes and apply the config.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f cwagent-configmap.yaml
</pre>
<p>Then deploy it as a DaemonSet.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl apply -f https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/cwagent/cwagent-daemonset.yaml
</pre>
<p>Verify that it&#8217;s running. Type the first line only. The rest is my output.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods -n amazon-cloudwatch
NAME                     READY   STATUS    RESTARTS   AGE
cloudwatch-agent-dfkzv   1/1     Running   0          18m
cloudwatch-agent-nfnf7   1/1     Running   0          18m
fluent-bit-8vd2n         1/1     Running   0          18m
fluent-bit-tvtpv         1/1     Running   0          18m
</pre>
<p>Check the logs.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl logs &lt;POD_NAME&gt;  -n amazon-cloudwatch
</pre>
<p>Or in my case&#8230;</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl logs cloudwatch-agent-dfkzv -n amazon-cloudwatch
&#x5B;2022/10/03 13:28:56] &#x5B; info] &#x5B;output:cloudwatch_logs:cloudwatch_logs.2] Created log stream ip-192-168-73-121.us-east-2.compute.internal.host.messages
&#x5B;2022/10/03 13:29:06] &#x5B; info] &#x5B;output:cloudwatch_logs:cloudwatch_logs.0] Creating log stream ip-192-168-73-121.us-east-2.compute.internal-application.var.log.containers.cloudwatch-agent-7fvxd_amazon-cloudwatch_cloudwatch-agent-5409bed9d4733a51602e4bc0cccde5e5580eb3e9282cd5cc4c1a4f2d2e28e8ea.log in log group /aws/containerinsights/eksWordPress/application
&#x5B;2022/10/03 13:29:06] &#x5B; info] &#x5B;output:cloudwatch_logs:cloudwatch_logs.0] Created log stream ip-192-168-73-121.us-east-2.compute.internal-application.var.log.containers.cloudwatch-agent-7fvxd_amazon-cloudwatch_cloudwatch-agent-5409bed9d4733a51602e4bc0cccde5e5580eb3e9282cd5cc4c1a4f2d2e28e8ea.log
</pre>
<p>If you go to CloudWatch now you&#8217;ll see 4 new log groups.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/10/P160-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1024x287.png" alt="" width="1024" height="287" class="aligncenter size-large wp-image-9277" srcset="https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1024x287.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-300x84.png 300w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-768x215.png 768w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1536x430.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-2048x573.png 2048w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1920x537.png 1920w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-1170x327.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-07-585x164.png 585w" sizes="(max-width: 1024px) 100vw, 1024px" /></a></p>
<h1>Prometheus and Grafana</h1>
<p>The Kubernetes API can also be monitored using Prometheus. We&#8217;ll install it using helm.<br />
Create a namespace first.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace prometheus
</pre>
<p>Add the Prometheus repo. </p>
<pre class="brush: bash; title: ; notranslate">
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
</pre>
<p>Deploy Prometheus.</p>
<pre class="brush: bash; title: ; notranslate">
helm upgrade -i prometheus prometheus-community/prometheus --namespace prometheus --set alertmanager.persistentVolume.storageClass=&quot;gp2&quot;,server.persistentVolume.storageClass=&quot;gp2&quot;
</pre>
<p>Check if everything is OK.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
kubectl get pods -n prometheus
NAME                                            READY   STATUS    RESTARTS   AGE
prometheus-alertmanager-5c57cc6945-v9lcb        2/2     Running   0          4m55s
prometheus-kube-state-metrics-77ddf69b4-jsgrp   1/1     Running   0          4m55s
prometheus-node-exporter-68dk9                  1/1     Running   0          4m55s
prometheus-node-exporter-m98xk                  1/1     Running   0          4m55s
prometheus-pushgateway-ff89cc976-4sfhl          1/1     Running   0          4m55s
prometheus-server-6c99667b9b-mpw97              2/2     Running   0          4m55s
</pre>
<p>Type this command and open up a browser and go to localhost:9090.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl --namespace=prometheus port-forward deploy/prometheus-server 9090
</pre>
<p>You can CTRL-C out of the command prompt once you verify it&#8217;s OK.<br />
Let&#8217;s add the Grafana repo first.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add grafana https://grafana.github.io/helm-charts
</pre>
<p>Copy, paste and save this as <em>grafana.yaml</em>.</p>
<pre class="brush: bash; title: ; notranslate">
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server.prometheus.svc.cluster.local
      access: proxy
      isDefault: true
</pre>
<p>Create a namespace for grafana.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace grafana
</pre>
<p>Deploy using helm. Look at the <strong>adminPassword</strong> parameter. Change it to something else. In my case it&#8217;s <em>admin123!</em>.</p>
<pre class="brush: bash; title: ; notranslate">
helm install grafana grafana/grafana --namespace grafana --set persistence.storageClassName=&quot;gp2&quot; --set persistence.enabled=true --set adminPassword=&#039;admin123!&#039; --values grafana.yaml --set service.type=LoadBalancer
</pre>
<p>Check if everything is OK.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get all -n grafana
</pre>
<p>Get the URL of the classic LB that was just created.</p>
<pre class="brush: bash; title: ; notranslate">
export ELB=$(kubectl get svc -n grafana grafana -o jsonpath=&#039;{.status.loadBalancer.ingress&#x5B;0].hostname}&#039;)
echo &quot;http://$ELB&quot;
</pre>
<p>Go to that URL and you&#8217;ll see the Grafana URL.<br />
You should see the Grafana landing page. Log as admin and the password you specified when pods were deployed.<br />
In Grafana create a new dashboard and <strong>Import</strong> the dashboard with ID <strong>3119</strong>. Choose <strong>Prometheus</strong> as source.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/10/P160-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1024x822.png" alt="" width="1024" height="822" class="aligncenter size-large wp-image-9279" srcset="https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1024x822.png 1024w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-300x241.png 300w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-768x616.png 768w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1536x1232.png 1536w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-1170x939.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08-585x469.png 585w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-08.png 1892w" sizes="(max-width: 1024px) 100vw, 1024px" /></a><br />
For pods monitoring use the same method but this time specify <strong>6417</strong> as a dashboard ID. This is how it looks like under my account.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2022/10/P160-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-976x1024.png" alt="" width="976" height="1024" class="aligncenter size-large wp-image-9281" srcset="https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-976x1024.png 976w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-286x300.png 286w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-768x806.png 768w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-1464x1536.png 1464w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-1170x1227.png 1170w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09-585x614.png 585w, https://blog.andreev.it/wp-content/uploads/2022/10/P160-09.png 1468w" sizes="(max-width: 976px) 100vw, 976px" /></a></p>
<h1>WordPress</h1>
<p>Let&#8217;s deploy WordPress.</p>
<pre class="brush: bash; title: ; notranslate">
kubectl create namespace wordpress
</pre>
<p>Add the Bitnami helm chart.</p>
<pre class="brush: bash; title: ; notranslate">
helm repo add bitnami https://charts.bitnami.com/bitnami
</pre>
<p>Deploy WordPress in its own namespace</p>
<pre class="brush: plain; title: ; notranslate">
helm -n wordpress install understood-zebu bitnami/wordpress
</pre>
<p>Wait for 3-4 mins and do this command. This is your ELB, get the URL, something like *zdasdfa*.elb.amazonaws.com</p>
<pre class="brush: bash; title: ; notranslate">
kubectl get svc --namespace wordpress -w understood-zebu-wordpress
</pre>
<p>The username is <strong>user</strong> and get the password with:</p>
<pre class="brush: bash; title: ; notranslate">
echo Password: $(kubectl get secret --namespace wordpress understood-zebu-wordpress -o jsonpath=&quot;{.data.wordpress-password}&quot; | base64 -d)
</pre>
<p>If you go to the ELB URL, you&#8217;ll hit WordPress main page, if you want to login, add <strong>/wp-login.php</strong> as suffix to the above URL.<br />
Go to CloudWatch and check the <strong>/aws/containerinsights/eksWordPress/application</strong> log group. You&#8217;ll see a bunch of references for WordPress. You can ship those to OpenSearch if you want and alert on errors or whatever you want to do.</p>
<h1>Delete EKS and OpenSearch cluster</h1>
<p>Detach the policy, delete the daemonset and delete the EKS cluster.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam detach-role-policy --policy-arn arn:aws:iam::aws:policy/CloudWatchAgentServerPolicy --role-name eksctl-eksWordPress-nodegroup-ng-NodeInstanceRole-LWJZESQ468Y2
ClusterName=&lt;CLUSTER_NAME&gt;
RegionName=&lt;REGION&gt;
FluentBitHttpPort=&#039;2020&#039;
FluentBitReadFromHead=&#039;Off&#039;
&#x5B;&#x5B; ${FluentBitReadFromHead} = &#039;On&#039; ]] &amp;&amp; FluentBitReadFromTail=&#039;Off&#039;|| FluentBitReadFromTail=&#039;On&#039;
&#x5B;&#x5B; -z ${FluentBitHttpPort} ]] &amp;&amp; FluentBitHttpServer=&#039;Off&#039; || FluentBitHttpServer=&#039;On&#039;
curl https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/quickstart/cwagent-fluent-bit-quickstart.yaml | sed &#039;s/{{cluster_name}}/&#039;${ClusterName}&#039;/;s/{{region_name}}/&#039;${LogRegion}&#039;/;s/{{http_server_toggle}}/&quot;&#039;${FluentBitHttpServer}&#039;&quot;/;s/{{http_server_port}}/&quot;&#039;${FluentBitHttpPort}&#039;&quot;/;s/{{read_from_head}}/&quot;&#039;${FluentBitReadFromHead}&#039;&quot;/;s/{{read_from_tail}}/&quot;&#039;${FluentBitReadFromTail}&#039;&quot;/&#039; | kubectl delete -f -
eksctl delete cluster --name eksWordPress --region=us-east-2
</pre>
<p>Delete policy.</p>
<pre class="brush: bash; title: ; notranslate">
aws iam delete-policy --policy-arn arn:aws:iam::492943873543:policy/polOpenSearch
</pre>
<p>Delete CloudWatch log groups.</p>
<pre class="brush: bash; title: ; notranslate">
EKS_CLUSTER=eksWordPress
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/application&quot;
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/dataplane&quot;
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/host&quot;
aws logs delete-log-group --log-group-name &quot;/aws/containerinsights/$EKS_CLUSTER/performance&quot;
aws logs delete-log-group --log-group-name &quot;/aws/eks/$EKS_CLUSTER/cluster&quot;
</pre>
<p>Delete the OpenSearch cluster.</p>
<pre class="brush: bash; title: ; notranslate">
aws opensearch delete-domain --domain-name oswordpress
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2022/10/aws-eks-monitoring-and-logging/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
