avatarRocky Chen

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

4849

Abstract

</span> }</pre></div><div id="a480"><pre>private_subnet_tags <span class="hljs-operator">=</span> { <span class="hljs-string">"kubernetes.io/cluster/${local.cluster_name}"</span> <span class="hljs-operator">=</span> <span class="hljs-string">"shared"</span> <span class="hljs-string">"kubernetes.io/role/internal-elb"</span> <span class="hljs-operator">=</span> <span class="hljs-string">"1"</span> } }</pre></div><h1 id="d85b">Provisioning an additional node group in the EKS cluster</h1><p id="86de">Node group is a set of EC2 instances with the same type. An EKS cluster may contains multiple node groups with different instance types.</p><p id="3c3f">In this tutorial repo, all EKS-related settings are included in the file <code>eks-cluster.tf</code>. By default, it contains two node groups.</p><div id="2f02"><pre>worker_groups = [ { name = <span class="hljs-string">"worker-group-1"</span> <span class="hljs-keyword">instance_type </span> = <span class="hljs-string">"t2.small"</span> <span class="hljs-keyword">additional_userdata </span> = <span class="hljs-string">"echo foo bar"</span> asg_desired_capacity = <span class="hljs-number">2</span> <span class="hljs-keyword">additional_security_group_ids </span>= [aws_security_group.worker_group_mgmt_one.id] }, { name = <span class="hljs-string">"worker-group-2"</span> <span class="hljs-keyword">instance_type </span> = <span class="hljs-string">"t2.medium"</span> <span class="hljs-keyword">additional_userdata </span> = <span class="hljs-string">"echo foo bar"</span> <span class="hljs-keyword">additional_security_group_ids </span>= [aws_security_group.worker_group_mgmt_two.id] asg_desired_capacity = <span class="hljs-number">1</span> }, ]</pre></div><p id="dbc7">These two node groups contains different instance types, one uses <code>t2.small</code> and the other uses <code>t2.medium</code>.</p><p id="34e0">Ideally, we hope Kubernetes schedules resource-consuming Pods to the nodes with powerful CPU or memory and schedule other Pods to the normal nodes. However, the node group setting above is not enough since Kubernetes has no idea about the strategy.</p><p id="1467">Therefore, Kubernetes treats those nodes in the both node groups as same and schedules Pods onto them with no difference.</p><p id="8eee">In order to achieve the scheduling strategy mentioned above, tainting nodes is needed.</p><h1 id="815e">Tainting nodes in the node group</h1><p id="ddab"><b>Taint</b> is a Kubernetes concept which works together with <b>toleration</b>.</p><h2 id="67df">Taint</h2><p id="6fe0">When a node is tainted, it repels all Pods from being scheduled onto it by Kubernetes except for those Pods with corresponding tolerations.</p><h2 id="744d">Toleration</h2><blockquote id="702a"><p><i>Tolerations</i> are applied to pods, and allow (but do not require) the pods to schedule onto nodes with matching taints.</p></blockquote><h2 id="c816">Node Group with Tainted Nodes</h2><p id="0f2a">Imagine this, there is a group of powerful nodes and all of them are tainted. So, the Pods without tolerations cannot be scheduled onto them.</p><p id="2cec">For those resource-consuming Pods, we can put tolerations of the corresponding taint, and then they can only be scheduled onto those tainted powerful node in the group.</p><p id="0de4">For more details about taints and tolerations, please refer to <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/">https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/</a>.</p><h2 id="f409">Taints in Terraform</h2><p id="64a0">How can we implement tainting nodes with Terraform? It is a bit tricky by providing <code>kubelet</code> arguments like:</p><div id="ca1e"><pre>--<span class="hljs-keyword">register</span>-<span class="hljs-keyword">with</span>-taints app=worker-<span class="hljs-number">3</span>:NoSchedule</pre></div><p id="ab78">Then, a taint is added to the node spec:</p><div id="7a0a"><pre><span class="hljs-attribute">taints</span><span class="hljs-punctuation">:</span> <span class="hljs-bullet">-</span> <span class="hljs-string">effect: NoSchedule</span> <span class="hljs-attribute">key</span><span class="hljs-punctuation">:</span> <span class="hljs-string">app</span> <span class="hljs-attribute">value</span><span class="hljs-punctuation">:</span> <span class="hljs-string">worker-3</span></pre></div><p id="b019">For the Pods they want to be scheduled onto those tainted node, they should include the following tolerations in their spec:</p><div id="7d65"><pre><span class="hljs-symbol">tolerations:</span> - key: <span class="hljs-strin # Options g">"app"</span> <span class="hljs-symbol"> operator:</span> <span class="hljs-string">"Equal"</span> <span class="hljs-symbol"> value:</span> <span class="hljs-string">"worker-3"</span> <span class="hljs-symbol"> effect:</span> <span class="hljs-string">"NoSchedule"</span></pre></div><p id="d0fe">And here is my definition of node groups in the <code>eks-cluster.tf</code> :</p><div id="4d39"><pre>worker_groups = [ { name = <span class="hljs-string">"worker-group-1"</span> <span class="hljs-keyword">instance_type </span> = <span class="hljs-string">"t2.small"</span> <span class="hljs-keyword">additional_userdata </span> = <span class="hljs-string">"echo foo bar"</span> asg_desired_capacity = <span class="hljs-number">2</span> <span class="hljs-keyword">additional_security_group_ids </span>= [aws_security_group.worker_group_mgmt_one.id] }, { name = <span class="hljs-string">"worker-group-3"</span> <span class="hljs-keyword">instance_type </span> = <span class="hljs-string">"t2.large"</span> <span class="hljs-keyword">additional_userdata </span> = <span class="hljs-string">"echo foo bar"</span> <span class="hljs-keyword">additional_security_group_ids </span>= [aws_security_group.worker_group_mgmt_three.id] asg_desired_capacity = <span class="hljs-number">1</span> kubelet_extra_args = <span class="hljs-string">"--node-labels=node_group=worker-group-3 --register-with-taints app=worker-3:NoSchedule"</span> }, ]</pre></div><h1 id="a5a3">Apply EKS Cluster with Terraform</h1><h2 id="d6dd">Apply Resource</h2><p id="5c9d">Once everything is ready, run the following commands to apply the EKS cluster:</p><div id="a9ce"><pre><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript">terraform init</span></pre></div><div id="312b"><pre><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript">terraform apply</span></pre></div><h2 id="9932">Generate Kubectl Configuration</h2><p id="5eaf">If you run terraform with your AWS user account, it would be easy to retrieve the access credentials for your cluster and automatically configure <code>kubectl</code>.</p><div id="6215"><pre><span class="hljs-meta prompt_">&gt;</span> <span class="language-javascript">aws eks --region $(terraform output -raw region) update-kubeconfig --name $(terraform output -raw cluster_name)</span></pre></div><p id="4da5">However, if you run terraform with assuming a AWS Role like me, it needs an extra step for configuring <code>kubectl</code>.</p><ul><li>Add the assuming role to AWS config file (<code>$HOME/.aws/config</code>)</li></ul><div id="8bde"><pre><span class="hljs-section">[profile dev-terraform]</span> <span class="hljs-attr">role_arn</span> = arn:aws:iam::xxxx4338xxxx:role/ClusterAdminRole <span class="hljs-attr">source_profile</span> = default <span class="hljs-attr">output</span> = json <span class="hljs-attr">region</span> = us-east-<span class="hljs-number">1</span></pre></div><p id="3b68">Append the section above to the file.</p><ul><li>Run update-kubeconfig CLI to configure <code>kubectl</code>.</li></ul><div id="cc62"><pre>&gt; aws eks <span class="hljs-attr">--region</span> $(terraform output -raw region) update-kubeconfig <span class="hljs-attr">--name</span> $(terraform output -raw cluster_name) <span class="hljs-attr">--profile</span> dev-terraform</pre></div><h2 id="7222">Connect to the EKS Cluster</h2><p id="e793">Now, your default Kubernetes config is pointing to the EKS cluster just created. Let’s run a <code>kubectl</code> command directly.</p><div id="8cff"><pre>&gt; kubectl <span class="hljs-built_in">get</span> node NAME STATUS ROLES AGE VERSION ip-<span class="hljs-number">10</span>-<span class="hljs-number">17</span>-<span class="hljs-number">41</span>-<span class="hljs-number">172.</span>ec2.internal Ready &lt;none&gt; 7d20h v1<span class="hljs-number">.20</span><span class="hljs-number">.4</span>-eks-<span class="hljs-number">6b7464</span> ip-<span class="hljs-number">10</span>-<span class="hljs-number">17</span>-<span class="hljs-number">42</span>-<span class="hljs-number">55.</span>ec2.internal Ready &lt;none&gt; 7d20h v1<span class="hljs-number">.20</span><span class="hljs-number">.4</span>-eks-<span class="hljs-number">6b7464</span> ip-<span class="hljs-number">10</span>-<span class="hljs-number">17</span>-<span class="hljs-number">42</span>-<span class="hljs-number">81.</span>ec2.internal Ready &lt;none&gt; 7d20h v1<span class="hljs-number">.20</span><span class="hljs-number">.4</span>-eks-<span class="hljs-number">6b7464</span></pre></div></article></body>
Photo by Massimo Botturi on Unsplash

Terraform Provisioning EKS Cluster with Additional Node Groups

Terraform is a popular provisioning tool for building, updating, and versioning infrastructure safely and efficiently. It is also well-known technology of Infrastructure-as-Code (IaC).

A common scenario in the work of cloud engineers is to provision and update Kubernetes clusters. So, the following points are covered in this post with Terraform.

  • Provisioning an EKS cluster
  • Provisioning an additional node group in the EKS cluster
  • Tainting nodes in the node group

Provisioning an EKS cluster

Terraform provides a good tutorial about how to providsion an EKS cluster: https://learn.hashicorp.com/tutorials/terraform/eks

Therefore, the github repo is good enough for start point.

Clone the Repo

Clone the repo of learn-terraform-provision-eks-cluster to local machine.

> git clone git@github.com:hashicorp/learn-terraform-provision-eks-cluster.git

Update AWS Info

In this repo, all the config values is preset and you have to put the right values per your AWS account information.

VPC

In the vpc.tf file, update region, provider and subnets with your preferred values.

If you want to provision an EKS cluster by assuming an IAM role, then specify the role_arn in the provider section.

Here is my case:

provider "aws" {
  region = "us-east-1"
  assume_role {
    role_arn = "arn:aws:iam::xxxx4338xxxx:role/ClusterAdminRole"
  }
}

In the vpc module, update CIDR and subnets for the EKS VPC.

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "2.66.0"
name                 = "rocky-dev-eks-vpc"
  cidr                 = "10.17.40.0/22"
  azs                  = data.aws_availability_zones.available.names
  private_subnets      = ["10.17.41.0/24", "10.17.42.0/24", "10.17.43.0/24"]
  public_subnets       = ["10.17.40.64/26", "10.17.40.128/26", "10.17.40.192/26"]
  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true
tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
  }
public_subnet_tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
    "kubernetes.io/role/elb"                      = "1"
  }
private_subnet_tags = {
    "kubernetes.io/cluster/${local.cluster_name}" = "shared"
    "kubernetes.io/role/internal-elb"             = "1"
  }
}

Provisioning an additional node group in the EKS cluster

Node group is a set of EC2 instances with the same type. An EKS cluster may contains multiple node groups with different instance types.

In this tutorial repo, all EKS-related settings are included in the file eks-cluster.tf. By default, it contains two node groups.

worker_groups = [
    {
      name                          = "worker-group-1"
      instance_type                 = "t2.small"
      additional_userdata           = "echo foo bar"
      asg_desired_capacity          = 2
      additional_security_group_ids = [aws_security_group.worker_group_mgmt_one.id]
    },
    {
      name                          = "worker-group-2"
      instance_type                 = "t2.medium"
      additional_userdata           = "echo foo bar"
      additional_security_group_ids = [aws_security_group.worker_group_mgmt_two.id]
      asg_desired_capacity          = 1
    },
  ]

These two node groups contains different instance types, one uses t2.small and the other uses t2.medium.

Ideally, we hope Kubernetes schedules resource-consuming Pods to the nodes with powerful CPU or memory and schedule other Pods to the normal nodes. However, the node group setting above is not enough since Kubernetes has no idea about the strategy.

Therefore, Kubernetes treats those nodes in the both node groups as same and schedules Pods onto them with no difference.

In order to achieve the scheduling strategy mentioned above, tainting nodes is needed.

Tainting nodes in the node group

Taint is a Kubernetes concept which works together with toleration.

Taint

When a node is tainted, it repels all Pods from being scheduled onto it by Kubernetes except for those Pods with corresponding tolerations.

Toleration

Tolerations are applied to pods, and allow (but do not require) the pods to schedule onto nodes with matching taints.

Node Group with Tainted Nodes

Imagine this, there is a group of powerful nodes and all of them are tainted. So, the Pods without tolerations cannot be scheduled onto them.

For those resource-consuming Pods, we can put tolerations of the corresponding taint, and then they can only be scheduled onto those tainted powerful node in the group.

For more details about taints and tolerations, please refer to https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/.

Taints in Terraform

How can we implement tainting nodes with Terraform? It is a bit tricky by providing kubelet arguments like:

--register-with-taints app=worker-3:NoSchedule

Then, a taint is added to the node spec:

taints:
  - effect: NoSchedule
    key: app
    value: worker-3

For the Pods they want to be scheduled onto those tainted node, they should include the following tolerations in their spec:

tolerations:
- key: "app"
  operator: "Equal"
  value: "worker-3"
  effect: "NoSchedule"

And here is my definition of node groups in the eks-cluster.tf :

worker_groups = [
    {
      name                          = "worker-group-1"
      instance_type                 = "t2.small"
      additional_userdata           = "echo foo bar"
      asg_desired_capacity          = 2
      additional_security_group_ids = [aws_security_group.worker_group_mgmt_one.id]
    },
    {
      name                          = "worker-group-3"
      instance_type                 = "t2.large"
      additional_userdata           = "echo foo bar"
      additional_security_group_ids = [aws_security_group.worker_group_mgmt_three.id]
      asg_desired_capacity          = 1
      kubelet_extra_args            = "--node-labels=node_group=worker-group-3 --register-with-taints app=worker-3:NoSchedule"
    },
  ]

Apply EKS Cluster with Terraform

Apply Resource

Once everything is ready, run the following commands to apply the EKS cluster:

> terraform init
> terraform apply

Generate Kubectl Configuration

If you run terraform with your AWS user account, it would be easy to retrieve the access credentials for your cluster and automatically configure kubectl.

> aws eks --region $(terraform output -raw region) update-kubeconfig --name $(terraform output -raw cluster_name)

However, if you run terraform with assuming a AWS Role like me, it needs an extra step for configuring kubectl.

  • Add the assuming role to AWS config file ($HOME/.aws/config)
[profile dev-terraform]
role_arn = arn:aws:iam::xxxx4338xxxx:role/ClusterAdminRole
source_profile = default
output = json
region = us-east-1

Append the section above to the file.

  • Run update-kubeconfig CLI to configure kubectl.
> aws eks --region $(terraform output -raw region) update-kubeconfig --name $(terraform output -raw cluster_name) --profile dev-terraform

Connect to the EKS Cluster

Now, your default Kubernetes config is pointing to the EKS cluster just created. Let’s run a kubectl command directly.

> kubectl get node
NAME                           STATUS   ROLES    AGE     VERSION
ip-10-17-41-172.ec2.internal   Ready    <none>   7d20h   v1.20.4-eks-6b7464
ip-10-17-42-55.ec2.internal    Ready    <none>   7d20h   v1.20.4-eks-6b7464
ip-10-17-42-81.ec2.internal    Ready    <none>   7d20h   v1.20.4-eks-6b7464
Kubernetes
Terraform
AWS
Programming
Recommended from ReadMedium