{"id":1703,"date":"2021-11-05T16:42:18","date_gmt":"2021-11-05T23:42:18","guid":{"rendered":"http:\/\/blog.nillsf.com\/?p=1703"},"modified":"2021-11-05T16:42:25","modified_gmt":"2021-11-05T23:42:25","slug":"creating-kubernetes-clusters-on-azure-using-cluster-api","status":"publish","type":"post","link":"https:\/\/nillsf.com\/index.php\/2021\/11\/05\/creating-kubernetes-clusters-on-azure-using-cluster-api\/","title":{"rendered":"Creating Kubernetes clusters on Azure using cluster API"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Last week, I wrote a post about how to create a Kubernetes cluster on Azure using kubeadm. It wasn&#8217;t as hard as Kubernetes the hard way, but it took some time to get everything setup and get all the infrastructure spun up.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The goal of this post is to show an easier path to get a non-AKS cluster running on Azure using the <a href=\"https:\/\/cluster-api.sigs.k8s.io\/\">cluster API project<\/a>.  The goal of the cluster API project is to provide a declarative API and tooling to create and manage Kubernetes clusters on different providers. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;ll be using the <a href=\"https:\/\/capz.sigs.k8s.io\/#what-is-the-cluster-api-provider-azure\">Cluster API Provider Azure<\/a> for the purpose of this post. This is an implementation of Cluster API for Azure, that spins up infrastructure on Azure to create that cluster.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s start!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Chicken and the egg: Using Kubernetes to create more Kubernetes<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Cluster API project leverages the Kubernetes API to create new Kubernetes clusters. This essentially means that you need an existing Kubernetes cluster to run the Cluster API. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To that purpose, I spun up a minikube cluster on my local laptop. This is a M1 MacBook pro, and I was very positively surprised that spinning up a minikube cluster worked from the get-go without any issues. <\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>brew install minikube\nminikube start\nkubectl get nodes<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This gave me a local Kubernetes cluster to work with. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"802\" height=\"92\" src=\"\/wp-content\/uploads\/2021\/11\/image.png\" alt=\"\" class=\"wp-image-1704\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image.png 802w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-300x34.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-768x88.png 768w\" sizes=\"auto, (max-width: 802px) 100vw, 802px\" \/><figcaption>Minikube up and running<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You can use any Kubernetes cluster to run the cluster API project. In production use cases, I wouldn&#8217;t use minikube for creating your clusters; but would rely on a production Kubernetes cluster (for example an AKS cluster).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Installing cluster API tooling<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Next up, you need the cluster API command line tool. Make sure to check out the <a href=\"https:\/\/cluster-api.sigs.k8s.io\/user\/quick-start.html\">cluster API documentation<\/a> so you can download the right tool for your environment (which in my case is M1 Mac):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>curl -L https:\/\/github.com\/kubernetes-sigs\/cluster-api\/releases\/download\/v1.0.0\/clusterctl-darwin-arm64 -o clusterctl\nchmod +x .\/clusterctl\nsudo mv .\/clusterctl \/usr\/local\/bin\/clusterctl\nclusterctl version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Which also worked great <em>(this is getting suspicious, isnt it?)<\/em>:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"325\" src=\"\/wp-content\/uploads\/2021\/11\/image-1-1024x325.png\" alt=\"\" class=\"wp-image-1705\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-1-1024x325.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-1-300x95.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-1-768x244.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-1.png 1248w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>clusterctl tooling available on my workstation<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And with that done, we can now go ahead and bootstrap cluster API for Azure on minikube:<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Setting up cluster API for Azure <\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">To setup cluster API for Azure, you need a service principal. The secret of this service principal will be stored in a Kubernetes secret. To do this, you can use the following commands:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export AZURE_SUBSCRIPTION_ID=$(az account show | jq .id | tr -d '\"')\n\n# Create an Azure Service Principal\nexport SERVICE_PRINCIPAL=$(az ad sp create-for-rbac --name nfclusterapiblog --role Contributor -o json)\nexport AZURE_TENANT_ID=$(echo $SERVICE_PRINCIPAL | jq .tenant | tr -d '\"')\nexport AZURE_CLIENT_ID=$(echo $SERVICE_PRINCIPAL | jq .appId | tr -d '\"')\nexport AZURE_CLIENT_SECRET=$(echo $SERVICE_PRINCIPAL | jq .password | tr -d '\"')\n\n# Base64 encode the variables\nexport AZURE_SUBSCRIPTION_ID_B64=\"$(echo -n \"$AZURE_SUBSCRIPTION_ID\" | base64 | tr -d '\\n')\"\nexport AZURE_TENANT_ID_B64=\"$(echo -n \"$AZURE_TENANT_ID\" | base64 | tr -d '\\n')\"\nexport AZURE_CLIENT_ID_B64=\"$(echo -n \"$AZURE_CLIENT_ID\" | base64 | tr -d '\\n')\"\nexport AZURE_CLIENT_SECRET_B64=\"$(echo -n \"$AZURE_CLIENT_SECRET\" | base64 | tr -d '\\n')\"\n\n# Settings needed for AzureClusterIdentity used by the AzureCluster\nexport AZURE_CLUSTER_IDENTITY_SECRET_NAME=\"cluster-identity-secret\"\nexport CLUSTER_IDENTITY_NAME=\"cluster-identity\"\nexport AZURE_CLUSTER_IDENTITY_SECRET_NAMESPACE=\"default\"\n\n# Create a secret to include the password of the Service Principal identity created in Azure\n# This secret will be referenced by the AzureClusterIdentity used by the AzureCluster\nkubectl create secret generic \"${AZURE_CLUSTER_IDENTITY_SECRET_NAME}\" --from-literal=clientSecret=\"${AZURE_CLIENT_SECRET}\"\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And then finally, you can setup cluster API (for Azure) on the cluster:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>clusterctl init --infrastructure azure<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Which will setup cluster API on the minikube cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"273\" src=\"\/wp-content\/uploads\/2021\/11\/image-2-1024x273.png\" alt=\"\" class=\"wp-image-1706\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-2-1024x273.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-2-300x80.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-2-768x204.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-2-1536x409.png 1536w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-2.png 1600w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Cluster API being setup on the minikube cluster<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Creating a Kubernetes cluster using Cluster API<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">We are now ready to create a new cluster. First, we&#8217;ll setup some Azure environment variables:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>export AZURE_LOCATION=\"westus2\"\nexport AZURE_RESOURCE_GROUP=\"capz-trial\"\n\n# Select VM types.\nexport AZURE_CONTROL_PLANE_MACHINE_TYPE=\"Standard_D2s_v4\"\nexport AZURE_NODE_MACHINE_TYPE=\"Standard_D2s_v4\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And then we can create the cluster definition:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>clusterctl generate cluster capz-trial \\\n  --kubernetes-version v1.21.0 \\\n  --control-plane-machine-count=3 \\\n  --worker-machine-count=3 \\\n  > capz-trial.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This creates a little over 200 lines of YAML, <a href=\"https:\/\/gist.github.com\/NillsF\/da435f522183d9a52a1929d92065df8a\">which you can find here.<\/a><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">We can now create this cluster using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl apply -f capz-trial.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Which will create the necessary objects in our minikube cluster, to create an Azure cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"215\" src=\"\/wp-content\/uploads\/2021\/11\/image-3-1024x215.png\" alt=\"\" class=\"wp-image-1707\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-3-1024x215.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-3-300x63.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-3-768x162.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-3.png 1236w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Creating the Kubernetes cluster on Azure<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">I then monitored the cluster using kubectl, and apparently after 70 seconds it was provisioned. I had stepped away, assuming it would take a couple minutes so can&#8217;t confirm this cluster was operational after 70 seconds. If so, that would be FAST.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"746\" height=\"200\" src=\"\/wp-content\/uploads\/2021\/11\/image-4.png\" alt=\"\" class=\"wp-image-1708\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-4.png 746w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-4-300x80.png 300w\" sizes=\"auto, (max-width: 746px) 100vw, 746px\" \/><figcaption>Cluster was provisioned after 70 seconds. That was quick.<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">We can get the kubeconfig to interact with this cluster using:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>clusterctl get kubeconfig capz-trial > capz-trial.kubeconfig<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And then do for instance a get nodes:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"204\" src=\"\/wp-content\/uploads\/2021\/11\/image-5-1024x204.png\" alt=\"\" class=\"wp-image-1709\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-5-1024x204.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-5-300x60.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-5-768x153.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-5.png 1282w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Using the kubeconfig to get the nodes<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The nodes aren&#8217;t ready yet, because there&#8217;s no CNI setup yet. We can setup a CNI now, and the CAPI project defaults to Calico, which we&#8217;ll setup (if you&#8217;re following the CAPI docs, please ensure to install the Azure calico CNI, because the default doesn&#8217;t work):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl --kubeconfig=.\/capz-trial.kubeconfig \\\n  apply -f https:\/\/raw.githubusercontent.com\/kubernetes-sigs\/cluster-api-provider-azure\/main\/templates\/addons\/calico.yaml<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">After that is applied, the nodes should be ready:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"205\" src=\"\/wp-content\/uploads\/2021\/11\/image-6-1024x205.png\" alt=\"\" class=\"wp-image-1710\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-6-1024x205.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-6-300x60.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-6-768x154.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-6.png 1170w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>With CNI setup, the nodes are ready<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And we can now create a sample application, to verify everything is working. I like to use Azure-vote for this purpose. Given CAPZ sets up the Azure load balancer integration, we can use the public version of azure-vote, including an Azure load balancer with public IP:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl apply -f https:\/\/raw.githubusercontent.com\/Azure-Samples\/azure-voting-app-redis\/master\/azure-vote-all-in-one-redis.yaml --kubeconfig=.\/capz-trial.kubeconfig <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This will create the workload and a service. To get the service details, you can use:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl get svc -w --kubeconfig=.\/capz-trial.kubeconfig <\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Which will return  you the service&#8217;s public IP:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"168\" src=\"\/wp-content\/uploads\/2021\/11\/image-7-1024x168.png\" alt=\"\" class=\"wp-image-1711\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-7-1024x168.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-7-300x49.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-7-768x126.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-7.png 1242w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Getting the Azure-vote public IP<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And by browsing to that public IP, you should see the Azure vote application:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"782\" src=\"\/wp-content\/uploads\/2021\/11\/image-8-1024x782.png\" alt=\"\" class=\"wp-image-1712\" srcset=\"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-8-1024x782.png 1024w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-8-300x229.png 300w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-8-768x587.png 768w, https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/image-8.png 1136w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption>Azure vote running on a CAPZ cluster<\/figcaption><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">And that&#8217;s how you create a CAPZ cluster!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s also clean up after ourselves. We can do this with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>kubectl delete cluster capz-trial<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">And we can also delete the minikube cluster:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>minikube delete<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In this post we explored creating a non-AKS Kubernetes cluster on Azure using the cluster API project. In my opinion, it&#8217;s a lot easier creating a cluster with cluster API than it is with kubeadm. Less infrastructure to setup, and less interactions with individual machines. Also, cluster API is available for a multitude of infrastructure providers, meaning you can get consistent tooling across cloud providers.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The downside of using cluster API is that you need an existing Kubernetes API to deploy the infrastructure with. Minikube and kind can solve this for my demo use cases, but I wouldn&#8217;t use that in production.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">One very interesting pattern that cluster API unlocks in integrating your Kubernetes cluster definition (i.e. Infrastructure-as-code) with GitOps. This is something I&#8217;m exploring at the moment, and will very likely write about soon. Stay tuned!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Last week, I wrote a post about how to create a Kubernetes cluster on Azure using kubeadm. It wasn&#8217;t as hard as Kubernetes the hard way, but it took some time to get everything setup and get all the infrastructure spun up. The goal of this post is to show an easier path to get [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":1714,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[2,58,5],"tags":[189,18],"class_list":["post-1703","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure","category-kubernetes","category-open-source","tag-capz","tag-kubernetes"],"jetpack_featured_media_url":"https:\/\/nillsfblog.blob.core.windows.net\/media\/2021\/11\/Screen-Shot-2021-11-05-at-4.39.03-PM.png","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1703","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/comments?post=1703"}],"version-history":[{"count":1,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1703\/revisions"}],"predecessor-version":[{"id":1713,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/posts\/1703\/revisions\/1713"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/media\/1714"}],"wp:attachment":[{"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/media?parent=1703"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/categories?post=1703"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/nillsf.com\/index.php\/wp-json\/wp\/v2\/tags?post=1703"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}