{"id":230,"date":"2026-04-30T01:31:00","date_gmt":"2026-04-30T01:31:00","guid":{"rendered":"https:\/\/blog.ngocha.biz\/?p=230"},"modified":"2026-04-30T01:31:00","modified_gmt":"2026-04-30T01:31:00","slug":"setup-kubernetes-cluster-kubeadm","status":"publish","type":"post","link":"https:\/\/blog.ngocha.biz\/?p=230","title":{"rendered":"How To Setup Kubernetes Cluster Using Kubeadm &#8211; Easy Guide"},"content":{"rendered":"<p>In this blog post, I have covered the <strong>step-by-step guide to setting up a kubernetes cluster<\/strong> using Kubeadm with one control plane node and two worker nodes.<\/p>\n<p><a href=\"https:\/\/github.com\/kubernetes\/kubeadm?ref=devopscube.com\" rel=\"noreferrer noopener\">Kubeadm<\/a> is an excellent tool to set up a working kubernetes cluster in less time. It does all the heavy lifting in terms of setting up all kubernetes cluster components. Also, it follows all the configuration best practices for a kubernetes cluster.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note<\/strong><\/b>: This guide is updated for latest Kubernetes 1.36 version<\/div>\n<\/div>\n<h2 id=\"what-is-kubeadm\">What is Kubeadm?<\/h2>\n<p>Kubeadm is a tool to set up a minimum viable Kubernetes cluster without much complex configuration. Also, Kubeadm makes the whole process easy by running a series of prechecks to ensure that the server has all the essential components and configs to run Kubernetes.<\/p>\n<p>It is developed and maintained by the official Kubernetes community. There are other options like <a href=\"https:\/\/devopscube.com\/kubernetes-minikube-tutorial\/\" rel=\"noreferrer\">minikube<\/a>, <a href=\"https:\/\/devopscube.com\/kubernetes-kind-cluster-tutorial-setup-and-deploy-apps\/\" rel=\"noreferrer\">kind<\/a>, etc., that are pretty easy to set up.  Those are good options with minimum hardware requirements if you are deploying and testing applications on Kubernetes.<\/p>\n<p>But if you want to play around with the cluster components or test utilities that are part of cluster administration, Kubeadm is the best option. Also, you can create a production-like cluster locally on a workstation for development and testing purposes.<\/p>\n<h2 id=\"kubeadm-setup-prerequisites\">Kubeadm Setup Prerequisites<\/h2>\n<p>Following are the prerequisites for <strong>Kubeadm Kubernetes cluster setup<\/strong>.<\/p>\n<ol>\n<li>Minimum two <strong>Ubuntu nodes<\/strong> [One control plane node and one worker node]. You can have more worker nodes as per your requirement.<\/li>\n<li>The control plane node should have a minimum of <strong>2 vCPU and 2GB RAM<\/strong>.<\/li>\n<li>For the worker nodes, a minimum of 1vCPU and 2 GB RAM is recommended.<\/li>\n<li><strong>10.X.X.X\/X <\/strong>network range with static IPs for control plane node and worker nodes. We will be using the <strong>192.x.x.x<\/strong> series as the pod network range that will be used by the Calico network plugin. Make sure the Node IP range and pod IP range don&#8217;t overlap.<\/li>\n<\/ol>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note<\/strong><\/b>: If you are setting up the cluster in the corporate network behind a proxy, ensure set the proxy variables and have access to the container registry and docker hub. Or talk to your network administrator to whitelist <b><code spellcheck=\"false\" style=\"white-space: pre-wrap;\"><strong>registry.k8s.io<\/strong><\/code><\/b> to pull the required images.<\/div>\n<\/div>\n<h2 id=\"kubeadm-port-requirements\">Kubeadm Port Requirements<\/h2>\n<p>Please refer to the following image and make sure all the ports are allowed for the control plane node and the worker nodes. If you are setting up the kubeadm cluster cloud servers, ensure you allow the ports in the firewall configuration.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/kuberetes-port-requirements-min-1.png\" class=\"kg-image\" alt=\"Kubeadm kubernetes cluster port requirements\" loading=\"lazy\" width=\"668\" height=\"473\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/kuberetes-port-requirements-min-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/kuberetes-port-requirements-min-1.png 668w\"><\/figure>\n<p>Additionally, Calico, requires specific UDP ports for inter-node and pod communication. So enable all UDP traffic between the cluster nodes.<\/p>\n<p>If UDP traffic is blocked, DNS queries from pods will fail, leading to errors like&nbsp;<code>connection timed out<\/code>, and Calico-based networking may also break, causing pod communication issues.<\/p>\n<p>If you are using vagrant-based Ubuntu VMs, the firewall will be disabled by default. So you don&#8217;t have to do any firewall configurations.<\/p>\n<h2 id=\"kubeadm-for-kubernetes-certification-exams\">Kubeadm for Kubernetes Certification Exams<\/h2>\n<p>If you are preparing for Kubernetes certifications like <strong>CKA, CKAD, or CKS<\/strong>, you can use the local kubeadm clusters to practice for the certification exam. In fact, kubeadm itself is part of the CKA and <a href=\"https:\/\/devopscube.com\/cks-exam-guide-tips\/\" rel=\"noreferrer noopener\">CKS exam<\/a>. For CKA you might be asked to bootstrap a cluster using Kubeadm. For CKS, you have to upgrade the cluster using kubeadm.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Important Note<\/strong><\/b>: If you are planning for Kubernetes certification, make use of the <a href=\"https:\/\/devopscube.ghost.io\/kubernetes-certification-coupon\/?ref=devopscube.com\" rel=\"noreferrer noopener\">CKA\/CKAD\/CKS coupon Codes<\/a> before the price increases.<\/div>\n<\/div>\n<p>If you use Vagrant-based VMs on your workstation, you can start and stop the cluster whenever you need. By having the local Kubeadm clusters, you can play around with all the cluster configurations and learn to troubleshoot different components in the cluster.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udccc<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Recommendation:<\/strong><\/b> Looking for an organized way to learn Kubernetes and prepare for the CKA exam ? <\/p>\n<p>Check out our <a href=\"https:\/\/techiescamp.com\/p\/cka-complete-prep-course-practice-tests?ref=devopscube.com\" rel=\"noreferrer noopener\">CKA course and practice test bundle<\/a> (use code DCUBE30 to get 30% OFF). <\/p>\n<p>We explain concepts using illustrations, hands-on exercises, real-world examples, and provide dedicated discord based user support.<\/p><\/div>\n<\/div>\n<h2 id=\"vagrantfile-kubeadm-scripts-manifests\">Vagrantfile, Kubeadm Scripts &amp; Manifests<\/h2>\n<p>Also, all the commands used in this guide for control plane and worker nodes config are hosted in <a href=\"https:\/\/github.com\/techiescamp\/kubeadm-scripts?ref=devopscube.com\" rel=\"noreferrer noopener\">GitHub<\/a>. You can clone the repository for reference.<\/p>\n<pre><code class=\"language-bash\">git clone https:\/\/github.com\/techiescamp\/kubeadm-scripts<\/code><\/pre>\n<p>This guide intends to make you understand each config required for the Kubeadm setup. If you don&#8217;t want to run the commands one by one, <strong>you can run the script file directl<\/strong>y.<\/p>\n<p>If you are using Vagrant to set up the Kubernetes cluster, you can make use of my Vagrantfile. It launches 3 VMs. A self-explanatory basic Vagrantfile. If you are new to Vagrant, check the <a href=\"https:\/\/devopscube.com\/vagrant-tutorial-beginners\/\" rel=\"noreferrer noopener\">Vagrant tutorial<\/a>.<\/p>\n<p>If you are a Terraform and AWS user, you can make use of the Terraform script present under the Terraform folder to spin up <a href=\"https:\/\/devopscube.com\/use-aws-cli-create-ec2-instance\/\">ec2 instances<\/a>.<\/p>\n<p>Also, I have created a <strong>video demo of the whole kubeadm setup. You can refer to <\/strong>it during the setup.<\/p>\n<figure class=\"kg-card kg-embed-card\"><iframe loading=\"lazy\" width=\"160\" height=\"90\" src=\"https:\/\/www.youtube.com\/embed\/xX52dc3u2HU?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen=\"\"><\/iframe><\/figure>\n<h2 id=\"kubernetes-cluster-setup-using-kubeadm\">Kubernetes Cluster Setup Using Kubeadm<\/h2>\n<p>The following are the high-level steps involved in setting up a kubeadm-based Kubernetes cluster.<\/p>\n<ol>\n<li>Install container runtime on all nodes. We will be using <a href=\"https:\/\/cri-o.io\/?ref=devopscube.com\" rel=\"noreferrer\">CRI-O<\/a><\/li>\n<li>Install Kubeadm, Kubelet, and <a href=\"https:\/\/devopscube.com\/kubectl-aliases\/\" rel=\"noreferrer\">kubectl<\/a> on all the nodes.<\/li>\n<li>Initiate Kubeadm control plane configuration on the control plane node.<\/li>\n<li>Save the node join command with the token.<\/li>\n<li>Install the <a href=\"https:\/\/docs.tigera.io\/calico\/latest\/about\/?ref=devopscube.com\" rel=\"noreferrer noopener\">Calico network plugin<\/a> (operator).<\/li>\n<li>Join the worker node to the control plane node using the join command.<\/li>\n<li>Validate all cluster components and nodes.<\/li>\n<li>Install Kubernetes Metrics Server<\/li>\n<li>Deploy a sample app and validate the app<\/li>\n<\/ol>\n<p>All the steps given in this guide are referenced from the official Kubernetes documentation and related GitHub project pages.<\/p>\n<p>If you want to understand every cluster component in detail, refer to the comprehensive <a href=\"https:\/\/devopscube.com\/kubernetes-architecture-explained\/\">Kubernetes Architecture<\/a>.<\/p>\n<p>Now let&#8217;s get started with the setup.<\/p>\n<h2 id=\"step-1-enable-iptables-bridged-traffic-on-all-the-nodes\">Step 1: Enable iptables Bridged Traffic on all the Nodes<\/h2>\n<p>Execute the following commands on <strong>all the nodes<\/strong> for IPtables to see bridged traffic. Here we are tweaking some kernel parameters and setting them using <strong><code>sysctl<\/code><\/strong>.<\/p>\n<pre><code class=\"language-bash\"># Keeps the swap off during reboot\n(crontab -l 2&gt;\/dev\/null; echo \"@reboot \/sbin\/swapoff -a\") | crontab - || true\nsudo apt-get update -y\n\n# Create the .conf file to load the modules at bootup\ncat &lt;&lt;EOF | sudo tee \/etc\/modules-load.d\/k8s.conf\noverlay\nbr_netfilter\nEOF\n\nsudo modprobe overlay\nsudo modprobe br_netfilter\n\n# Sysctl params required by setup, params persist across reboots\ncat &lt;&lt;EOF | sudo tee \/etc\/sysctl.d\/k8s.conf\nnet.bridge.bridge-nf-call-iptables  = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.ipv4.ip_forward                 = 1\nEOF\n\n# Apply sysctl params without reboot\nsudo sysctl --system<\/code><\/pre>\n<h2 id=\"step-2-disable-swap-on-all-the-nodes\">Step 2: Disable swap on all the Nodes<\/h2>\n<p>For kubeadm to work properly, you need to disable swap on all the nodes using the following command.<\/p>\n<pre><code class=\"language-bash\">sudo swapoff -a\n(crontab -l 2&gt;\/dev\/null; echo \"@reboot \/sbin\/swapoff -a\") | crontab - || true<\/code><\/pre>\n<p>The <code>fstab<\/code> entry will make sure the swap is off on system reboots.<\/p>\n<p>You can also, control swap errors using the kubeadm parameter <code>--ignore-preflight-errors Swap<\/code> we will look at it in the latter part.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note<\/strong><\/b>: From 1.28 kubeadm has beta support for using swap with kubeadm clusters. Read <a href=\"https:\/\/devopscube.com\/kubernetes-swap\/\" rel=\"noreferrer\">kubernetes swap<\/a> blog to understand more.<\/div>\n<\/div>\n<h2 id=\"step-3-install-cri-o-runtime-on-all-the-nodes\">Step 3: Install CRI-O  Runtime On All The Nodes<\/h2>\n<p>The basic requirement for a Kubernetes cluster is a <a href=\"https:\/\/devopscube.com\/what-is-docker\/\" rel=\"noreferrer noopener\">container runtime<\/a>. You can have any one of the following container runtimes.<\/p>\n<ol>\n<li>CRI-O<\/li>\n<li>containerd<\/li>\n<li>Docker Engine (using cri-dockerd)<\/li>\n<\/ol>\n<p>We will be using CRI-O instead of containerd for this setup.<\/p>\n<p>Execute the following commands <strong>on all the nodes<\/strong> to install required dependencies and the latest version of CRI-O and <strong><code>crictl<\/code><\/strong>.<\/p>\n<pre><code class=\"language-bash\">CRIO_VERSION=\"v1.36\"\nKUBERNETES_VERSION=\"v1.36\"\n\n# Install CRI-O Runtime\nsudo apt-get update -y\nsudo apt-get install -y software-properties-common curl apt-transport-https ca-certificates\n\nsudo install -m 0755 -d \/etc\/apt\/keyrings\n\n# Add CRI-O apt repository\ncurl -fsSL https:\/\/download.opensuse.org\/repositories\/isv:\/cri-o:\/stable:\/$CRIO_VERSION\/deb\/Release.key |\n    gpg --dearmor -o \/etc\/apt\/keyrings\/cri-o-apt-keyring.gpg\n\necho \"deb [signed-by=\/etc\/apt\/keyrings\/cri-o-apt-keyring.gpg] https:\/\/download.opensuse.org\/repositories\/isv:\/cri-o:\/stable:\/$CRIO_VERSION\/deb\/ \/\" |\n    tee \/etc\/apt\/sources.list.d\/cri-o.list\n\nsudo apt-get update -y\nsudo apt-get install -y cri-o\n\nsudo systemctl daemon-reload\nsudo systemctl enable crio --now\nsudo systemctl start crio.service\n<\/code><\/pre>\n<h2 id=\"step-4-install-configure-crictl-to-use-cri-o\">Step 4: Install &amp; Configure Crictl to use CRI-O<\/h2>\n<p>Download and install Crictl on all nodes and configure it to use the CRI-O runtime.<\/p>\n<pre><code class=\"language-bash\">CRICTL_VERSION=\"v1.36.0\"\n\n# Install crictl\ncurl -LO \"https:\/\/github.com\/kubernetes-sigs\/cri-tools\/releases\/download\/${CRICTL_VERSION}\/crictl-${CRICTL_VERSION}-linux-${CRICTL_ARCH}.tar.gz\"\nsudo tar zxvf \"crictl-${CRICTL_VERSION}-linux-${CRICTL_ARCH}.tar.gz\" -C \/usr\/local\/bin\nrm -f \"crictl-${CRICTL_VERSION}-linux-${CRICTL_ARCH}.tar.gz\"\n\n# Configure crictl to use CRI-O socket\ncat &lt;&lt;EOF | sudo tee \/etc\/crictl.yaml\nruntime-endpoint: unix:\/\/\/run\/crio\/crio.sock\nimage-endpoint: unix:\/\/\/run\/crio\/crio.sock\ntimeout: 10\ndebug: false\nEOF<\/code><\/pre>\n<p>Now you will also have <strong><code>crictl<\/code><\/strong> available in your nodes.<\/p>\n<p><code>crictl<\/code> is a CLI utility to interact with the containers created by the container runtime.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\">When you use container runtimes other than Docker, you can use the <b><strong style=\"white-space: pre-wrap;\">crictl utility<\/strong><\/b> to debug containers on the nodes. Also, it is useful in CKA and <a href=\"https:\/\/devopscube.com\/cks-exam-guide-tips\/\">CKS certification<\/a> where you need to <a href=\"https:\/\/devopscube.com\/troubleshoot-kubernetes-pods\/\" rel=\"noreferrer\">troubleshoot the pods<\/a>.<\/div>\n<\/div>\n<h2 id=\"step-5-install-kubeadm-kubelet-kubectl-on-all-nodes\">Step 5: Install Kubeadm &amp; Kubelet &amp; Kubectl on all Nodes<\/h2>\n<p>Download the GPG key for the Kubernetes APT repository <strong>on all the nodes.<\/strong><\/p>\n<pre><code class=\"language-bash\">\n# Add Kubernetes apt repository\ncurl -fsSL https:\/\/pkgs.k8s.io\/core:\/stable:\/$KUBERNETES_VERSION\/deb\/Release.key |\n    gpg --dearmor -o \/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg\n\necho \"deb [signed-by=\/etc\/apt\/keyrings\/kubernetes-apt-keyring.gpg] https:\/\/pkgs.k8s.io\/core:\/stable:\/$KUBERNETES_VERSION\/deb\/ \/\" |\n    tee \/etc\/apt\/sources.list.d\/kubernetes.list<\/code><\/pre>\n<p>Update apt repo<\/p>\n<pre><code class=\"language-bash\">sudo apt-get update -y<\/code><\/pre>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note<\/strong><\/b>: If you are preparing for Kubernetes certification, install the specific version of kubernetes. For example, the current Kubernetes version for CKA, CKAD, and CKS exams is Kubernetes version 1.34<\/div>\n<\/div>\n<p>You can use the following commands to find the latest versions. Install the first version in 1.35 so that you can practice the cluster upgrade task.<\/p>\n<pre><code class=\"language-bash\">apt-cache madison kubeadm | tac<\/code><\/pre>\n<p>Specify the version as shown below. Here I am using <code>1.36.0-1.1<\/code><\/p>\n<pre><code class=\"language-bash\">KUBERNETES_INSTALL_VERSION=\"1.36.0-1.1\"\n\nsudo apt-get install -y kubelet=\"$KUBERNETES_INSTALL_VERSION\" kubectl=\"$KUBERNETES_INSTALL_VERSION\" kubeadm=\"$KUBERNETES_INSTALL_VERSION\"\n<\/code><\/pre>\n<p>Or, to<strong> install the latest version<\/strong> from the repo use the following command without specifying any version.<\/p>\n<pre><code class=\"language-bash\">sudo apt-get install -y kubelet kubeadm kubectl<\/code><\/pre>\n<p>Add hold to the packages to prevent upgrades.<\/p>\n<pre><code class=\"language-bassh\">sudo apt-mark hold kubelet kubeadm kubectl<\/code><\/pre>\n<p>Now we have all the required utilities and tools for configuring Kubernetes components using kubeadm.<\/p>\n<p>Add the node IP to <code>KUBELET_EXTRA_ARGS<\/code>.<\/p>\n<pre><code class=\"language-bash\">sudo apt-get install -y jq\n\nlocal_ip=\"$(ip --json addr show eth0 | jq -r '.[0].addr_info[] | select(.family == \"inet\") | .local')\"\n\ncat &gt; \/etc\/default\/kubelet &lt;&lt; EOF\n\nKUBELET_EXTRA_ARGS=--node-ip=$local_ip\n\nEOF<\/code><\/pre>\n<h2 id=\"step-6-create-the-kubeadm-config\">Step 6: Create the Kubeadm Config<\/h2>\n<p>Now that we have the nodes ready with all the utilities for kubernetes, we will initialize the control plane using a kubeadm config file. We will be using the Kubernetes version&nbsp;<code>v1.36<\/code><\/p>\n<p>Here, you need to consider two options.<\/p>\n<ol>\n<li><strong>ControlPlane Node with Private IP:<\/strong> If you have nodes with only private IP addresses, the API server would be accessed over the private IP of the ControlPlane node.<\/li>\n<li><strong>ControlPlane With Public IP: <\/strong>If you are setting up<strong> <\/strong>Kubeadm cluster on Cloud platforms and you need ControlPlane API server access over the Public IP of the master node server.<\/li>\n<\/ol>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Important Note:<\/strong><\/b>&nbsp;If you are using cloud VMs with&nbsp;<b><strong style=\"white-space: pre-wrap;\">public IP addresses<\/strong><\/b>, it is important to assign a&nbsp;<b><strong style=\"white-space: pre-wrap;\">static public IP<\/strong><\/b>&nbsp;to the control plane node if you plan to access the cluster remotely from your workstation.<\/p>\n<p>Without a static IP, the public IP may change if the VM is restarted or shut down, which can cause connectivity issues and prevent the cluster from functioning as expected.<\/p><\/div>\n<\/div>\n<p>Lets create the Kubeadm Config.<\/p>\n<pre><code class=\"language-bash\">vi kubeadm.config<\/code><\/pre>\n<p>In the following YAML, replace&nbsp;<strong><code>192.168.201.10<\/code><\/strong>&nbsp;with your control plane node&#8217;s&nbsp;<strong>private IP<\/strong>&nbsp;in&nbsp;<strong>advertiseAddress.<\/strong><\/p>\n<ol>\n<li>For cloud VMs with a&nbsp;public IP we discussed earlier, update the <strong><code>controlPlaneEndpoint<\/code><\/strong> with the Public IP<\/li>\n<li>If you don&#8217;t need public access, use the private IP in <strong><code>controlPlaneEndpoint<\/code><\/strong> instead.<\/li>\n<\/ol>\n<pre><code class=\"language-yaml\">apiVersion: kubeadm.k8s.io\/v1beta4\nkind: InitConfiguration\nlocalAPIEndpoint:\n  advertiseAddress: \"192.168.249.201\"\n  bindPort: 6443\nnodeRegistration:\n  name: \"controlplane\"\n\n---\napiVersion: kubeadm.k8s.io\/v1beta4\nkind: ClusterConfiguration\nkubernetesVersion: \"v1.36.0\"\ncontrolPlaneEndpoint: \"192.168.249.201:6443\"\napiServer:\n  extraArgs:\n    - name: \"enable-admission-plugins\"\n      value: \"NodeRestriction\"\n    - name: \"audit-log-path\"\n      value: \"\/var\/log\/kubernetes\/audit.log\"\ncontrollerManager:\n  extraArgs:\n    - name: \"node-cidr-mask-size\"\n      value: \"24\"\nscheduler:\n  extraArgs:\n    - name: \"leader-elect\"\n      value: \"true\"\nnetworking:\n  podSubnet: \"10.244.0.0\/16\"\n  serviceSubnet: \"10.96.0.0\/12\"\n  dnsDomain: \"cluster.local\"\n\n---\napiVersion: kubelet.config.k8s.io\/v1beta1\nkind: KubeletConfiguration\ncgroupDriver: \"systemd\"\nsyncFrequency: \"1m\"\n\n---\napiVersion: kubeproxy.config.k8s.io\/v1alpha1\nkind: KubeProxyConfiguration\nmode: \"ipvs\"\nconntrack:\n  maxPerCore: 32768\n  min: 131072\n  tcpCloseWaitTimeout: \"1h\"\n  tcpEstablishedTimeout: \"24h\"<\/code><\/pre>\n<h2 id=\"step-7-initialize-kubeadm-on-controlplane-node\">Step 7: Initialize Kubeadm On Controlplane Node<\/h2>\n<p>Now, lets initialize the cluster.<\/p>\n<pre><code class=\"language-bash\">sudo kubeadm init --config=kubeadm.config<\/code><\/pre>\n<p>On a successful kubeadm initialization, you should get an output with <a href=\"https:\/\/devopscube.com\/kubernetes-kubeconfig-file\/\">kubeconfig file<\/a> location and the <strong>join command with the token<\/strong> as shown below. Copy that and save it to the file. we will need it for <strong>joining the worker node to the master<\/strong>.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/kubeadm-3.png\" class=\"kg-image\" alt=\"Kubeadm init command output\" loading=\"lazy\" width=\"1578\" height=\"888\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/kubeadm-3.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/kubeadm-3.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/kubeadm-3.png 1578w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>Use the following <strong>commands from the output <\/strong>to create the <code>kubeconfig<\/code> in the control plane so that you can use <code>kubectl<\/code> to interact with the cluster API.<\/p>\n<pre><code class=\"language-bash\">mkdir -p $HOME\/.kube\nsudo cp -i \/etc\/kubernetes\/admin.conf $HOME\/.kube\/config\nsudo chown $(id -u):$(id -g) $HOME\/.kube\/config<\/code><\/pre>\n<p>Now, verify the kubeconfig by executing the following kubectl command to list all the pods in the <code>kube-system<\/code> namespace.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get po -n kube-system<\/code><\/pre>\n<p>You should see the following output. You will see the two <strong>Coredns pods in a pending state.<\/strong> It is the expected behavior. Once we <strong>install the network plugin<\/strong>, it will be in a running state.<\/p>\n<pre><code class=\"language-bash\">$ kubectl get po -n kube-system\n\nNAME                                   READY   STATUS    RESTARTS   AGE\ncoredns-668d6bf9bc-6tpcr               0\/1     Pending   0          15m\ncoredns-668d6bf9bc-gwzql               0\/1     Pending   0          15m\netcd-controlplane                      1\/1     Running   0          15m\nkube-apiserver-controlplane            1\/1     Running   0          15m\nkube-controller-manager-controlplane   1\/1     Running   0          15m\nkube-proxy-xpbjr                       1\/1     Running   0          15m\nkube-scheduler-controlplane            1\/1     Running   0          15m<\/code><\/pre>\n<p>You can verify all the cluster component health statuses using the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl get --raw='\/readyz?verbose'<\/code><\/pre>\n<p>You can get the cluster info using the following command.<\/p>\n<pre><code class=\"language-bash\">kubectl cluster-info <\/code><\/pre>\n<p>By default,  apps won&#8217;t get scheduled on the control plane node. If you <strong>want to use the control plane node for scheduling apps<\/strong>, taint the master node.<\/p>\n<pre><code class=\"language-bash\">kubectl taint nodes --all node-role.kubernetes.io\/control-plane-<\/code><\/pre>\n<h2 id=\"step-8-join-worker-nodes-to-kubernetes-control-plane\">Step 8: Join Worker Nodes To Kubernetes Control Plane<\/h2>\n<p>We have set up <strong>cri-o, kubelet, and kubeadm<\/strong> utilities on the worker nodes as well.<\/p>\n<p>Now, let&#8217;s join the worker node to the Control Plane node using the Kubeadm join c<strong>ommand you have got in the output<\/strong> while setting up the master node.<\/p>\n<p>If you missed copying the join command, execute the following command in the master node to recreate the token with the join command.<\/p>\n<pre><code class=\"language-bash\">kubeadm token create --print-join-command<\/code><\/pre>\n<p>Here is what the command looks like. Use <code>sudo<\/code> if you running as a normal user.  This command performs the <a href=\"https:\/\/kubernetes.io\/docs\/reference\/access-authn-authz\/kubelet-tls-bootstrapping\/?ref=devopscube.com\" rel=\"noreferrer noopener\">TLS bootstrapping<\/a> for the nodes.<\/p>\n<pre><code class=\"language-bash\">sudo kubeadm join 10.128.0.37:6443 --token j4eice.33vgvgyf5cxw4u8i \\\n    --discovery-token-ca-cert-hash sha256:37f94469b58bcc8f26a4aa44441fb17196a585b37288f85e22475b00c36f1c61<\/code><\/pre>\n<p>On successful execution, you will see the output saying, &#8220;This node has joined the cluster&#8221;.<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-25-17.png\" class=\"kg-image\" alt=\"kubeadm node join output.\" loading=\"lazy\" width=\"555\" height=\"389\"><\/figure>\n<p>You can further <strong>add more nodes<\/strong> with the same join command.<\/p>\n<p>Now execute the <strong>kubectl command from the control-plane node<\/strong> to check if the node is added to the master.<\/p>\n<pre><code class=\"language-bash\">kubectl get nodes<\/code><\/pre>\n<p>Example output,<\/p>\n<pre><code class=\"language-bash\">root@controlplane:~# kubectl get nodes\n\nNAME           STATUS     ROLES           AGE   VERSION\ncontrolplane   NotReady   control-plane   28m   v1.36.0\nnode01         NotReady   &lt;none&gt;          39s   v1.36.0\nnode02         NotReady   &lt;none&gt;          33s   v1.36.0<\/code><\/pre>\n<p>You can see that the nodes are in a&nbsp;<strong>NotReady<\/strong>&nbsp;state because a Network Plugin (CNI) has not been installed in the cluster yet.<\/p>\n<p>Kubernetes requires the CNI for pod networking. Without it, the nodes cannot communicate properly, which is why they are in a&nbsp;<strong>NotReady<\/strong>&nbsp;state.<\/p>\n<p>The node status will change to&nbsp;<strong>Ready<\/strong>&nbsp;once we install and configure a Network Plugin in the cluster.<\/p>\n<p>Also, in the above output, the ROLE is  <code>&lt;none&gt;<\/code> for the worker nodes. <\/p>\n<p>You can add a label to the worker node using the following command. Replace <strong><code>node01<\/code><\/strong>  with the hostname of the worker node you want to label.<\/p>\n<pre><code>kubectl label node node01  node-role.kubernetes.io\/worker=worker<\/code><\/pre>\n<h2 id=\"step-9-install-calico-network-plugin-for-pod-networking\">Step 9: Install Calico Network Plugin for Pod Networking<\/h2>\n<p>Kubeadm does not configure any network plugin. You need to install a network plugin of your choice for <a href=\"https:\/\/devopscube.com\/kubernetes-pod\/\">kubernetes pod<\/a> networking and enable network policy.<\/p>\n<p>I am using the Calico network plugin for this setup.<\/p>\n<div class=\"kg-card kg-callout-card kg-callout-card-blue\">\n<div class=\"kg-callout-emoji\">\ud83d\udca1<\/div>\n<div class=\"kg-callout-text\"><b><strong style=\"white-space: pre-wrap;\">Note<\/strong><\/b>: Make sure you execute the kubectl command from where you have configured the <code spellcheck=\"false\" style=\"white-space: pre-wrap;\">kubeconfig<\/code> file. Either from the master of your workstation with the connectivity to the kubernetes API.<\/div>\n<\/div>\n<p>Follow the steps to configure Calico Operator.<\/p>\n<h4 id=\"step-1-install-the-tigera-operator-and-custom-resources\">Step 1: Install the Tigera Operator and Custom Resources<\/h4>\n<p>Execute the following commands to install the <a href=\"https:\/\/docs.tigera.io\/calico\/latest\/about\/?ref=devopscube.com\">Calico network plugin<\/a> operator and CRD&#8217;s on the cluster.<\/p>\n<pre><code class=\"language-bash\">kubectl create -f https:\/\/raw.githubusercontent.com\/projectcalico\/calico\/v3.31.3\/manifests\/operator-crds.yaml\nkubectl create -f https:\/\/raw.githubusercontent.com\/projectcalico\/calico\/v3.31.3\/manifests\/tigera-operator.yaml<\/code><\/pre>\n<p>The above command installs the Tigera operator and related <a href=\"https:\/\/devopscube.com\/kubernetes-objects-resources\/\" rel=\"noreferrer\">Custom Resource Definitions<\/a> (CRDs) in your Kubernetes cluster.<\/p>\n<p>The Tigera operator is responsible for managing the installation and lifecycle of Calico, which is a CNI (Container Network Interface) plugin used for pod networking and network policies.<\/p>\n<h3 id=\"step-2-download-the-calico-custom-resource\">Step 2: Download the Calico Custom Resource<\/h3>\n<p>Use the following command to download the Calico custom resource.<\/p>\n<pre><code class=\"language-bash\">curl -O https:\/\/raw.githubusercontent.com\/projectcalico\/calico\/v3.31.3\/manifests\/custom-resources.yaml<\/code><\/pre>\n<p>This will&nbsp;<strong>download<\/strong>&nbsp;the&nbsp;<code>custom-resources.yaml<\/code>&nbsp;file to your local machine, but it&nbsp;<strong>won&#8217;t apply<\/strong>&nbsp;it to the cluster.<\/p>\n<p>So why do you need to download and edit the custom resource for pod networking?<\/p>\n<p>Because the custom resource defines how Calico should be configured in your cluster.<\/p>\n<p>In our case, we need to&nbsp;<strong>adjust the IP pools<\/strong>&nbsp;to match our Pod network settings that we used in the Kubeadm config.<\/p>\n<h3 id=\"step-3-get-the-cluster-cidr-range\">Step 3: Get the cluster CIDR range<\/h3>\n<p>To get the&nbsp;<strong>cluster CIDR range<\/strong>, run the following command.<\/p>\n<pre><code>kubectl -n kube-system get pod -l component=kube-controller-manager -o yaml | grep -i cluster-cidr<\/code><\/pre>\n<p>You should get&nbsp;<strong>10.244.0.0\/16<\/strong>&nbsp;as the output because that value was used in the&nbsp;<strong><code>kubeadm.config<\/code><\/strong>&nbsp;during cluster initialization.<\/p>\n<h3 id=\"step-4-customize-custom-resourcesyaml\">Step 4: Customize custom-resources.yaml<\/h3>\n<p>Open the&nbsp;<code>custom-resources.yaml<\/code>&nbsp;file and change the default CIDR from&nbsp;<code>192.168.0.0\/16<\/code>&nbsp;to&nbsp;<code>10.244.0.0\/16<\/code>, which is the value specified in your kubeadm configuration. You should also see this same value in the previous output.<\/p>\n<p>Verifying the CIDR ensures that the pod network is correctly configured for your cluster.<\/p>\n<p>Open the&nbsp;<code>custom-resources.yaml<\/code><\/p>\n<pre><code class=\"language-bash\">vi custom-resources.yaml<\/code><\/pre>\n<p>Find the section with&nbsp;<code>cidr: 192.168.0.0\/16<\/code>&nbsp;and replace it with&nbsp;<code>cidr: 10.244.0.0\/16<\/code><\/p>\n<p>Here\u2019s how the updated section should look after replacing&nbsp;<code>cidr: 192.168.0.0\/16<\/code>&nbsp;with&nbsp;<code>cidr: 10.244.0.0\/16<\/code>\ufeff<\/p>\n<pre><code class=\"language-yaml\">apiVersion: operator.tigera.io\/v1\nkind: Installation\nmetadata:\n  name: default\nspec:\n  # Configures Calico networking.\n  calicoNetwork:\n    ipPools:\n    - name: default-ipv4-ippool\n      blockSize: 26\n      cidr: 10.244.0.0\/16\n      encapsulation: VXLANCrossSubnet\n      natOutgoing: Enabled\n      nodeSelector: all()<\/code><\/pre>\n<h3 id=\"step-5-deploy-the-custom-resource\">Step 5: Deploy the custom resource<\/h3>\n<p>After updating the&nbsp;<code>custom-resources.yaml<\/code>&nbsp;file with the correct pod CIDR, apply it to the cluster to complete the Calico setup.<\/p>\n<p>Run the following command:<\/p>\n<pre><code class=\"language-bash\">kubectl apply -f custom-resources.yaml<\/code><\/pre>\n<p>Now, when you check the pod status, you should see all the pods, including&nbsp;<strong>Calico<\/strong>&nbsp;and&nbsp;<strong>CoreDNS<\/strong>, in a&nbsp;<strong>Running<\/strong>&nbsp;state.<\/p>\n<pre><code class=\"language-bash\">kubectl get po -A<\/code><\/pre>\n<p>It may take a&nbsp;<strong>few minutes<\/strong>&nbsp;for all the pods to reach the running state.<\/p>\n<p>Also, if you check the&nbsp;<strong>node status<\/strong>, you will see that all the nodes are now in a&nbsp;<strong>Ready<\/strong>&nbsp;state.<\/p>\n<p>Before installing the&nbsp;<strong>CNI<\/strong>, the nodes were in a&nbsp;<strong>NotReady<\/strong>&nbsp;state.<\/p>\n<p>Run the following command to verify:<\/p>\n<pre><code class=\"language-bash\">kubectl get no<\/code><\/pre>\n<h2 id=\"step-10-setup-kubernetes-metrics-server\">Step 10: Setup Kubernetes Metrics Server<\/h2>\n<p>Kubeadm doesn&#8217;t install <a href=\"https:\/\/devopscube.com\/setup-prometheus-monitoring-on-kubernetes\/\" rel=\"noreferrer noopener\">metrics server<\/a> component during its initialization. We have to install it separately.<\/p>\n<p>To verify this, if you run the top command, you will see the <code>Metrics API not available<\/code> error.<\/p>\n<pre><code>root@controlplane:~# kubectl top nodes\n\nerror: Metrics API not available<\/code><\/pre>\n<p>To install the metrics server, execute the following metric server manifest file. <\/p>\n<pre><code>kubectl apply -f https:\/\/raw.githubusercontent.com\/techiescamp\/cka-certification-guide\/refs\/heads\/main\/lab-setup\/manifests\/metrics-server\/metrics-server.yaml<\/code><\/pre>\n<p>This manifest is taken from the official <a href=\"https:\/\/github.com\/kubernetes-sigs\/metrics-server?ref=devopscube.com\" rel=\"noreferrer noopener\">metrics serve<\/a>r repo. I have added the <code>--kubelet-insecure-tls<\/code> flag to the container to make it work in the local setup and hosted it separately. Or else, you will get the following error.<\/p>\n<pre><code> because it doesn't contain any IP SANs\" node<\/code><\/pre>\n<p>Once the metrics server objects are deployed, <strong>it takes a minute<\/strong> for you to see the node and pod metrics using the top command.<\/p>\n<pre><code>kubectl top nodes<\/code><\/pre>\n<p>You should be able to view the node metrics as shown below.<\/p>\n<pre><code class=\"language-bash\">root@controlplane:~# kubectl top nodes\n\nNAME           CPU(cores)   CPU(%)   MEMORY(bytes)   MEMORY(%)   \ncontrolplane   107m         5%       1083Mi          58%         \nnode01         76m          3%       639Mi           34%         \nnode02         28m          1%       732Mi           39%  <\/code><\/pre>\n<p>You can also view the pod CPU and memory metrics using the following command.<\/p>\n<pre><code>kubectl top pod -n kube-system<\/code><\/pre>\n<h2 id=\"step-11-deploy-a-sample-nginx-application\">Step 11: Deploy A Sample Nginx Application<\/h2>\n<p>Now that we have all the components to make the cluster and applications work, let&#8217;s deploy a sample Nginx application and see if we can access it over a NodePort<\/p>\n<p>Create an Nginx <a href=\"https:\/\/devopscube.com\/kubernetes-deployment-tutorial\/\">deployment<\/a>. Execute the following directly on the command line. It deploys the pod in the default namespace.<\/p>\n<pre><code>cat &lt;&lt;EOF | kubectl apply -f -\napiVersion: apps\/v1\nkind: Deployment\nmetadata:\n  name: nginx-deployment\nspec:\n  selector:\n    matchLabels:\n      app: nginx\n  replicas: 2 \n  template:\n    metadata:\n      labels:\n        app: nginx\n    spec:\n      containers:\n      - name: nginx\n        image: nginx:latest\n        ports:\n        - containerPort: 80      \nEOF<\/code><\/pre>\n<p>Expose the Nginx deployment on a <strong>NodePort 32000<\/strong><\/p>\n<pre><code>cat &lt;&lt;EOF | kubectl apply -f -\napiVersion: v1\nkind: Service\nmetadata:\n  name: nginx-service\nspec:\n  selector: \n    app: nginx\n  type: NodePort  \n  ports:\n    - port: 80\n      targetPort: 80\n      nodePort: 32000\nEOF<\/code><\/pre>\n<p>Check the pod status using the following command.<\/p>\n<pre><code>kubectl get pods<\/code><\/pre>\n<p>Once the deployment is up, you should be able to access the Nginx home page on the allocated NodePort.<\/p>\n<p>For example,<\/p>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/nginx-1.png\" class=\"kg-image\" alt=\"kubeadm Nnginx test deployment\" loading=\"lazy\" width=\"1336\" height=\"616\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/nginx-1.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/nginx-1.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/nginx-1.png 1336w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<h2 id=\"step-12-add-kubeadm-config-to-workstation\">Step 12: Add Kubeadm Config to Workstation<\/h2>\n<p>If you prefer to connect the Kubeadm cluster using kubectl from your workstation, you can merge the kubeadm <strong><code>admin.conf <\/code><\/strong>with your existing kubeconfig file.<\/p>\n<p>Follow the steps given below for the configuration.<\/p>\n<p><strong>Step 1: <\/strong>Copy the contents of <strong><code>admin.conf<\/code><\/strong> from the control plane node and save it in a file named <strong><code>kubeadm-config.yaml<\/code><\/strong> in your workstation.<\/p>\n<p><strong>Step 2: <\/strong> Take a backup of the existing kubeconfig.<\/p>\n<pre><code>cp ~\/.kube\/config ~\/.kube\/config.bak<\/code><\/pre>\n<p><strong>Step 3: <\/strong>Merge the default config with kubeadm-config.yaml and export it to KUBECONFIG variable<\/p>\n<pre><code>export KUBECONFIG=~\/.kube\/config:\/path\/to\/kubeadm-config.yaml<\/code><\/pre>\n<p><strong>Step 4:<\/strong> Merger the configs to a file<\/p>\n<pre><code>kubectl config view --flatten &gt; ~\/.kube\/merged_config.yaml<\/code><\/pre>\n<p><strong>Step 5:<\/strong> Replace the old config with the new config<\/p>\n<pre><code>mv ~\/.kube\/merged_config.yaml ~\/.kube\/config<\/code><\/pre>\n<p><strong>Step 6:<\/strong> List all the contexts<\/p>\n<pre><code>kubectl config get-contexts -o name<\/code><\/pre>\n<p><strong>Step 7:<\/strong> Set the current context to the kubeadm cluster.<\/p>\n<pre><code>kubectl config use-context &lt;cluster-name-here&gt;<\/code><\/pre>\n<p>Now, you should be able to connect to the Kubeadm cluster from your local workstation kubectl utility.<\/p>\n<h2 id=\"step-13-validate-the-cluster\">Step 13: Validate the Cluster<\/h2>\n<p>Lets do a final validation of the cluster.<\/p>\n<p>Run the following command to verify access to the API server.<\/p>\n<pre><code>$ kubectl cluster-info\n\nKubernetes control plane is running at https:\/\/192.168.249.201:6443\nCoreDNS is running at https:\/\/192.168.249.201:6443\/api\/v1\/namespaces\/kube-system\/services\/kube-dns:dns\/proxy\n\nTo further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.<\/code><\/pre>\n<p>Next, we will test the CoreDNS DNS resolution.<\/p>\n<p>To check if CoreDNS can resolve internal Kubernetes services, lets deploy a dnsutils pod.<\/p>\n<pre><code class=\"language-bash\">$ kubectl apply  -f https:\/\/raw.githubusercontent.com\/techiescamp\/cka-certification-guide\/refs\/heads\/main\/lab-setup\/manifests\/utilities\/dnsutils.yaml\n\n$ kubectl get pods dnsutils<\/code><\/pre>\n<p>Execute the following nslookup command.<\/p>\n<p>Expected output: Resolves to the cluster IP (<code>10.96.0.1<\/code>).<\/p>\n<pre><code class=\"language-bash\">kubectl exec -i -t dnsutils -- nslookup kubernetes.default\nServer:         10.96.0.10\nAddress:        10.96.0.10#53\n\nName:   kubernetes.default.svc.cluster.local\nAddress: 10.96.0.1<\/code><\/pre>\n<p>To check if the cluster can resolve external domains, execute:<\/p>\n<pre><code class=\"language-bash\">$ kubectl exec -i -t dnsutils -- nslookup google.com\n\nServer:         10.96.0.10\nAddress:        10.96.0.10#53\n\nName:   google.com.localdomain\nAddress: 142.250.195.238\nName:   google.com.localdomain\nAddress: 142.250.195.238<\/code><\/pre>\n<p>Cleanup dnutils pod.<\/p>\n<pre><code class=\"language-bash\">kubectl delete -f https:\/\/raw.githubusercontent.com\/techiescamp\/cka-certification-guide\/refs\/heads\/main\/lab-setup\/manifests\/utilities\/dnsutils.yaml<\/code><\/pre>\n<h2 id=\"possible-kubeadm-issues\">Possible Kubeadm Issues<\/h2>\n<p>The following are the possible issues you might encounter in the kubeadm setup.<\/p>\n<ol>\n<li><strong>Pod Out of memory and CPU:<\/strong> The master node should have a minimum of 2vCPU and 2 GB memory.<\/li>\n<li><strong>Nodes cannot connect to Master:<\/strong> Check the firewall between nodes and make sure all the nodes can talk to each other on the required kubernetes ports.<\/li>\n<li><strong>Calico Pod Restarts:<\/strong> Sometimes, if you use the same IP range for the node and pod network, Calico pods may not work as expected. So make sure the node and pod IP ranges don&#8217;t overlap. Overlapping <a href=\"https:\/\/devopscube.com\/ip-address-tutorial\/\">IP addresses<\/a> could result in issues for other applications running on the cluster as well.<\/li>\n<\/ol>\n<p>For other pod errors, check out the <a href=\"https:\/\/devopscube.com\/troubleshoot-kubernetes-pods\/\">kubernetes pod troubleshooting<\/a> guide.<\/p>\n<p>If your server doesn&#8217;t have a minimum of 2 vCPU, you will get the following error.<\/p>\n<pre><code>[ERROR NumCPU]: the number of available CPUs 1 is less than the required 2<\/code><\/pre>\n<p>If you use a public IP with<code> --apiserver-advertise-address<\/code> parameter, you will have failed master node components with the following error. To rectify this error, use <code>--control-plane-endpoint<\/code> parameter with the public IP address.<\/p>\n<pre><code>kubelet-check] Initial timeout of 40s passed.\n\n\nUnfortunately, an error has occurred:\n        timed out waiting for the condition\n\nThis error is likely caused by:\n        - The kubelet is not running\n        - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)\n\nIf you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:\n        - 'systemctl status kubelet'\n        - 'journalctl -xeu kubelet'<\/code><\/pre>\n<p>You will get the following error in worker nodes when you try to join a worker node with a new token after the master node reset. To rectify this error, reset the worker node using the command <code>kubeadm reset<\/code>.<\/p>\n<pre><code>[ERROR FileAvailable--etc-kubernetes-kubelet.conf]: \/etc\/kubernetes\/kubelet.conf already exists\n        [ERROR Port-10250]: Port 10250 is in use\n        [ERROR FileAvailable--etc-kubernetes-pki-ca.crt]: \/etc\/kubernetes\/pki\/ca.crt already exists<\/code><\/pre>\n<h2 id=\"kubernetes-cluster-important-configurations\">Kubernetes Cluster Important Configurations<\/h2>\n<p>Following are the important <a href=\"https:\/\/devopscube.com\/kubernetes-cluster-configurations\/\">Kubernetes cluster configurations<\/a> you should know.<\/p>\n<p><!--kg-card-begin: html--><\/p>\n<table class=\"has-fixed-layout\">\n<thead>\n<tr>\n<th>Configuration<\/th>\n<th>Location<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Static Pods Location (etcd, api-server, controller manager and scheduler)<\/td>\n<td>\/etc\/kubernetes\/manifests<\/td>\n<\/tr>\n<tr>\n<td>TLS Certificates location (kubernetes-ca,  etcd-ca and kubernetes-front-proxy-ca)<\/td>\n<td>\/etc\/kubernetes\/pki<\/td>\n<\/tr>\n<tr>\n<td>Admin Kubeconfig File<\/td>\n<td>\/etc\/kubernetes\/admin.conf<\/td>\n<\/tr>\n<tr>\n<td>Kubelet configuration<\/td>\n<td>\/var\/lib\/kubelet\/config.yaml<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p><!--kg-card-end: html--><\/p>\n<p>There are configurations that are part of Kubernetes feature gates. If you want to use the features that are part of feature gates, you need to enable them during the Kubeadm initialization using a kubeadm configuration file.<\/p>\n<p>You can refer to <a href=\"https:\/\/devopscube.com\/enable-feature-gates-kubeadm\/\">enabling feature gates in Kubeadm<\/a> blog to understand more.<\/p>\n<h2 id=\"upgrading-kubeadm-cluster\">Upgrading Kubeadm Cluster<\/h2>\n<p>Using Kubeadm, you can upgrade the kubernetes cluster to the same version patch or a new version.<\/p>\n<p>Kubeadm upgrade doesn&#8217;t introduce any downtime if you upgrade one node at a time.<\/p>\n<p>To do hands-on, please refer to my step-by-step guide on <a href=\"https:\/\/devopscube.com\/upgrade-kubernetes-cluster-kubeadm\/\">Kubeadm cluster upgrade<\/a><\/p>\n<h2 id=\"backing-up-etcd-data\">Backing Up ETCD Data<\/h2>\n<p>etcd backup is one the key task in real world projects and for CKA certification.<\/p>\n<p>You can follow the<a href=\"https:\/\/devopscube.com\/backup-etcd-restore-kubernetes\/\"> etcd backup guide<\/a> to learn how to perform etcd backup and restore.<\/p>\n<h2 id=\"setup-prometheus-monitoring\">Setup Prometheus Monitoring<\/h2>\n<p>As a next step, you can try setting up the Prometheus monitoring stack on the Kubeadm cluster.<\/p>\n<p>I have published a detailed guide for the setup. Refer to <a href=\"https:\/\/devopscube.com\/setup-prometheus-monitoring-on-kubernetes\/\">prometheus on Kubernetes<\/a> guide for step-by-step guides. The stack contains, prometheus, alert manager, kube state metrics and Grafana.<\/p>\n<h2 id=\"how-does-kubeadm-work\">How Does Kubeadm Work?<\/h2>\n<p>Here is how the Kubeadm setup works.<\/p>\n<p>When you initialize a Kubernetes cluster using Kubeadm, it does the following.<\/p>\n<ol>\n<li>When you initialize kubeadm, first it runs all the preflight checks to validate the system state and it downloads all the required cluster container images from the <strong>registry.k8s.io<\/strong> container registry.<\/li>\n<li>It then generates required TLS certificates and stores them in the <strong>\/etc\/kubernetes\/pki<\/strong> folder.<\/li>\n<li>Next, it generates all the kubeconfig files for the cluster components in the <strong>\/etc\/kubernetes<\/strong> folder.<\/li>\n<li>Then it starts the kubelet service generates the static pod manifests for all the cluster components and saves it in the <strong>\/etc\/kubernetes\/manifests<\/strong> folder.<\/li>\n<li>Next, it starts all the control plane components from the static pod manifests.<\/li>\n<li>Then it installs core DNS and Kubeproxy components<\/li>\n<li>Finally, it generates the node bootstrap token.<\/li>\n<li>Worker nodes use this token to join the control plane.<\/li>\n<\/ol>\n<figure class=\"kg-card kg-image-card\"><img decoding=\"async\" src=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/2025\/03\/image-7-36.png\" class=\"kg-image\" alt=\"Kubeadm Workflow\" loading=\"lazy\" width=\"2000\" height=\"1004\" srcset=\"https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w600\/2025\/03\/image-7-36.png 600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1000\/2025\/03\/image-7-36.png 1000w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w1600\/2025\/03\/image-7-36.png 1600w, https:\/\/storage.ghost.io\/c\/5f\/2f\/5f2f4d20-2abf-4534-8d40-7aa233aedd43\/content\/images\/size\/w2400\/2025\/03\/image-7-36.png 2400w\" sizes=\"auto, (min-width: 720px) 720px\"><\/figure>\n<p>As you can see all the key cluster configurations will be present under the \/etc\/kubernetes folder.<\/p>\n<h2 id=\"kubeadm-faqs\">Kubeadm FAQs<\/h2>\n<h3 id=\"how-to-use-custom-ca-certificates-with-kubeadm\">How to use Custom CA Certificates With Kubeadm?<\/h3>\n<p>By default, kubeadm creates its own CA certificates. However, if you wish to use custom CA certificates, they should be placed in the <strong><code>\/etc\/kubernetes\/pki<\/code><\/strong> folder. When kubeadm is run, it will make use of existing certificates if they are found, and will not overwrite them.<\/p>\n<h3 id=\"how-to-generate-the-kubeadm-join-command\">How to generate the Kubeadm Join command?<\/h3>\n<p>You can use <code>kubeadm token create --print-join-command<\/code> command to generate the join command.<\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>In this post, we learned to install Kubernetes step by step using kubeadm. <\/p>\n<p>As a <a href=\"https:\/\/devopscube.com\/become-devops-engineer\/\" rel=\"noreferrer noopener\">DevOps engineer<\/a>, it is good to have an understanding of the Kubernetes cluster components. With companies using <a href=\"https:\/\/devopscube.com\/docker-container-clustering-tools\/\" rel=\"noreferrer noopener\">managed Kubernetes services<\/a>, we miss learning the basic building blocks of kubernetes.<\/p>\n<p>This Kubeadm setup is good for learning and playing around with kubernetes.<\/p>\n<p>Also, there are many other Kubeadm configs that I did not cover in this guide as it is out of the scope of this guide. Please refer to the official <a href=\"https:\/\/kubernetes.io\/docs\/reference\/setup-tools\/kubeadm\/?ref=devopscube.com\" rel=\"noreferrer noopener\">Kubeadm documentation<\/a>. By having the whole cluster setup in VMs, you can learn all the cluster components configs and troubleshoot the cluster on component failures.<\/p>\n<p>Also, with Vagrant, you can create simple automation to bring up and tear down Kubernetes clusters on-demand in your local workstation. Check out my guide on <a href=\"https:\/\/devopscube.com\/kubernetes-cluster-vagrant\/\" rel=\"noreferrer noopener\">automated kubernetes vagrant setup using kubeadm.<\/a><\/p>\n<p>If you are learning kubernetes, check out the comprehensive <a href=\"https:\/\/devopscube.com\/kubernetes-tutorials-beginners\/\">Kubernetes tutorial for beginners<\/a>.<\/p>\n<hr>\n<p><strong>Ngu\u1ed3n:<\/strong> <a href=\"https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/\" target=\"_blank\" rel=\"noopener noreferrer\">How To Setup Kubernetes Cluster Using Kubeadm &#8211; Easy Guide \u2014 DevOpsCube<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Source: https:\/\/devopscube.com\/setup-kubernetes-cluster-kubeadm\/<\/p>\n","protected":false},"author":1,"featured_media":231,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-230","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devops"],"_links":{"self":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/230","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=230"}],"version-history":[{"count":0,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/posts\/230\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=\/wp\/v2\/media\/231"}],"wp:attachment":[{"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=230"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=230"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.ngocha.biz\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=230"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}