{"id":1689,"date":"2021-10-29T14:56:44","date_gmt":"2021-10-29T21:56:44","guid":{"rendered":"http:\/\/blog.nillsf.com\/?p=1689"},"modified":"2021-10-29T14:56:50","modified_gmt":"2021-10-29T21:56:50","slug":"setting-up-kubernetes-on-azure-using-kubeadm","status":"publish","type":"post","link":"https:\/\/blog.nillsf.com\/index.php\/2021\/10\/29\/setting-up-kubernetes-on-azure-using-kubeadm\/","title":{"rendered":"Setting up Kubernetes on Azure using kubeadm"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I&#8217;m studying to renew my CKA certification. Part of the CKA certification learning goals is setting up and managing a cluster with kubeadm. The goal of this post is to share how to setup a cluster with kubeadm using VMs running on Azure. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The steps that are required here are:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Setup Azure infrastructure<\/li><li>Setup first control node<\/li><li>Setup second control node<\/li><li>Setup worker nodes<\/li><li>Run a small workload<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Credit where credit is due, most of the steps here are taken from the Kubernetes documentation pages:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li><a href=\"https:\/\/kubernetes.io\/docs\/setup\/production-environment\/tools\/kubeadm\/install-kubeadm\/\">Installing kubeadm | Kubernetes<\/a><\/li><li><a href=\"https:\/\/kubernetes.io\/docs\/setup\/production-environment\/tools\/kubeadm\/high-availability\/\">Creating Highly Available clusters with kubeadm | Kubernetes<\/a><\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">With that, let&#8217;s get going:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up the required Azure infrastructure<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We need a bit of Azure infrastructure to build a highly available Kubernetes cluster on Azure. What we&#8217;ll create:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>VNET<\/li><li>Network Security Group<\/li><li>2 master VMs<\/li><li>2 worker VMs<\/li><li>A load balancer for the master VMs<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s create all of this using the Azure CLI:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First up, all the networking elements (VNET and NSG)<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az group create -n kubeadm -l westus2\n\naz network vnet create \\\n    --resource-group kubeadm \\\n    --name kubeadm \\\n    --address-prefix 192.168.0.0\/16 \\\n    --subnet-name kube \\\n    --subnet-prefix 192.168.0.0\/16\n\naz network nsg create \\\n    --resource-group kubeadm \\\n    --name kubeadm\n\naz network nsg rule create \\\n    --resource-group kubeadm \\\n    --nsg-name kubeadm \\\n    --name kubeadmssh \\\n    --protocol tcp \\\n    --priority 1000 \\\n    --destination-port-range 22 \\\n    --access allow\n\naz network nsg rule create \\\n    --resource-group kubeadm \\\n    --nsg-name kubeadm \\\n    --name kubeadmWeb \\\n    --protocol tcp \\\n    --priority 1001 \\\n    --destination-port-range 6443 \\\n    --access allow\n\naz network vnet subnet update \\\n    -g kubeadm \\\n    -n kube \\\n    --vnet-name kubeadm \\\n    --network-security-group kubeadm<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Then we can add the 4 virtual machines:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az vm create -n kube-master-1 -g kubeadm \\\n--image UbuntuLTS \\\n--vnet-name kubeadm --subnet kube \\\n--admin-username nilfranadmin \\\n--ssh-key-value @~\/.ssh\/id_rsa.pub \\\n--size Standard_D2ds_v4 \\\n--nsg kubeadm \\\n--public-ip-sku Standard --no-wait\n\naz vm create -n kube-master-2 -g kubeadm \\\n--image UbuntuLTS \\\n--vnet-name kubeadm --subnet kube \\\n--admin-username nilfranadmin \\\n--ssh-key-value @~\/.ssh\/id_rsa.pub \\\n--size Standard_D2ds_v4 \\\n--nsg kubeadm \\\n--public-ip-sku Standard --no-wait\n\naz vm create -n kube-worker-1 -g kubeadm \\\n--image UbuntuLTS \\\n--vnet-name kubeadm --subnet kube \\\n--admin-username nilfranadmin \\\n--ssh-key-value @~\/.ssh\/id_rsa.pub \\\n--size Standard_D2ds_v4 \\\n--nsg kubeadm \\\n--public-ip-sku Standard --no-wait\n\naz vm create -n kube-worker-2 -g kubeadm \\\n--image UbuntuLTS \\\n--vnet-name kubeadm --subnet kube \\\n--admin-username nilfranadmin \\\n--ssh-key-value @~\/.ssh\/id_rsa.pub \\\n--size Standard_D2ds_v4 \\\n--nsg kubeadm \\\n--public-ip-sku Standard<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And then finally, we can create the load balancer:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\naz network public-ip create \\\n    --resource-group kubeadm \\\n    --name controlplaneip \\\n    --sku Standard \\\n    --dns-name nilfrankubeadm\n\n az network lb create \\\n    --resource-group kubeadm \\\n    --name kubemaster \\\n    --sku Standard \\\n    --public-ip-address controlplaneip \\\n    --frontend-ip-name controlplaneip \\\n    --backend-pool-name masternodes     \n\naz network lb probe create \\\n    --resource-group kubeadm \\\n    --lb-name kubemaster \\\n    --name kubemasterweb \\\n    --protocol tcp \\\n    --port 6443   \n\naz network lb rule create \\\n    --resource-group kubeadm \\\n    --lb-name kubemaster \\\n    --name kubemaster \\\n    --protocol tcp \\\n    --frontend-port 6443 \\\n    --backend-port 6443 \\\n    --frontend-ip-name controlplaneip \\\n    --backend-pool-name masternodes \\\n    --probe-name kubemasterweb \\\n    --disable-outbound-snat true \\\n    --idle-timeout 15 \\\n    --enable-tcp-reset true\n\naz network nic ip-config address-pool add \\\n    --address-pool masternodes \\\n    --ip-config-name ipconfigkube-master-1 \\\n    --nic-name kube-master-1VMNic \\\n    --resource-group kubeadm \\\n    --lb-name kubemaster\n\naz network nic ip-config address-pool add \\\n    --address-pool masternodes \\\n    --ip-config-name ipconfigkube-master-2 \\\n    --nic-name kube-master-2VMNic \\\n    --resource-group kubeadm \\\n    --lb-name kubemaster<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And then, let&#8217;s get the public IP of all the nodes so we can connect to them and set up what&#8217;s needed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>MASTER1IP=`az vm list-ip-addresses -g kubeadm -n kube-master-1 \\\n--query \"&#91;].virtualMachine.network.publicIpAddresses&#91;0].ipAddress\" --output tsv`\nMASTER2IP=`az vm list-ip-addresses -g kubeadm -n kube-master-2 \\\n--query \"&#91;].virtualMachine.network.publicIpAddresses&#91;0].ipAddress\" --output tsv`\nWORKER1IP=`az vm list-ip-addresses -g kubeadm -n kube-worker-1 \\\n--query \"&#91;].virtualMachine.network.publicIpAddresses&#91;0].ipAddress\" --output tsv`\nWORKER2IP=`az vm list-ip-addresses -g kubeadm -n kube-worker-2 \\\n--query \"&#91;].virtualMachine.network.publicIpAddresses&#91;0].ipAddress\" --output tsv`<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And now, we can setup the first master node:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setup first master node<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now we can start setting up the first master node. Much of the configuration for the first master node will be repeated on the other nodes; as most of this is laying the plumbing for Kubernetes to work. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We need to do a couple things:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Install the right tools, like kubeadm, kubectl and kubelet.<\/li><li>Configure networking, container runtime.<\/li><li>Use kubeadm to setup node.<\/li><li>Setup a CNI.<\/li><\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s start with the first step:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh nilfranadmin@$MASTER1IP\n\nsudo apt update\nsudo apt -y install curl apt-transport-https;\n\ncurl -s https:\/\/packages.cloud.google.com\/apt\/doc\/apt-key.gpg | sudo apt-key add -\necho \"deb https:\/\/apt.kubernetes.io\/ kubernetes-xenial main\" | sudo tee \/etc\/apt\/sources.list.d\/kubernetes.list\n\nsudo apt update\nsudo apt -y install vim git curl wget kubelet kubeadm kubectl containerd;\n\nsudo apt-mark hold kubelet kubeadm kubectl\n\nkubectl version --client &amp;&amp; kubeadm version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That final command should confirm kubectl and kubeadm are installed and return something similar to the following screenshot:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"166\" src=\"\/wp-content\/uploads\/2021\/10\/image-1024x166.png\" alt=\"\" class=\"wp-image-1692\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-1024x166.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-300x49.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-768x124.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image.png 1286w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Output showing kubectl and kubeadm successfully installed<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Next up, let&#8217;s configure networking and containerd on the node:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cat &lt;&lt;EOF | sudo tee \/etc\/modules-load.d\/containerd.conf\noverlay\nbr_netfilter\nEOF\n\nsudo modprobe overlay\nsudo modprobe br_netfilter\n\n# Setup required sysctl params, these persist across reboots.\ncat &lt;&lt;EOF | sudo tee \/etc\/sysctl.d\/99-kubernetes-cri.conf\nnet.bridge.bridge-nf-call-iptables  = 1\nnet.ipv4.ip_forward                 = 1\nnet.bridge.bridge-nf-call-ip6tables = 1\nEOF\n\n# Apply sysctl params without reboot\nsudo sysctl --system\n\nsudo mkdir -p \/etc\/containerd\ncontainerd config default | sudo tee \/etc\/containerd\/config.toml\n\nsudo systemctl restart containerd\n\ncat &lt;&lt;EOF | sudo tee \/etc\/modules-load.d\/k8s.conf\nbr_netfilter\nEOF\n\ncat &lt;&lt;EOF | sudo tee \/etc\/sysctl.d\/k8s.conf\nnet.bridge.bridge-nf-call-ip6tables = 1\nnet.bridge.bridge-nf-call-iptables = 1\nEOF\nsudo sysctl --system<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And with the node prepared, we can setup Kubernetes using kubeadm. Although all the steps to this point seemed like a ton of work; setting up Kubernetes using kubeadm is a single command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo kubeadm init --control-plane-endpoint \"nilfrankubeadm.westus2.cloudapp.azure.com:6443\" --upload-certs<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The output of this command will show you three things:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Where to get the kubeconfig to configure kubectl to work with this cluster.<\/li><li>How to join additional control plane nodes<\/li><li>How to join worker nodes<\/li><\/ol>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"556\" src=\"\/wp-content\/uploads\/2021\/10\/image-1-1024x556.png\" alt=\"\" class=\"wp-image-1693\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-1-1024x556.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-1-300x163.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-1-768x417.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-1-1536x833.png 1536w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-1.png 1596w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Instructions showing you how to interact with the cluster.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Make sure to copy the two kubeadm join commands, as we&#8217;ll need these to bootstrap the other nodes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s do the first command shown in the screenshot above to make the kubeconfig file available:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>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\n\n\n<p class=\"wp-block-paragraph\">And then we can interact with the cluster, and e.g. execute a get nodes:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"932\" height=\"92\" src=\"\/wp-content\/uploads\/2021\/10\/image-2.png\" alt=\"\" class=\"wp-image-1694\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-2.png 932w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-2-300x30.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-2-768x76.png 768w\" sizes=\"auto, (max-width: 932px) 100vw, 932px\" \/><figcaption>After copying the kubeconfig file, we can use kubectl to see the node<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The node shows not ready, because a CNI wasn&#8217;t setup yet. We can do this using the following command:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl apply -f \"https:\/\/cloud.weave.works\/k8s\/net?k8s-version=$(kubectl version | base64 | tr -d '\\n')\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And if you wait a couple of seconds, you will see the node transition into a ready state:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"928\" height=\"148\" src=\"\/wp-content\/uploads\/2021\/10\/image-3.png\" alt=\"\" class=\"wp-image-1695\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-3.png 928w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-3-300x48.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-3-768x122.png 768w\" sizes=\"auto, (max-width: 928px) 100vw, 928px\" \/><figcaption>After setting up the CNI, we can see the node get into a ready state.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Now that that&#8217;s done, we can exit out of this node since we need to configure the other nodes now.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up the second control node<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Now we have to prepare the second control node. Here, we&#8217;ll do a couple things:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>Get the kubeconfig from the master node locally, so we can use kubectl from our local machine (optional)<\/li><li>Setup the node (like we did with the first control node)<\/li><li>Join using kubeadm<\/li><\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">For step 2, we won&#8217;t use SSH and execute the commands; we&#8217;ll rather execute the <a href=\"https:\/\/github.com\/NillsF\/blog\/blob\/master\/kubeadm\/prep-node.sh\">prep-node.sh<\/a> script from my GitHub repo. Copy it locally if you are following along.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To get kubeconfig locally, you can use the following command. This will not overwrite the .config in your ~\/.kube folder, as this might hold more permanent clusters.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>scp nilfranadmin@$MASTER1IP:\/home\/nilfranadmin\/.kube\/config .config\nexport KUBECONFIG=`pwd`\/.config<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Verify that this works by doing kubectl get nodes:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"113\" src=\"\/wp-content\/uploads\/2021\/10\/image-4-1024x113.png\" alt=\"\" class=\"wp-image-1696\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-4-1024x113.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-4-300x33.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-4-768x85.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-4.png 1534w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Configuring kubeconfig on my local machine<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Next, we&#8217;ll setup the second control plane node. We can do this using the following commands. The second command will not be exactly the same in your situation, since you will have different certs. This command was copy-pasted from the kubeadm init step on the first control node.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh nilfranadmin@$MASTER2IP -o \"StrictHostKeyChecking no\" 'bash -s' &lt; prep-node.sh\n\nssh nilfranadmin@$MASTER2IP -o \"StrictHostKeyChecking no\" 'sudo kubeadm join nilfrankubeadm.westus2.cloudapp.azure.com:6443 --token tqczj3.65y68j4nx5wggtvz \\\n        --discovery-token-ca-cert-hash sha256:2dddb301ba8b413138dd42a46d5a6e49bf665a6ab90ed4b3646d50804ef2d719 \\\n        --control-plane --certificate-key f8af04b6d58e0013d4cac3a97ac2275a693798f3d5f1f51973bdb2c9fadd3247'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This will join the second control node to the cluster. If you check the nodes, you&#8217;ll see this node join the cluster and get ready once the CNI is setup:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"918\" height=\"242\" src=\"\/wp-content\/uploads\/2021\/10\/image-5.png\" alt=\"\" class=\"wp-image-1697\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-5.png 918w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-5-300x79.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-5-768x202.png 768w\" sizes=\"auto, (max-width: 918px) 100vw, 918px\" \/><figcaption>Second control node joining the cluster<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And now we&#8217;re ready for the worker nodes:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up the worker nodes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Setting up the worker nodes is very similar to setting up the second control plane node. The only difference is the command to join the cluster is slightly different, just make sure to copy-paste the right kubeadm output command.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>ssh nilfranadmin@$WORKER1IP -o \"StrictHostKeyChecking no\" 'bash -s' &lt; prep-node.sh\nssh nilfranadmin@$WORKER2IP -o \"StrictHostKeyChecking no\" 'bash -s' &lt; prep-node.sh\n\nssh nilfranadmin@$WORKER1IP -o \"StrictHostKeyChecking no\" 'sudo kubeadm join nilfrankubeadm.westus2.cloudapp.azure.com:6443 --token tqczj3.65y68j4nx5wggtvz \\\n    --discovery-token-ca-cert-hash sha256:2dddb301ba8b413138dd42a46d5a6e49bf665a6ab90ed4b3646d50804ef2d719 '\n\nssh nilfranadmin@$WORKER2IP -o \"StrictHostKeyChecking no\" 'sudo kubeadm join nilfrankubeadm.westus2.cloudapp.azure.com:6443 --token tqczj3.65y68j4nx5wggtvz \\\n    --discovery-token-ca-cert-hash sha256:2dddb301ba8b413138dd42a46d5a6e49bf665a6ab90ed4b3646d50804ef2d719 '<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once these commands have successfully executed, you should be able to watch those worker nodes join your cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"936\" height=\"330\" src=\"\/wp-content\/uploads\/2021\/10\/image-6.png\" alt=\"\" class=\"wp-image-1698\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-6.png 936w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-6-300x106.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-6-768x271.png 768w\" sizes=\"auto, (max-width: 936px) 100vw, 936px\" \/><figcaption>Worker nodes joining the cluster.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s verify everything works fine my running a small sample workload on the cluster:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Running a small app on the cluster<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To verify everything works OK, let&#8217;s run a small little sample app on the cluster. In my case, I like to use the azure-vote application for this purpose. However, the default azure-vote uses a service of type LoadBalancer, which this cluster isn&#8217;t setup for yet. I changed this service type to type NodePort in this <a href=\"https:\/\/github.com\/NillsF\/blog\/blob\/master\/kubeadm\/azure-vote.yml\">snippet on GitHub<\/a>, which you can use as well to create the app:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl create -f https:\/\/raw.githubusercontent.com\/NillsF\/blog\/master\/kubeadm\/azure-vote.yml\nkubectl port-forward service\/azure-vote-front 8080:80<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Give it a couple seconds for the pods to start, and then connect to localhost:8080 in your browser and you&#8217;ll see the azure-vote application running on your kubernetes cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"780\" src=\"\/wp-content\/uploads\/2021\/10\/image-7-1024x780.png\" alt=\"\" class=\"wp-image-1699\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-7-1024x780.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-7-300x228.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-7-768x585.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/image-7.png 1098w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Azure-vote running on a kubeadm based Kubernetes cluster<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Clean-up<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To clean up our deployment, we can simply delete the kubeadm resource group we created. This will take care of deleting all the resources:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>az group delete -n kubeadm --yes<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this post we explored creating a Kubernetes cluster using kubeadm. Although kubeadm itself makes it super easy to spin up a cluster, there&#8217;s a number of things we have to make a cluster work. You have to create the infrastructure for the cluster, setup the nodes correctly and only then can you run kubeadm. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Going through this work has increased my appreciation for the work done by managed Kubernetes versions (EKS\/GKE\/AKS) or the cluster-api project that make it a lot easier to spin up a cluster.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m studying to renew my CKA certification. Part of the CKA certification learning goals is setting up and managing a cluster with kubeadm. The goal of this post is to share how to setup a cluster with kubeadm using VMs running on Azure. The steps that are required here are: Setup Azure infrastructure Setup first [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1701,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[2,48,58,5,1],"tags":[8,187,18],"class_list":["post-1689","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-certification","category-kubernetes","category-open-source","category-uncategorized","tag-azure","tag-kubeadm","tag-kubernetes"],"jetpack_featured_media_url":"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/10\/Screen-Shot-2021-10-29-at-2.55.32-PM.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1689","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/comments?post=1689"}],"version-history":[{"count":4,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1689\/revisions"}],"predecessor-version":[{"id":1702,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1689\/revisions\/1702"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/media\/1701"}],"wp:attachment":[{"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/media?parent=1689"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/categories?post=1689"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.nillsf.com\/index.php\/wp-json\/wp\/v2\/tags?post=1689"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}