<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:hashnode="https://hashnode.com/rss"><channel><title><![CDATA[devOops.blog]]></title><description><![CDATA[devOops.blog]]></description><link>https://devoops.blog</link><image><url>https://cdn.hashnode.com/res/hashnode/image/upload/v1662454893654/IUfr1ZKF8.png</url><title>devOops.blog</title><link>https://devoops.blog</link></image><generator>RSS for Node</generator><lastBuildDate>Tue, 03 Dec 2024 07:21:07 GMT</lastBuildDate><atom:link href="https://devoops.blog/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><atom:link rel="next" href="https://devoops.blog/rss.xml?page=2"/><atom:link rel="previous" href="https://devoops.blog/rss.xml"/><item><title><![CDATA[Exposing an app on GKE using Ingress and TLS Termination]]></title><description><![CDATA[I recently needed to expose an application that runs on GKE to the outside world. To do that, I had to learn and use GKE's way of working with Ingress and Load Balancers. Although it's not too complicated (compared to other Ingress implementations), ...]]></description><link>https://devoops.blog/exposing-an-app-on-gke-using-ingress-and-tls-termination</link><guid isPermaLink="true">https://devoops.blog/exposing-an-app-on-gke-using-ingress-and-tls-termination</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[gke]]></category><category><![CDATA[ingress]]></category><category><![CDATA[Load Balancing]]></category><category><![CDATA[GCP]]></category><dc:creator><![CDATA[Shay Ulmer]]></dc:creator><pubDate>Tue, 17 Jan 2023 09:24:53 GMT</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;I recently needed to expose an application that runs on GKE to the outside world. To do that, I had to learn and use GKE&apos;s way of working with Ingress and Load Balancers. Although it&apos;s not too complicated (compared to other Ingress implementations), there are some technicalities that we need to resolve first.&lt;/p&gt;&lt;h1 id=&quot;heading-overview&quot;&gt;Overview&lt;/h1&gt;&lt;p&gt;Today we will expose &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/loadbalancer-tester&quot;&gt;a simple &quot;hello world&quot; app&lt;/a&gt; (&lt;a target=&quot;_blank&quot; href=&quot;https://hub.docker.com/r/shayulmer/loadbalancer-tester&quot;&gt;shayulmer/loadbalancer-tester&lt;/a&gt; container image) to the world, using TLS on GKE. This app serves as a web server that prints out the hostname of the running container, so we would be able to see the GCP Load Balancer in action.&lt;/p&gt;&lt;p&gt;The application itself runs on HTTP, but we will expose it over HTTPS using a GCP Managed Certificate and TLS Termination on our Ingress Load Balancer. We will also use Google Cloud Armor to limit traffic to the Load Balancer.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1673872422573/79297c58-5e41-42c2-9bfd-d99581cdba82.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;h1 id=&quot;heading-prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;A running GKE Cluster&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Basic Kubernetes knowledge - understanding of Deployments, Services, Ingress.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;gcloud CLI configured and logged in to the relevant GCP Project.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h1 id=&quot;heading-the-helm-chart&quot;&gt;The Helm Chart&lt;/h1&gt;&lt;p&gt;The following repository contains a helm chart for today&apos;s experiment: &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/gke-ingress-example&quot;&gt;https://github.com/shay-ul/gke-ingress-example&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Note: If you are going to deploy this helm chart on your environment, make sure to override the values.yaml file with your specific environment&apos;s details.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Since we want to see what&apos;s being deployed to GKE, we will not go over the helm chart itself. Instead, we will discuss the Kubernetes Manifests which are being generated from this chart. We will generate the manifests using this command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;helm template hello-world . -f values.yaml&lt;/code&gt;&lt;/p&gt;&lt;h1 id=&quot;heading-step-1-prepare-the-gcp-infrastructure&quot;&gt;Step 1: Prepare the GCP Infrastructure&lt;/h1&gt;&lt;p&gt;Since we want to issue a public Google-managed certificate, we have to use an external HTTP(S) Load Balancer&lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/load-balancing/docs/ssl-certificates#certificate-types&quot;&gt;[1]&lt;/a&gt;. When working with External Load Balancers, we have to consider two things:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Public Static IP&lt;/strong&gt; - External GCP Load balancers (unlike their AWS counterparts) will not receive a dynamic DNS that points to a dynamically allocated IP address&lt;a target=&quot;_blank&quot; href=&quot;https://stackoverflow.com/questions/63650088/getting-dns-for-load-balancer-in-gcp&quot;&gt;[2]&lt;/a&gt;. Instead, you have to manually point a DNS Record to the IP address assigned to the Load Balancer. To verify the IP address will never be changed (and the DNS will always point to the correct address), we have to request a Global Static Public IP. This address will be attached to our Load Balancer.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manage IP Whitelist&lt;/strong&gt; - Our application will be widely accessible over the internet unless we limit who can access our application. Since this is just a test run, we will utilize Google&apos;s Cloud Armor to block traffic which is not originated from IP addresses we specifically allow.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;details&gt;&lt;summary&gt;Step 1.a - Request a Global Static IP Address&lt;/summary&gt;&lt;br /&gt;&lt;p&gt;To do this, we will run the following command:&lt;br /&gt;&lt;code&gt;gcloud compute addresses create gke-ingress-example-address --global&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;After the request is completed, we can observe the allocated IP using this command:&lt;br /&gt;&lt;code&gt;gcloud compute addresses describe gke-ingress-example-address --global&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Register the address shown since you will need it later for the DNS record settings.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;/details&gt;&lt;details&gt;&lt;summary&gt;Step 1.b - Configure Cloud Armor Policy&lt;/summary&gt;Here we will create a new network security policy:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gcloud compute security-policies create gke-ingress-example-security-policy`&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Next we will create a default rule to block all traffic, with the lowest priority allowed (2147483647):&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gcloud compute security-policies rules update 2147483647 --security-policy gke-ingress-example-security-policy --action &quot;deny-502&quot;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And finally, we will add our &quot;Allow&quot; rule to allow traffic from known IPs:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gcloud compute security-policies rules create 1000 &lt;br /&gt;    --security-policy gke-ingress-example-security-policy &lt;br /&gt;    --description &quot;allow traffic&quot; &lt;br /&gt;    --src-ip-ranges &quot;192.0.2.0/24&quot;  &lt;br /&gt;    --action &quot;allow&quot;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Make sure to insert your specific allowed IP Addresses or subnets.&lt;p&gt;&lt;/p&gt;&lt;/details&gt;&lt;h1 id=&quot;heading-step-2-the-deployment&quot;&gt;Step 2: The Deployment&lt;/h1&gt;&lt;p&gt;Let&apos;s dig into our Kubernetes Manifests which were generated by the Helm Template. This is our deployment, which is very basic:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/deployments/deployment.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;apps/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-deployment&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;replicas:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;matchLabels:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;template:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;containers:&lt;/span&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-deployment&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;shayulmer/loadbalancer-tester&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;containerPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the Kubernetes Service which exposes the deployment. Notice how it references a &lt;em&gt;&quot;backend-config&lt;/em&gt;&quot;, which we will deploy and discuss in a moment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/services/service.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-service&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;annotations:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;cloud.google.com/backend-config:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&apos;{&quot;default&quot;: &quot;hello-world-gke-backend-config&quot;}&apos;&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;NodePort&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;    &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;protocol:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;TCP&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;targetPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Note: External GKE Load Balancers will not work with ClusterIP Services &lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/kubernetes-engine/docs/concepts/service-networking#client_network&quot;&gt;[4]&lt;/a&gt;, therefore our service type is &quot;NodePort&quot;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Now it&apos;s time for the Backend Config which is referenced by the Service. This config specifies the desired Cloud Armor Security Policy (which we created earlier):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/crds/gke-backend-config.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;cloud.google.com/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;BackendConfig&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-gke-backend-config&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;securityPolicy:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example-security-policy&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;BackendConfigs can be very important if your application does not return HTTP 200 response code on the &quot;/&quot; path. The Load Balancer health check defaults to this path, so if your app will not return 200 OK on &quot;/&quot;, the Load Balancer will show &quot;unhealthy backends&quot; and will not work. To fix this, you will need to directly configure a different health check on the BackendConfig: &lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#direct_hc&quot;&gt;https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#direct_hc&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Since we want to redirect all traffic to HTTPS, we also need a Kubernetes Custom Resource named FrontendConfig. We will later reference this config in our Ingress object:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/crds/gke-frontend-config.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;networking.gke.io/v1beta1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;FrontendConfig&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-gke-frontend-config&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;redirectToHttps:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;enabled:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We also need to create a GCP Managed Certificate. This certificate will be used on the Ingress object. The most crucial part of this manifest is specifying the correct domain, since the certificate will be verified and signed against the specified domain name.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/crds/gke-managed-certificate.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;networking.gke.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ManagedCertificate&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-managed-cert&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;domains:&lt;/span&gt;    &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example.devoops.blog&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And lastly, our Ingress which puts it all together:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/ingress/ingress.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;networking.k8s.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Ingress&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-ingress&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;annotations:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;kubernetes.io/ingress.class:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;gce&quot;&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;networking.gke.io/managed-certificates:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-managed-cert&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;kubernetes.io/ingress.global-static-ip-name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example-address&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;networking.gke.io/v1beta1.FrontendConfig:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-gke-frontend-config&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;rules:&lt;/span&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;host:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example.devoops.blog&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;http:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;paths:&lt;/span&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;path:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;/*&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;pathType:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ImplementationSpecific&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;backend:&lt;/span&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;service:&lt;/span&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-service&lt;/span&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt;              &lt;span class=&quot;hljs-attr&quot;&gt;number:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;See how under &quot;Annotations&quot;, the Ingress references the names of the following:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Our Ingress class (&quot;&lt;em&gt;gce&lt;/em&gt;&quot;, which is the External Load Balancer ingress class)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The managed certificate which will be used to initiate TLS Sessions&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The Global Static IP address which should be attached to the Load Balancer.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The FrontendConfig which redirects HTTP traffic to HTTPS.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;After reviewing the generated manifests, we may now install our Helm Chart. Verify you are in the desired Namespace and run:&lt;/p&gt;&lt;p&gt;&lt;code&gt;helm install hello-world . -f values.yaml&lt;/code&gt;&lt;/p&gt;&lt;h1 id=&quot;heading-step-3-signing-the-certificate&quot;&gt;Step 3: Signing the certificate&lt;/h1&gt;&lt;p&gt;Google signs certificate requests with DNS validation&lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs#update-dns&quot;&gt;[5]&lt;/a&gt;. All we have to do is go to our DNS provider and make sure our ingress domain name (in our case - &lt;em&gt;gke-ingress-example.devoops.blog&lt;/em&gt;) points to the global static IP address we requested earlier. After this is done, we will wait for the ManagedCertificate to become &quot;Active&quot;. This can take some time due to DNS propagation delay.&lt;/p&gt;&lt;p&gt;You can check the ManagedCertificate status by running:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;kubectl get ManagedCertificate&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1670327742344/ssdbBKWsK.png&quot; alt=&quot;Screenshot from 2022-12-06 13-55-26.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1670328420424/XppzCMJGJ.png&quot; alt=&quot;Screenshot from 2022-12-06 14-06-46.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;Once the certificate is signed (Active), it&apos;s going to take a few more minutes for GCP to configure the certificate on the load balancer (this will happen automatically).&lt;/p&gt;&lt;h1 id=&quot;heading-step-4-successful-deployment&quot;&gt;Step 4: Successful Deployment!&lt;/h1&gt;&lt;p&gt;we can finally access our application securely (from Cloud-Armor whitelisted IP addresses only):&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1673947231132/0014e7c1-550c-4ed9-90f9-37cef90ff0df.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Pro tip: Refresh the application (or browse from multiple tabs) to see the Load Balancer in action. The hostname of the pod changes as the Load Balancer distributes traffic to different pods.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Mission Accomplished! It was quite a challenge to figure all of this out, but once the concepts are laid out - I believe it&apos;s not too hard to understand and deploy.&lt;/p&gt;&lt;p&gt;The best part of this experiment is that now we have &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/gke-ingress-example/tree/main/templates&quot;&gt;a valid Helm chart template&lt;/a&gt; for exposing an application on GKE. To take this to the next level, we can even sync this chart to our cluster with a GitOps tool like ArgoCD, but this is a topic for another post :)&lt;/p&gt;]]&gt;</content:encoded><hashnode:content>&lt;![CDATA[&lt;p&gt;I recently needed to expose an application that runs on GKE to the outside world. To do that, I had to learn and use GKE&apos;s way of working with Ingress and Load Balancers. Although it&apos;s not too complicated (compared to other Ingress implementations), there are some technicalities that we need to resolve first.&lt;/p&gt;&lt;h1 id=&quot;heading-overview&quot;&gt;Overview&lt;/h1&gt;&lt;p&gt;Today we will expose &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/loadbalancer-tester&quot;&gt;a simple &quot;hello world&quot; app&lt;/a&gt; (&lt;a target=&quot;_blank&quot; href=&quot;https://hub.docker.com/r/shayulmer/loadbalancer-tester&quot;&gt;shayulmer/loadbalancer-tester&lt;/a&gt; container image) to the world, using TLS on GKE. This app serves as a web server that prints out the hostname of the running container, so we would be able to see the GCP Load Balancer in action.&lt;/p&gt;&lt;p&gt;The application itself runs on HTTP, but we will expose it over HTTPS using a GCP Managed Certificate and TLS Termination on our Ingress Load Balancer. We will also use Google Cloud Armor to limit traffic to the Load Balancer.&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1673872422573/79297c58-5e41-42c2-9bfd-d99581cdba82.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;h1 id=&quot;heading-prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;A running GKE Cluster&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Basic Kubernetes knowledge - understanding of Deployments, Services, Ingress.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;gcloud CLI configured and logged in to the relevant GCP Project.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h1 id=&quot;heading-the-helm-chart&quot;&gt;The Helm Chart&lt;/h1&gt;&lt;p&gt;The following repository contains a helm chart for today&apos;s experiment: &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/gke-ingress-example&quot;&gt;https://github.com/shay-ul/gke-ingress-example&lt;/a&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Note: If you are going to deploy this helm chart on your environment, make sure to override the values.yaml file with your specific environment&apos;s details.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Since we want to see what&apos;s being deployed to GKE, we will not go over the helm chart itself. Instead, we will discuss the Kubernetes Manifests which are being generated from this chart. We will generate the manifests using this command:&lt;/p&gt;&lt;p&gt;&lt;code&gt;helm template hello-world . -f values.yaml&lt;/code&gt;&lt;/p&gt;&lt;h1 id=&quot;heading-step-1-prepare-the-gcp-infrastructure&quot;&gt;Step 1: Prepare the GCP Infrastructure&lt;/h1&gt;&lt;p&gt;Since we want to issue a public Google-managed certificate, we have to use an external HTTP(S) Load Balancer&lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/load-balancing/docs/ssl-certificates#certificate-types&quot;&gt;[1]&lt;/a&gt;. When working with External Load Balancers, we have to consider two things:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Public Static IP&lt;/strong&gt; - External GCP Load balancers (unlike their AWS counterparts) will not receive a dynamic DNS that points to a dynamically allocated IP address&lt;a target=&quot;_blank&quot; href=&quot;https://stackoverflow.com/questions/63650088/getting-dns-for-load-balancer-in-gcp&quot;&gt;[2]&lt;/a&gt;. Instead, you have to manually point a DNS Record to the IP address assigned to the Load Balancer. To verify the IP address will never be changed (and the DNS will always point to the correct address), we have to request a Global Static Public IP. This address will be attached to our Load Balancer.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manage IP Whitelist&lt;/strong&gt; - Our application will be widely accessible over the internet unless we limit who can access our application. Since this is just a test run, we will utilize Google&apos;s Cloud Armor to block traffic which is not originated from IP addresses we specifically allow.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;details&gt;&lt;summary&gt;Step 1.a - Request a Global Static IP Address&lt;/summary&gt;&lt;br /&gt;&lt;p&gt;To do this, we will run the following command:&lt;br /&gt;&lt;code&gt;gcloud compute addresses create gke-ingress-example-address --global&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;After the request is completed, we can observe the allocated IP using this command:&lt;br /&gt;&lt;code&gt;gcloud compute addresses describe gke-ingress-example-address --global&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Register the address shown since you will need it later for the DNS record settings.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;/details&gt;&lt;details&gt;&lt;summary&gt;Step 1.b - Configure Cloud Armor Policy&lt;/summary&gt;Here we will create a new network security policy:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gcloud compute security-policies create gke-ingress-example-security-policy`&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Next we will create a default rule to block all traffic, with the lowest priority allowed (2147483647):&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gcloud compute security-policies rules update 2147483647 --security-policy gke-ingress-example-security-policy --action &quot;deny-502&quot;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;And finally, we will add our &quot;Allow&quot; rule to allow traffic from known IPs:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;gcloud compute security-policies rules create 1000 &lt;br /&gt;    --security-policy gke-ingress-example-security-policy &lt;br /&gt;    --description &quot;allow traffic&quot; &lt;br /&gt;    --src-ip-ranges &quot;192.0.2.0/24&quot;  &lt;br /&gt;    --action &quot;allow&quot;&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;Make sure to insert your specific allowed IP Addresses or subnets.&lt;p&gt;&lt;/p&gt;&lt;/details&gt;&lt;h1 id=&quot;heading-step-2-the-deployment&quot;&gt;Step 2: The Deployment&lt;/h1&gt;&lt;p&gt;Let&apos;s dig into our Kubernetes Manifests which were generated by the Helm Template. This is our deployment, which is very basic:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/deployments/deployment.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;apps/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-deployment&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;replicas:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;3&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;matchLabels:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;template:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;containers:&lt;/span&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-deployment&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;shayulmer/loadbalancer-tester&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;        &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;containerPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the Kubernetes Service which exposes the deployment. Notice how it references a &lt;em&gt;&quot;backend-config&lt;/em&gt;&quot;, which we will deploy and discuss in a moment:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/services/service.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-service&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;annotations:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;cloud.google.com/backend-config:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&apos;{&quot;default&quot;: &quot;hello-world-gke-backend-config&quot;}&apos;&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;NodePort&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;    &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;protocol:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;TCP&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;targetPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;Note: External GKE Load Balancers will not work with ClusterIP Services &lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/kubernetes-engine/docs/concepts/service-networking#client_network&quot;&gt;[4]&lt;/a&gt;, therefore our service type is &quot;NodePort&quot;.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Now it&apos;s time for the Backend Config which is referenced by the Service. This config specifies the desired Cloud Armor Security Policy (which we created earlier):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/crds/gke-backend-config.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;cloud.google.com/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;BackendConfig&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-gke-backend-config&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;securityPolicy:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example-security-policy&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;blockquote&gt;&lt;p&gt;BackendConfigs can be very important if your application does not return HTTP 200 response code on the &quot;/&quot; path. The Load Balancer health check defaults to this path, so if your app will not return 200 OK on &quot;/&quot;, the Load Balancer will show &quot;unhealthy backends&quot; and will not work. To fix this, you will need to directly configure a different health check on the BackendConfig: &lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#direct_hc&quot;&gt;https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#direct_hc&lt;/a&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Since we want to redirect all traffic to HTTPS, we also need a Kubernetes Custom Resource named FrontendConfig. We will later reference this config in our Ingress object:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/crds/gke-frontend-config.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;networking.gke.io/v1beta1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;FrontendConfig&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-gke-frontend-config&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;redirectToHttps:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;enabled:&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;true&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We also need to create a GCP Managed Certificate. This certificate will be used on the Ingress object. The most crucial part of this manifest is specifying the correct domain, since the certificate will be verified and signed against the specified domain name.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/crds/gke-managed-certificate.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;networking.gke.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ManagedCertificate&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-managed-cert&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;domains:&lt;/span&gt;    &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example.devoops.blog&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And lastly, our Ingress which puts it all together:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-meta&quot;&gt;---&lt;/span&gt;&lt;span class=&quot;hljs-comment&quot;&gt;# Source: gke-ingress-example/templates/ingress/ingress.yaml&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;networking.k8s.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Ingress&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-ingress&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;annotations:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;kubernetes.io/ingress.class:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;&quot;gce&quot;&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;networking.gke.io/managed-certificates:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-managed-cert&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;kubernetes.io/ingress.global-static-ip-name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example-address&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;networking.gke.io/v1beta1.FrontendConfig:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-gke-frontend-config&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;rules:&lt;/span&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;host:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;gke-ingress-example.devoops.blog&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;http:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;paths:&lt;/span&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;path:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;/*&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;pathType:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ImplementationSpecific&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;backend:&lt;/span&gt;          &lt;span class=&quot;hljs-attr&quot;&gt;service:&lt;/span&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;hello-world-service&lt;/span&gt;            &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt;              &lt;span class=&quot;hljs-attr&quot;&gt;number:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;See how under &quot;Annotations&quot;, the Ingress references the names of the following:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;Our Ingress class (&quot;&lt;em&gt;gce&lt;/em&gt;&quot;, which is the External Load Balancer ingress class)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The managed certificate which will be used to initiate TLS Sessions&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The Global Static IP address which should be attached to the Load Balancer.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;The FrontendConfig which redirects HTTP traffic to HTTPS.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;After reviewing the generated manifests, we may now install our Helm Chart. Verify you are in the desired Namespace and run:&lt;/p&gt;&lt;p&gt;&lt;code&gt;helm install hello-world . -f values.yaml&lt;/code&gt;&lt;/p&gt;&lt;h1 id=&quot;heading-step-3-signing-the-certificate&quot;&gt;Step 3: Signing the certificate&lt;/h1&gt;&lt;p&gt;Google signs certificate requests with DNS validation&lt;a target=&quot;_blank&quot; href=&quot;https://cloud.google.com/load-balancing/docs/ssl-certificates/google-managed-certs#update-dns&quot;&gt;[5]&lt;/a&gt;. All we have to do is go to our DNS provider and make sure our ingress domain name (in our case - &lt;em&gt;gke-ingress-example.devoops.blog&lt;/em&gt;) points to the global static IP address we requested earlier. After this is done, we will wait for the ManagedCertificate to become &quot;Active&quot;. This can take some time due to DNS propagation delay.&lt;/p&gt;&lt;p&gt;You can check the ManagedCertificate status by running:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;kubectl get ManagedCertificate&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1670327742344/ssdbBKWsK.png&quot; alt=&quot;Screenshot from 2022-12-06 13-55-26.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1670328420424/XppzCMJGJ.png&quot; alt=&quot;Screenshot from 2022-12-06 14-06-46.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;Once the certificate is signed (Active), it&apos;s going to take a few more minutes for GCP to configure the certificate on the load balancer (this will happen automatically).&lt;/p&gt;&lt;h1 id=&quot;heading-step-4-successful-deployment&quot;&gt;Step 4: Successful Deployment!&lt;/h1&gt;&lt;p&gt;we can finally access our application securely (from Cloud-Armor whitelisted IP addresses only):&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1673947231132/0014e7c1-550c-4ed9-90f9-37cef90ff0df.png&quot; alt class=&quot;image--center mx-auto&quot; /&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Pro tip: Refresh the application (or browse from multiple tabs) to see the Load Balancer in action. The hostname of the pod changes as the Load Balancer distributes traffic to different pods.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Mission Accomplished! It was quite a challenge to figure all of this out, but once the concepts are laid out - I believe it&apos;s not too hard to understand and deploy.&lt;/p&gt;&lt;p&gt;The best part of this experiment is that now we have &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/gke-ingress-example/tree/main/templates&quot;&gt;a valid Helm chart template&lt;/a&gt; for exposing an application on GKE. To take this to the next level, we can even sync this chart to our cluster with a GitOps tool like ArgoCD, but this is a topic for another post :)&lt;/p&gt;]]&gt;</hashnode:content><hashnode:coverImage>https://cdn.hashnode.com/res/hashnode/image/upload/v1673872051155/e511bf97-fd95-4d3a-b16c-385d0f9ff419.png</hashnode:coverImage></item><item><title><![CDATA[First post: Creating a simple web app to list kubernetes resources]]></title><description><![CDATA[Today we're going to overview a fun experiment that involves many aspects of working with Kubernetes. I had a very interesting use case where I needed a simple and easy way for my developers to see some Kubernetes resources. To do that, I created a q...]]></description><link>https://devoops.blog/kubernetes-pods-extractor</link><guid isPermaLink="true">https://devoops.blog/kubernetes-pods-extractor</guid><category><![CDATA[Kubernetes]]></category><category><![CDATA[Python]]></category><category><![CDATA[Flask Framework]]></category><dc:creator><![CDATA[Shay Ulmer]]></dc:creator><pubDate>Mon, 12 Sep 2022 13:26:03 GMT</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;Today we&apos;re going to overview a fun experiment that involves many aspects of working with Kubernetes. I had a very interesting use case where I needed a simple and easy way for my developers to see some Kubernetes resources. To do that, I created a quick solution which is basically a very simple flask app that does one thing: it lists resources on the Kubernetes cluster it runs on.&lt;/p&gt;&lt;p&gt;I personally thought that this is a specifically interesting experiment since it involved learning how to utilize kubernetes Service Accounts, RBAC, python client, Ingress and more.&lt;/p&gt;&lt;p&gt;All the resources in this article are already hosted on github - &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor&quot;&gt;Click here&lt;/a&gt; to view.&lt;/p&gt;&lt;h1 id=&quot;heading-tldr&quot;&gt;TL;DR&lt;/h1&gt;&lt;p&gt;to deploy and check out the application, run:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-shell&quot;&gt;git clone https://github.com/shay-ul/kubernetes-pods-extractor.gitcd kubernetes-pods-extractorkubectl apply -f manifests/&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;heading-prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;A running Kubernetes cluster.&lt;/li&gt;&lt;li&gt;Basic Kubernetes knowledge (i.e - How to apply manifests).&lt;/li&gt;&lt;/ol&gt;&lt;h1 id=&quot;heading-step-1-prepare-the-kubernetes-resources&quot;&gt;Step 1: Prepare the Kubernetes resources&lt;/h1&gt;&lt;p&gt;First, let&apos;s create our namespace:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Namespace&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since our application will run inside the cluster, we will need to create a dedicated Service Account that the application will use:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ServiceAccount&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-sa&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we need to create a ClusterRole that will enable our ServiceAcccount to get the information needed on pods:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRole&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;namespace&quot; omitted since ClusterRoles are not namespaced&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-reader&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;rules:&lt;/span&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;apiGroups:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;]  &lt;span class=&quot;hljs-attr&quot;&gt;resources:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;pods&quot;&lt;/span&gt;]  &lt;span class=&quot;hljs-attr&quot;&gt;verbs:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;get&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;watch&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;list&quot;&lt;/span&gt;]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we now have a ServiceAccount and a ClusterRole, we need to couple those two together, we will do so using ClusterRoleBinding:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRoleBinding&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-clusterrolebinding&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;subjects:&lt;/span&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ServiceAccount&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-sa&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;roleRef:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRole&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-reader&lt;/span&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;apiGroup:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are now ready to create our application.&lt;/p&gt;&lt;h1 id=&quot;heading-step-2-working-with-flask-and-the-kubernetes-python-client&quot;&gt;Step 2: Working with Flask and the Kubernetes python client&lt;/h1&gt;&lt;p&gt;Let&apos;s dive deep into our main script (&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/src/extract_pods.py&quot;&gt;src/extract_pods.py&lt;/a&gt;).Our first function is &quot;get_kube_config&quot;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;get_kube_config&lt;/span&gt;():&lt;/span&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:        config.load_incluster_config()    &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; config.ConfigException:        &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:            config.load_kube_config()        &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; config.ConfigException:            &lt;span class=&quot;hljs-keyword&quot;&gt;raise&lt;/span&gt; Exception(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Could not configure kubernetes python client&quot;&lt;/span&gt;)    v1 = client.CoreV1Api()    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; v1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function will basically load the ServiceAccount token if it runs inside the cluster, or your local kubectl context if you run the script locally.&lt;/p&gt;&lt;p&gt;How will the Kubernetes client know which one to choose? Well, our script just tries both methods (try-except). If it can&apos;t load the in-cluster config, it will then go and look for the kubernetes context (&lt;strong&gt;&lt;em&gt;config.load_incluster_config&lt;/em&gt;&lt;/strong&gt; vs &lt;strong&gt;&lt;em&gt;config.load_kube_config&lt;/em&gt;&lt;/strong&gt;).&lt;/p&gt;&lt;p&gt;When we ask the Kubernetes client to load the in-cluster config, it basically looks for the ServiceAccount token which should be loaded to &quot;&lt;strong&gt;&lt;em&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/em&gt;&lt;/strong&gt;&quot; file. If the ServiceAccount was configured correctly on the pod level, we should have no problems querying the Kubernetes API from within our pod.&lt;/p&gt;&lt;p&gt;Our next function is the main function, which will call our previous explained &lt;strong&gt;&lt;em&gt;get_kube_config&lt;/em&gt;&lt;/strong&gt; function. It will then iterate over all pods in the cluster and create a dedicated list (&lt;strong&gt;&lt;em&gt;pods_details_list&lt;/em&gt;&lt;/strong&gt;). This list will be comprised from dictionaries, each one will hold the specific details of a specific pod: &lt;strong&gt;&lt;em&gt;name&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;namespace&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;ip&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;node&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;status&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;main&lt;/span&gt;():&lt;/span&gt;    v1 = get_kube_config()    pods = v1.list_pod_for_all_namespaces(watch=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;)    pods_details_list = []    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; pod &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; pods.items:        status = get_pod_status(pod)        dict = {            &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: pod.metadata.name,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;namespace&quot;&lt;/span&gt;: pod.metadata.namespace,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;ip&quot;&lt;/span&gt;: pod.status.pod_ip,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;node&quot;&lt;/span&gt;: pod.spec.node_name,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;status&quot;&lt;/span&gt;: status        }        pods_details_list.append(dict)    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (pods_details_list)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The main function calls for one more function called &lt;strong&gt;&lt;em&gt;get_pod_status&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;get_pod_status&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;pod&lt;/span&gt;):&lt;/span&gt;    status = pod.status.phase    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; container_status &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; pod.status.container_statuses:        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; container_status.started &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;or&lt;/span&gt; container_status.ready &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;:            waiting_state = container_status.state.waiting            &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; waiting_state &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;:                status = waiting_state.reason    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; status&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function is basically a workaround for fetching the pod status. See, when &lt;strong&gt;&lt;em&gt;kubectl get pod&lt;/em&gt;&lt;/strong&gt; fetches a pod status, it does a similar thing. It looks for containers failing inside the specific pod, then reports back the specific problematic container status as the entire pod status. This is what we&apos;re doing here, since the python kubernetes client won&apos;t give us a simpler method to get this information.&lt;/p&gt;&lt;p&gt;The next part will be the entry function for the entire script. Since our script will be ran upon a HTTP request to Flask, we need to initialize our Flask app:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;@app.route(&lt;span class=&quot;hljs-string&quot;&gt;&apos;/&apos;&lt;/span&gt;)def hello_world():    &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; render_template(&lt;span class=&quot;hljs-string&quot;&gt;&apos;index.html&apos;&lt;/span&gt;, pods_list=main())&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&apos;__main__&apos;&lt;/span&gt;:    app.run(host=&lt;span class=&quot;hljs-string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;, port=8080)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The most important line in this function is &lt;code&gt;return render_template(&apos;index.html&apos;, pods_list=main())&lt;/code&gt;. &lt;/p&gt;&lt;p&gt;This line will be executed when an HTTP request hits our flask application, and it will render a JINJA2 template file (&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/src/templates/index.html&quot;&gt;src/templates/index.html&lt;/a&gt;) and forward a list called &lt;strong&gt;&lt;em&gt;pods_list&lt;/em&gt;&lt;/strong&gt; as a variable to this template. The list contains the return values from our &quot;main&quot; function, described above.&lt;/p&gt;&lt;p&gt;Now that we understand our main script, we can have a look at our template (&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/src/templates/index.html&quot;&gt;src/templates/index.html&lt;/a&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;integrity&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;crossorigin&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;table table-striped&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;thead&lt;/span&gt;&amp;gt;&lt;/span&gt;      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Name&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Namespace&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;IP&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Node&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Status&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;thead&lt;/span&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;tbody&lt;/span&gt;&amp;gt;&lt;/span&gt;        {% for pod in pods_list %}        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;name&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;namespace&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;ip&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;node&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;status&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;    {% endfor %}    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;tbody&lt;/span&gt;&amp;gt;&lt;/span&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;table&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this template, we are using basic bootstrap so our table will look pretty.Our pod data is populated since we are iterating over our pods list which is recieved as an input to this template (&lt;code&gt;{% for pod in pods_list %}&lt;/code&gt;).&lt;/p&gt;&lt;h1 id=&quot;heading-step-3-build-and-package-our-application&quot;&gt;Step 3: Build and package our application&lt;/h1&gt;&lt;p&gt;our application is ready, but we now need to package it to a container image. To do so, we need to create a new &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;FROM python:&lt;span class=&quot;hljs-number&quot;&gt;3.7&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;slim&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;busterCOPY src &lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;COPY requirements.txt &lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;requirements.txtRUN apt&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;get update \    &lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt; pip install &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;upgrade pip \    &lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt; pip install &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;cache&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;dir &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;r app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;requirements.txtWORKDIR &lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;CMD [&lt;span class=&quot;hljs-string&quot;&gt;&quot;python3&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;extract_pods.py&quot;&lt;/span&gt;]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, we will build our image on top of python:3.7 image and our application will run as a simple python script when our container starts.&lt;/p&gt;&lt;p&gt;We will also need a requirements.txt file that will hold our pip dependencies:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;kubernetes&lt;/span&gt;==&lt;span class=&quot;hljs-number&quot;&gt;24&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;flask&lt;/span&gt;==&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are only using two packages, which makes our requirements file kind of small :) &lt;/p&gt;&lt;p&gt;You can now build and push your container image to any registry. If you wish to avoid building and pushing your own image, you can use the image published from my source repository:&lt;/p&gt;&lt;p&gt;&lt;code&gt;shayulmer/kubernetes-pods-extractor:latest&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This image is automatically built and pushed using Github Actions on any push to the main branch on my source repostory (some very basic CI, you can check it out &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/.github/workflows/docker-image.yml&quot;&gt;here&lt;/a&gt;)&lt;/p&gt;&lt;h1 id=&quot;heading-step-4-deploy-our-application&quot;&gt;Step 4: Deploy our application&lt;/h1&gt;&lt;p&gt;Finally, we can now apply our application to Kubernetes, using this &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/manifests/04-deployment.yaml&quot;&gt;Deployment&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;apps/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;replicas:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;matchLabels:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;template:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;containers:&lt;/span&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;shayulmer/kubernetes-pods-extractor:latest&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;serviceAccountName:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-sa&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our ServiceAccount is mounted to our pod in the last line of this file.&lt;/p&gt;&lt;p&gt;And now we can expose the deployment using a &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/manifests/05-service.yaml&quot;&gt;service&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;annotations:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-service&lt;/span&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;http&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;protocol:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;TCP&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;targetPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;8080&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterIP&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;heading-step-5-we-are-done&quot;&gt;Step 5: We are done!&lt;/h1&gt;&lt;p&gt;Everthing is ready for us to access our application. All we need is to run:&lt;/p&gt;&lt;p&gt;&lt;code&gt;kubectl port-forward svc/kubernetes-pods-extractor-service 8081:80 -n kubernetes-pods-extractor&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Access &lt;code&gt;http://localhost:8081&lt;/code&gt; and behold!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1662470071490/T7KIN00Ha.png&quot; alt=&quot;Screenshot from 2022-07-12 11-13-46.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;If we wish to expose this service to (potentially) the entire world, we can do so using an Ingress object, but this is a topic for our next post :)&lt;/p&gt;]]&gt;</content:encoded><hashnode:content>&lt;![CDATA[&lt;p&gt;Today we&apos;re going to overview a fun experiment that involves many aspects of working with Kubernetes. I had a very interesting use case where I needed a simple and easy way for my developers to see some Kubernetes resources. To do that, I created a quick solution which is basically a very simple flask app that does one thing: it lists resources on the Kubernetes cluster it runs on.&lt;/p&gt;&lt;p&gt;I personally thought that this is a specifically interesting experiment since it involved learning how to utilize kubernetes Service Accounts, RBAC, python client, Ingress and more.&lt;/p&gt;&lt;p&gt;All the resources in this article are already hosted on github - &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor&quot;&gt;Click here&lt;/a&gt; to view.&lt;/p&gt;&lt;h1 id=&quot;heading-tldr&quot;&gt;TL;DR&lt;/h1&gt;&lt;p&gt;to deploy and check out the application, run:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-shell&quot;&gt;git clone https://github.com/shay-ul/kubernetes-pods-extractor.gitcd kubernetes-pods-extractorkubectl apply -f manifests/&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;heading-prerequisites&quot;&gt;Prerequisites&lt;/h1&gt;&lt;ol&gt;&lt;li&gt;A running Kubernetes cluster.&lt;/li&gt;&lt;li&gt;Basic Kubernetes knowledge (i.e - How to apply manifests).&lt;/li&gt;&lt;/ol&gt;&lt;h1 id=&quot;heading-step-1-prepare-the-kubernetes-resources&quot;&gt;Step 1: Prepare the Kubernetes resources&lt;/h1&gt;&lt;p&gt;First, let&apos;s create our namespace:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Namespace&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since our application will run inside the cluster, we will need to create a dedicated Service Account that the application will use:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ServiceAccount&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-sa&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now we need to create a ClusterRole that will enable our ServiceAcccount to get the information needed on pods:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRole&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-comment&quot;&gt;# &quot;namespace&quot; omitted since ClusterRoles are not namespaced&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-reader&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;rules:&lt;/span&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;apiGroups:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;&quot;&lt;/span&gt;]  &lt;span class=&quot;hljs-attr&quot;&gt;resources:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;pods&quot;&lt;/span&gt;]  &lt;span class=&quot;hljs-attr&quot;&gt;verbs:&lt;/span&gt; [&lt;span class=&quot;hljs-string&quot;&gt;&quot;get&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;watch&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;list&quot;&lt;/span&gt;]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Since we now have a ServiceAccount and a ClusterRole, we need to couple those two together, we will do so using ClusterRoleBinding:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRoleBinding&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-clusterrolebinding&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;subjects:&lt;/span&gt;&lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ServiceAccount&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-sa&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;roleRef:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterRole&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-reader&lt;/span&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;apiGroup:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;rbac.authorization.k8s.io&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are now ready to create our application.&lt;/p&gt;&lt;h1 id=&quot;heading-step-2-working-with-flask-and-the-kubernetes-python-client&quot;&gt;Step 2: Working with Flask and the Kubernetes python client&lt;/h1&gt;&lt;p&gt;Let&apos;s dive deep into our main script (&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/src/extract_pods.py&quot;&gt;src/extract_pods.py&lt;/a&gt;).Our first function is &quot;get_kube_config&quot;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;get_kube_config&lt;/span&gt;():&lt;/span&gt;    &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:        config.load_incluster_config()    &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; config.ConfigException:        &lt;span class=&quot;hljs-keyword&quot;&gt;try&lt;/span&gt;:            config.load_kube_config()        &lt;span class=&quot;hljs-keyword&quot;&gt;except&lt;/span&gt; config.ConfigException:            &lt;span class=&quot;hljs-keyword&quot;&gt;raise&lt;/span&gt; Exception(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Could not configure kubernetes python client&quot;&lt;/span&gt;)    v1 = client.CoreV1Api()    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; v1&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function will basically load the ServiceAccount token if it runs inside the cluster, or your local kubectl context if you run the script locally.&lt;/p&gt;&lt;p&gt;How will the Kubernetes client know which one to choose? Well, our script just tries both methods (try-except). If it can&apos;t load the in-cluster config, it will then go and look for the kubernetes context (&lt;strong&gt;&lt;em&gt;config.load_incluster_config&lt;/em&gt;&lt;/strong&gt; vs &lt;strong&gt;&lt;em&gt;config.load_kube_config&lt;/em&gt;&lt;/strong&gt;).&lt;/p&gt;&lt;p&gt;When we ask the Kubernetes client to load the in-cluster config, it basically looks for the ServiceAccount token which should be loaded to &quot;&lt;strong&gt;&lt;em&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/em&gt;&lt;/strong&gt;&quot; file. If the ServiceAccount was configured correctly on the pod level, we should have no problems querying the Kubernetes API from within our pod.&lt;/p&gt;&lt;p&gt;Our next function is the main function, which will call our previous explained &lt;strong&gt;&lt;em&gt;get_kube_config&lt;/em&gt;&lt;/strong&gt; function. It will then iterate over all pods in the cluster and create a dedicated list (&lt;strong&gt;&lt;em&gt;pods_details_list&lt;/em&gt;&lt;/strong&gt;). This list will be comprised from dictionaries, each one will hold the specific details of a specific pod: &lt;strong&gt;&lt;em&gt;name&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;namespace&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;ip&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;node&lt;/em&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;em&gt;status&lt;/em&gt;&lt;/strong&gt;.&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;main&lt;/span&gt;():&lt;/span&gt;    v1 = get_kube_config()    pods = v1.list_pod_for_all_namespaces(watch=&lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;)    pods_details_list = []    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; pod &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; pods.items:        status = get_pod_status(pod)        dict = {            &lt;span class=&quot;hljs-string&quot;&gt;&quot;name&quot;&lt;/span&gt;: pod.metadata.name,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;namespace&quot;&lt;/span&gt;: pod.metadata.namespace,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;ip&quot;&lt;/span&gt;: pod.status.pod_ip,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;node&quot;&lt;/span&gt;: pod.spec.node_name,            &lt;span class=&quot;hljs-string&quot;&gt;&quot;status&quot;&lt;/span&gt;: status        }        pods_details_list.append(dict)    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; (pods_details_list)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The main function calls for one more function called &lt;strong&gt;&lt;em&gt;get_pod_status&lt;/em&gt;&lt;/strong&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-python&quot;&gt;&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;get_pod_status&lt;/span&gt;(&lt;span class=&quot;hljs-params&quot;&gt;pod&lt;/span&gt;):&lt;/span&gt;    status = pod.status.phase    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; container_status &lt;span class=&quot;hljs-keyword&quot;&gt;in&lt;/span&gt; pod.status.container_statuses:        &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; container_status.started &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;or&lt;/span&gt; container_status.ready &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;False&lt;/span&gt;:            waiting_state = container_status.state.waiting            &lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; waiting_state &lt;span class=&quot;hljs-keyword&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;hljs-keyword&quot;&gt;not&lt;/span&gt; &lt;span class=&quot;hljs-literal&quot;&gt;None&lt;/span&gt;:                status = waiting_state.reason    &lt;span class=&quot;hljs-keyword&quot;&gt;return&lt;/span&gt; status&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This function is basically a workaround for fetching the pod status. See, when &lt;strong&gt;&lt;em&gt;kubectl get pod&lt;/em&gt;&lt;/strong&gt; fetches a pod status, it does a similar thing. It looks for containers failing inside the specific pod, then reports back the specific problematic container status as the entire pod status. This is what we&apos;re doing here, since the python kubernetes client won&apos;t give us a simpler method to get this information.&lt;/p&gt;&lt;p&gt;The next part will be the entry function for the entire script. Since our script will be ran upon a HTTP request to Flask, we need to initialize our Flask app:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-bash&quot;&gt;@app.route(&lt;span class=&quot;hljs-string&quot;&gt;&apos;/&apos;&lt;/span&gt;)def hello_world():    &lt;span class=&quot;hljs-built_in&quot;&gt;return&lt;/span&gt; render_template(&lt;span class=&quot;hljs-string&quot;&gt;&apos;index.html&apos;&lt;/span&gt;, pods_list=main())&lt;span class=&quot;hljs-keyword&quot;&gt;if&lt;/span&gt; __name__ == &lt;span class=&quot;hljs-string&quot;&gt;&apos;__main__&apos;&lt;/span&gt;:    app.run(host=&lt;span class=&quot;hljs-string&quot;&gt;&quot;0.0.0.0&quot;&lt;/span&gt;, port=8080)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The most important line in this function is &lt;code&gt;return render_template(&apos;index.html&apos;, pods_list=main())&lt;/code&gt;. &lt;/p&gt;&lt;p&gt;This line will be executed when an HTTP request hits our flask application, and it will render a JINJA2 template file (&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/src/templates/index.html&quot;&gt;src/templates/index.html&lt;/a&gt;) and forward a list called &lt;strong&gt;&lt;em&gt;pods_list&lt;/em&gt;&lt;/strong&gt; as a variable to this template. The list contains the return values from our &quot;main&quot; function, described above.&lt;/p&gt;&lt;p&gt;Now that we understand our main script, we can have a look at our template (&lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/src/templates/index.html&quot;&gt;src/templates/index.html&lt;/a&gt;):&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-html&quot;&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;link&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;rel&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;stylesheet&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;href&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;https://cdn.jsdelivr.net/npm/bootstrap@4.1.3/dist/css/bootstrap.min.css&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;integrity&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO&quot;&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;crossorigin&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;anonymous&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;class&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;table table-striped&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;thead&lt;/span&gt;&amp;gt;&lt;/span&gt;      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Name&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Namespace&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;IP&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Node&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;scope&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;col&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;Status&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;th&lt;/span&gt;&amp;gt;&lt;/span&gt;      &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;thead&lt;/span&gt;&amp;gt;&lt;/span&gt;    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;tbody&lt;/span&gt;&amp;gt;&lt;/span&gt;        {% for pod in pods_list %}        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;name&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;namespace&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;ip&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;node&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;style&lt;/span&gt;=&lt;span class=&quot;hljs-string&quot;&gt;&quot;width: 10%;&quot;&lt;/span&gt;&amp;gt;&lt;/span&gt;{{ pod[&apos;status&apos;] }}&lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;td&lt;/span&gt;&amp;gt;&lt;/span&gt;        &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;tr&lt;/span&gt;&amp;gt;&lt;/span&gt;    {% endfor %}    &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;tbody&lt;/span&gt;&amp;gt;&lt;/span&gt;  &lt;span class=&quot;hljs-tag&quot;&gt;&amp;lt;/&lt;span class=&quot;hljs-name&quot;&gt;table&lt;/span&gt;&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this template, we are using basic bootstrap so our table will look pretty.Our pod data is populated since we are iterating over our pods list which is recieved as an input to this template (&lt;code&gt;{% for pod in pods_list %}&lt;/code&gt;).&lt;/p&gt;&lt;h1 id=&quot;heading-step-3-build-and-package-our-application&quot;&gt;Step 3: Build and package our application&lt;/h1&gt;&lt;p&gt;our application is ready, but we now need to package it to a container image. To do so, we need to create a new &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/Dockerfile&quot;&gt;Dockerfile&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;FROM python:&lt;span class=&quot;hljs-number&quot;&gt;3.7&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;slim&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;busterCOPY src &lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;COPY requirements.txt &lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;requirements.txtRUN apt&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;get update \    &lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt; pip install &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;upgrade pip \    &lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;&amp;amp;&lt;/span&gt; pip install &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;no&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;cache&lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;dir &lt;span class=&quot;hljs-operator&quot;&gt;-&lt;/span&gt;r app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;requirements.txtWORKDIR &lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;app&lt;span class=&quot;hljs-operator&quot;&gt;/&lt;/span&gt;CMD [&lt;span class=&quot;hljs-string&quot;&gt;&quot;python3&quot;&lt;/span&gt;, &lt;span class=&quot;hljs-string&quot;&gt;&quot;extract_pods.py&quot;&lt;/span&gt;]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;As you can see, we will build our image on top of python:3.7 image and our application will run as a simple python script when our container starts.&lt;/p&gt;&lt;p&gt;We will also need a requirements.txt file that will hold our pip dependencies:&lt;/p&gt;&lt;pre&gt;&lt;code&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;kubernetes&lt;/span&gt;==&lt;span class=&quot;hljs-number&quot;&gt;24&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;hljs-attribute&quot;&gt;flask&lt;/span&gt;==&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;.&lt;span class=&quot;hljs-number&quot;&gt;2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We are only using two packages, which makes our requirements file kind of small :) &lt;/p&gt;&lt;p&gt;You can now build and push your container image to any registry. If you wish to avoid building and pushing your own image, you can use the image published from my source repository:&lt;/p&gt;&lt;p&gt;&lt;code&gt;shayulmer/kubernetes-pods-extractor:latest&lt;/code&gt;&lt;/p&gt;&lt;p&gt;This image is automatically built and pushed using Github Actions on any push to the main branch on my source repostory (some very basic CI, you can check it out &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/.github/workflows/docker-image.yml&quot;&gt;here&lt;/a&gt;)&lt;/p&gt;&lt;h1 id=&quot;heading-step-4-deploy-our-application&quot;&gt;Step 4: Deploy our application&lt;/h1&gt;&lt;p&gt;Finally, we can now apply our application to Kubernetes, using this &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/manifests/04-deployment.yaml&quot;&gt;Deployment&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;apps/v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Deployment&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;replicas:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;1&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;matchLabels:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;template:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;labels:&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;containers:&lt;/span&gt;      &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;        &lt;span class=&quot;hljs-attr&quot;&gt;image:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;shayulmer/kubernetes-pods-extractor:latest&lt;/span&gt;      &lt;span class=&quot;hljs-attr&quot;&gt;serviceAccountName:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-sa&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Our ServiceAccount is mounted to our pod in the last line of this file.&lt;/p&gt;&lt;p&gt;And now we can expose the deployment using a &lt;a target=&quot;_blank&quot; href=&quot;https://github.com/shay-ul/kubernetes-pods-extractor/blob/main/manifests/05-service.yaml&quot;&gt;service&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;&lt;code class=&quot;lang-yaml&quot;&gt;&lt;span class=&quot;hljs-attr&quot;&gt;apiVersion:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;v1&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;kind:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;Service&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;metadata:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;annotations:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor-service&lt;/span&gt;   &lt;span class=&quot;hljs-attr&quot;&gt;namespace:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;&lt;span class=&quot;hljs-attr&quot;&gt;spec:&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;ports:&lt;/span&gt;  &lt;span class=&quot;hljs-bullet&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;hljs-attr&quot;&gt;name:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;http&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;port:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;80&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;protocol:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;TCP&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;targetPort:&lt;/span&gt; &lt;span class=&quot;hljs-number&quot;&gt;8080&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;selector:&lt;/span&gt;    &lt;span class=&quot;hljs-attr&quot;&gt;app:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;kubernetes-pods-extractor&lt;/span&gt;  &lt;span class=&quot;hljs-attr&quot;&gt;type:&lt;/span&gt; &lt;span class=&quot;hljs-string&quot;&gt;ClusterIP&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;h1 id=&quot;heading-step-5-we-are-done&quot;&gt;Step 5: We are done!&lt;/h1&gt;&lt;p&gt;Everthing is ready for us to access our application. All we need is to run:&lt;/p&gt;&lt;p&gt;&lt;code&gt;kubectl port-forward svc/kubernetes-pods-extractor-service 8081:80 -n kubernetes-pods-extractor&lt;/code&gt;&lt;/p&gt;&lt;p&gt;Access &lt;code&gt;http://localhost:8081&lt;/code&gt; and behold!&lt;/p&gt;&lt;p&gt;&lt;img src=&quot;https://cdn.hashnode.com/res/hashnode/image/upload/v1662470071490/T7KIN00Ha.png&quot; alt=&quot;Screenshot from 2022-07-12 11-13-46.png&quot; /&gt;&lt;/p&gt;&lt;p&gt;If we wish to expose this service to (potentially) the entire world, we can do so using an Ingress object, but this is a topic for our next post :)&lt;/p&gt;]]&gt;</hashnode:content><hashnode:coverImage>https://cdn.hashnode.com/res/hashnode/image/upload/v1662884499032/f-Gc4dSsO.png</hashnode:coverImage></item><item><title><![CDATA[What is this?]]></title><description><![CDATA[The most interesting aspect of DevOps is the fact that if you ask 3 different DevOps engineers what DevOps is, you will probably receive 5 different answers.
There are more philosophies around DevOps than I could count, and most of them are correct b...]]></description><link>https://devoops.blog/what-is-this</link><guid isPermaLink="true">https://devoops.blog/what-is-this</guid><dc:creator><![CDATA[Shay Ulmer]]></dc:creator><pubDate>Tue, 06 Sep 2022 09:13:43 GMT</pubDate><content:encoded>&lt;![CDATA[&lt;p&gt;The most interesting aspect of DevOps is the fact that if you ask 3 different DevOps engineers what DevOps is, you will probably receive 5 different answers.There are more philosophies around DevOps than I could count, and most of them are correct because DevOps is actually a large mix of tools, methodologies, ideas and views.&lt;/p&gt;&lt;p&gt;Some say its a form of Software Engineering, but I personally believe that DevOps is a lot about bodging. You have to learn how to pry, twist and bend new tools and technologies for your needs. As the name of the blog suggests  this involves trying (and failing) a lot until things work out your way.&lt;/p&gt;&lt;p&gt;You have to have the agility to adapt to new technologies and tools fairly fast, and if youre a busy DevOps engineer, youll soon learn that diving deep into different stuff is going to be a lot harder once you hold an entire set of tools and infrastructures. You have to be very knowledgeable in every aspect of your work, and in other peoples work as well  which can be very challenging.&lt;/p&gt;&lt;p&gt;Your main ambition as a DevOps engineer should be helping your Dev team achieve better results faster while making sure your workload runs reliably. Theres an entire ecosystem you must design, build and maintain to achieve this goal (Virtualization\ Containerization\Orchestration, Monitoring\Alerting\Logging, CI/CD and more).&lt;/p&gt;&lt;p&gt;This blog will contain my personal experience with various technical challenges and how I solved them. My bodge solutions may not work for every case, but they may help understanding some concepts.&lt;/p&gt;]]&gt;</content:encoded><hashnode:content>&lt;![CDATA[&lt;p&gt;The most interesting aspect of DevOps is the fact that if you ask 3 different DevOps engineers what DevOps is, you will probably receive 5 different answers.There are more philosophies around DevOps than I could count, and most of them are correct because DevOps is actually a large mix of tools, methodologies, ideas and views.&lt;/p&gt;&lt;p&gt;Some say its a form of Software Engineering, but I personally believe that DevOps is a lot about bodging. You have to learn how to pry, twist and bend new tools and technologies for your needs. As the name of the blog suggests  this involves trying (and failing) a lot until things work out your way.&lt;/p&gt;&lt;p&gt;You have to have the agility to adapt to new technologies and tools fairly fast, and if youre a busy DevOps engineer, youll soon learn that diving deep into different stuff is going to be a lot harder once you hold an entire set of tools and infrastructures. You have to be very knowledgeable in every aspect of your work, and in other peoples work as well  which can be very challenging.&lt;/p&gt;&lt;p&gt;Your main ambition as a DevOps engineer should be helping your Dev team achieve better results faster while making sure your workload runs reliably. Theres an entire ecosystem you must design, build and maintain to achieve this goal (Virtualization\ Containerization\Orchestration, Monitoring\Alerting\Logging, CI/CD and more).&lt;/p&gt;&lt;p&gt;This blog will contain my personal experience with various technical challenges and how I solved them. My bodge solutions may not work for every case, but they may help understanding some concepts.&lt;/p&gt;]]&gt;</hashnode:content><hashnode:coverImage>https://cdn.hashnode.com/res/hashnode/image/upload/v1662456423613/-LpCDcUWD.png</hashnode:coverImage></item></channel></rss>