<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4912226416400969049</id><updated>2026-08-07T12:53:08.306+01:00</updated><category term="DevOps"/><category term="Software Engineering"/><category term="Linux"/><category term="Ubuntu"/><category term="AWS"/><category term="Kubernetes"/><category term="C++"/><category term="Docker"/><category term="Data Science"/><category term="Terraform"/><category term="Python"/><category term="Go"/><category term="Machine Learning"/><category term="C#"/><category term="JavaScript"/><category term="Networking"/><category term="Windows"/><category term="AWS EKS"/><category term="Elasticsearch"/><category term="Computer Vision"/><category term="Security"/><category term="MFC"/><category term="MySQL"/><category term="Unix"/><category term="AWS EC2"/><category term="Interview"/><category term="Node.js"/><category term="VSCode"/><category term="Visual Studio"/><category term="Amazon Elastic Kubernetes Service"/><category term="GUI"/><category term="NSIS"/><category term="DB"/><category term="Design Patterns"/><category term="IIS"/><category term="Jetson"/><category term="SSH"/><category term="TypeScript"/><category term="VirtualBox"/><category term="Async"/><category term="Computer architecture"/><category term="DNS"/><category term="Load Balancing"/><category term="STL"/><category term="git"/><category term="kubectl"/><category term="AI"/><category term="AWS IAM"/><category term="Anaconda"/><category term="Ansible"/><category term="Axis2C"/><category term="Helm"/><category term="Observability"/><category term="System Architecture"/><category term="Bash"/><category term="GCP"/><category term="GitHub"/><category term="Google Cloud"/><category term="Monitoring"/><category term="NVIDIA"/><category term="PostgreSQL"/><category term="Web Services"/><category term="conda"/><category term="logging"/><category term="makefile"/><category term="video"/><category term=".NET"/><category term="AWS EC2 Auto scaling"/><category term="Autoscaling"/><category term="ELK"/><category term="GNU"/><category term="Grafana"/><category term="HTTP"/><category term="Karpenter"/><category term="Mac"/><category term="Metrics"/><category term="Minikube"/><category term="OIDC"/><category term="OpenCV"/><category term="Prometheus"/><category term="Rollup.js"/><category term="System Design"/><category term="Unit Testing"/><category term="Virtualenv"/><category term="WCF"/><category term="environment variables"/><category term="multithreading"/><category term="osx"/><category term="string"/><category term=".NET Core"/><category term="AWS EBS"/><category term="AWS S3"/><category term="Agentic AI"/><category term="Babel"/><category term="Batch file"/><category term="Boost"/><category term="Coursera"/><category term="DB dump"/><category term="DynamoDB"/><category term="EKS"/><category term="ESLint"/><category term="Firewall"/><category term="Hardware"/><category term="IP"/><category term="JSON"/><category term="JetPack"/><category term="Jetson Nano"/><category term="Jupyter Notebook"/><category term="Kibana"/><category term="Lambda"/><category term="MacOS"/><category term="NMAKE"/><category term="OpenID"/><category term="R"/><category term="SQL"/><category term="TCP/IP"/><category term="TDD"/><category term="Unicode"/><category term="Wi-Fi"/><category term="Wireshark"/><category term="XML"/><category term="XML Data Binding"/><category term="YAML"/><category term="cryptography"/><category term="docker-compose"/><category term="g++"/><category term="make"/><category term="nvm"/><category term="AWS EFS"/><category term="AWS ELB"/><category term="AWS Lambda"/><category term="AWS RDS"/><category term="AWS SQS"/><category term="AWS VPC"/><category term="Amazon GuardDuty"/><category term="Amazon Kinesis"/><category term="C"/><category term="CAS"/><category term="CI/CD"/><category term="CMake"/><category term="CUDA"/><category term="Claude Code"/><category term="Cloud Storage"/><category term="CloudWatch"/><category term="Cloudflare"/><category term="Confluence"/><category term="DOS"/><category term="DVD"/><category term="Debugging"/><category term="Deployment"/><category term="Doxygen"/><category term="ECK"/><category term="Endianness"/><category term="Filesystem"/><category term="GPU"/><category term="GParted"/><category term="GitHub Actions"/><category term="Graphviz"/><category term="Hacking"/><category term="IPv6"/><category term="Image Segmentation"/><category term="Ingress"/><category term="JupyterLab"/><category term="LLM"/><category term="Linter"/><category term="Meld"/><category term="Merge"/><category term="Microservices"/><category term="MySQL Workbench"/><category term="NUnit"/><category term="Neural Networks"/><category term="Nmap"/><category term="OOP"/><category term="Object Detection"/><category term="PHP"/><category term="Packer"/><category term="Pingdom"/><category term="QA"/><category term="RStudio"/><category term="Redis"/><category term="Resources"/><category term="SOAP"/><category term="Serverless"/><category term="TPL"/><category term="TeamCity"/><category term="TensorFlow"/><category term="Tools"/><category term="VPC"/><category term="VS Code"/><category term="Vagrant"/><category term="Virtualenvwrapper"/><category term="Vue.js"/><category term="Web Applications"/><category term="Web Server"/><category term="Yarn"/><category term="cron"/><category term="crontab"/><category term="curl"/><category term="gdb"/><category term="mysqlsh"/><category term="npm"/><category term="psql"/><category term="scp"/><category term="sed"/><category term="vi"/><category term="wget"/><category term="ACL"/><category term="AI Agents"/><category term="AKS"/><category term="API Proxy"/><category term="AWS AMI"/><category term="AWS CLI"/><category term="AWS CodeDeploy"/><category term="AWS ECS"/><category term="AWS Fargate"/><category term="AWS IAM Identity Center"/><category term="AWS S2S VPN"/><category term="AWS SNS"/><category term="AWS Secrets Manager"/><category term="Access Control List"/><category term="Agile"/><category term="Algorithms"/><category term="Alpine"/><category term="Altinity"/><category term="Amazon API Gateway"/><category term="Amazon Bedrock"/><category term="Amazon CloudWatch"/><category term="Amazon ElastiCache"/><category term="Amazon Kinesis Data Streams"/><category term="Amazon Linux"/><category term="Amazon RDS"/><category term="ArgoCD"/><category term="Axios"/><category term="Azure Kubernetes Service"/><category term="Backup"/><category term="Bazel"/><category term="Big  O Notation"/><category term="BitLocker"/><category term="Browser"/><category term="Browserify"/><category term="CDN"/><category term="CI"/><category term="COM"/><category term="CRD"/><category term="CRI"/><category term="Caffe"/><category term="Checkly"/><category term="Chromium"/><category term="Claude"/><category term="Clickhouse"/><category term="Cloud"/><category term="Cloud CDN"/><category term="Cloud DNS"/><category term="CloudFront"/><category term="Cluster Autoscaler"/><category term="CodeSynthesis"/><category term="Coding Practices"/><category term="Compute Engine"/><category term="Containerization"/><category term="CronJob"/><category term="Cygwin"/><category term="DLL"/><category term="Data Binding"/><category term="DevSecOps"/><category term="Diff"/><category term="Disaster Recovery"/><category term="EBS"/><category term="Eclipse"/><category term="Fast R-CNN"/><category term="Faster R-CNN"/><category term="Fiddler"/><category term="Freeware"/><category term="Frontend"/><category term="GKE"/><category term="GPG"/><category term="GStreamer"/><category term="GTK"/><category term="GeminiAI"/><category term="Gimp"/><category term="Ginkgo"/><category term="GitHub CLI"/><category term="GitOps"/><category term="Google"/><category term="Google Colab"/><category term="Google Kubernetes Engine"/><category term="Grafana Loki"/><category term="Gulp"/><category term="Guththila"/><category term="H2O"/><category term="HCP"/><category term="HPA"/><category term="HandBrake"/><category term="HashiCorp Cloud Provider"/><category term="Horizontal Pod Autoscaler"/><category term="IAM"/><category term="IAM Identity Center"/><category term="IEEE802.11"/><category term="INetCfg"/><category term="IPv4"/><category term="IaaS"/><category term="Idiom"/><category term="Image Classification"/><category term="Image Processing"/><category term="Image Recognition"/><category term="Image editor"/><category term="ImageMagick"/><category term="Instance Segmentation"/><category term="Integration Testing"/><category term="JIRA"/><category term="JQL"/><category term="JSON Spirit"/><category term="Jest"/><category term="Job"/><category term="Kali Linux"/><category term="Krew"/><category term="KubePug"/><category term="Kubent"/><category term="LAMP"/><category term="Linker"/><category term="Logstash"/><category term="MCP"/><category term="Meetup"/><category term="Memory dump"/><category term="MongoDB"/><category term="NAT"/><category term="NVIDIA DIGITS"/><category term="Ncat"/><category term="NetBIOS"/><category term="Nginx"/><category term="Notepad++"/><category term="Object Localization"/><category term="Object Recognition"/><category term="OpenMetrics"/><category term="OpenVpn"/><category term="Overfeat"/><category term="PKA"/><category term="PaaS"/><category term="Pluto"/><category term="Port forwarding"/><category term="Postman"/><category term="Proxy"/><category term="PyDev"/><category term="PyInstaller"/><category term="PyWin32"/><category term="Qt"/><category term="R-CNN"/><category term="RawCap"/><category term="Refactoring"/><category term="RegEx"/><category term="RequireJS"/><category term="Restore"/><category term="Router"/><category term="S3"/><category term="SAML"/><category term="SMB"/><category term="SOA"/><category term="SRE"/><category term="SSD"/><category term="SSO"/><category term="Scrapy"/><category term="Security Group"/><category term="Semantic Segmentation"/><category term="Site24x7"/><category term="Sliding Window"/><category term="Software"/><category term="SourceTree"/><category term="Static Code Analysis"/><category term="Stored Procedure"/><category term="TAP"/><category term="TSLint"/><category term="Taskbar"/><category term="TensorRT"/><category term="Terminal"/><category term="Terraform Cloud"/><category term="Tray icon"/><category term="UAC"/><category term="UML"/><category term="Unsupervised Machine Learning"/><category term="V8"/><category term="VLC"/><category term="VPN"/><category term="Velero"/><category term="VertexAI"/><category term="Viber"/><category term="Virtualization"/><category term="WAF"/><category term="WSDL"/><category term="Web scraping"/><category term="WinDbg"/><category term="Windows API"/><category term="Xerces"/><category term="YOLO"/><category term="airmon-ng"/><category term="brew"/><category term="cAdvisor"/><category term="chmod"/><category term="chown"/><category term="containerd"/><category term="ffmpeg"/><category term="functor"/><category term="gSOAP"/><category term="gcc"/><category term="gh"/><category term="heredoc"/><category term="jq"/><category term="kube-state-metrics"/><category term="kubeadm"/><category term="manipulator"/><category term="map"/><category term="ngrok"/><category term="node-gyp"/><category term="open-source"/><category term="pgAdmin"/><category term="ping"/><category term="pip"/><category term="pip3"/><category term="read"/><category term="tar"/><category term="templates"/><category term="terraform-docs"/><category term="vim"/><category term="webcam"/><category term="wxPython"/><category term="yq"/><title type='text'>My Public Notepad</title><subtitle type='html'>Bits and bobs about computers and programming</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>538</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-548238883294702384</id><published>2026-08-07T12:53:08.305+01:00</published><updated>2026-08-07T12:53:08.306+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="DNS"/><category scheme="http://www.blogger.com/atom/ns#" term="GitHub"/><title type='text'>Splitting One Domain Across GitHub Pages and Blogger: A Complete DNS Setup Guide</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi4v4Hz7SDJFunh84iYlHmuzgu9D1mXV6ULhkZEZGlEmr0kuA4ugmW44MPAldZ5zygH3u-Sn6cIg3Ec_iN0VjDZqvYjUIDfnoFa5JK81vaTuYZdJpHyFOAmUI6iss9kV89YWaIte5HWyp6UzyDyOQzzB8FVTfNNkJU3_OZ93srPM7kWFWQWjGEtzPF/s512/dns.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;512&quot; data-original-width=&quot;512&quot; height=&quot;400&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi4v4Hz7SDJFunh84iYlHmuzgu9D1mXV6ULhkZEZGlEmr0kuA4ugmW44MPAldZ5zygH3u-Sn6cIg3Ec_iN0VjDZqvYjUIDfnoFa5JK81vaTuYZdJpHyFOAmUI6iss9kV89YWaIte5HWyp6UzyDyOQzzB8FVTfNNkJU3_OZ93srPM7kWFWQWjGEtzPF/w400-h400/dns.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h1 class=&quot;mt-3 -mb-1 text-[1.375rem] font-bold&quot; data-sourcepos=&quot;1:1-1:83;0-82&quot; dir=&quot;ltr&quot;&gt;&lt;br /&gt;&lt;/h1&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;3:1-3:416;84-499&quot; dir=&quot;ltr&quot;&gt;If you own a single domain but want to run two separate things on it — say, a static landing page on GitHub Pages at the &lt;b&gt;root domain&lt;/b&gt;, and a blog on Blogger at a &lt;b&gt;subdomain&lt;/b&gt; — you&#39;ll quickly discover that the actual mechanics of DNS, custom domain verification, and HTTPS certificate provisioning are scattered across three different systems that don&#39;t talk to each other and don&#39;t always explain their errors clearly.&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;5:1-5:219;501-719&quot; dir=&quot;ltr&quot;&gt;This post documents the full setup, end to end: what records you need, what each provider actually requires, and the specific errors you&#39;re likely to hit along the way (all of which I hit myself while setting this up).&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;7:1-7:26;721-746&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;The goal, concretely:&lt;/strong&gt;&lt;/p&gt;

&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;8:1-10:69;747-1018&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;8:1-8:82;747-828&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;example.com&lt;/code&gt; (the apex/root domain) → a static site hosted on &lt;strong&gt;GitHub Pages&lt;/strong&gt;&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;9:1-9:121;829-949&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;blog.example.com&lt;/code&gt; (a subdomain) → a &lt;strong&gt;Blogger&lt;/strong&gt; blog, with its original &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;xxxx.blogspot.com&lt;/code&gt; address redirecting to it&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;10:1-10:69;950-1018&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www.example.com&lt;/code&gt; → optionally also works, redirecting to the apex&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;12:1-12:94;1020-1113&quot; dir=&quot;ltr&quot;&gt;I&#39;ll use &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;bojankomazec.com&lt;/code&gt; as the running example, since that&#39;s the domain I set this up on.&lt;/p&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;16:1-16:49;1120-1168&quot; dir=&quot;ltr&quot;&gt;1. The Big Picture: Three Systems, One Domain&lt;/h2&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;18:1-18:103;1170-1272&quot; dir=&quot;ltr&quot;&gt;Before touching any settings, it helps to understand the three layers involved and what each one owns:&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;18:1-18:103;1170-1272&quot; dir=&quot;ltr&quot;&gt;&lt;br /&gt;&lt;/p&gt;

&lt;div class=&quot;overflow-x-auto w-full px-2 mb-6 print:overflow-x-visible&quot; data-sourcepos=&quot;20:1-24:160;1274-1909&quot; dir=&quot;ltr&quot;&gt;&lt;table class=&quot;min-w-full border-collapse text-sm leading-[1.7] whitespace-normal&quot;&gt;&lt;thead class=&quot;text-left&quot;&gt;&lt;tr&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;Layer&lt;br /&gt;&lt;br /&gt;&lt;/th&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;What it controls&lt;br /&gt;&lt;br /&gt;&lt;/th&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;Where you configure it&lt;br /&gt;&lt;br /&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;strong&gt;DNS registrar&lt;/strong&gt; (e.g. GoDaddy, Namecheap, Cloudflare)&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Which records exist for your domain — A, CNAME, MX, etc. This is the actual &quot;phone book&quot; that tells the internet where to send traffic.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Your registrar&#39;s DNS management panel&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;strong&gt;GitHub Pages&lt;br /&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Which domain(s) it will accept and serve your static site for, and whether it issues an HTTPS certificate for them&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Repository → Settings → Pages&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;strong&gt;Blogger&lt;/strong&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Which domain(s) it will accept and serve your blog for, and whether it issues an HTTPS certificate for them&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Blogger → Settings → Publishing&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;26:1-26:418;1911-2328&quot; dir=&quot;ltr&quot;&gt;The critical thing to understand: &lt;strong&gt;DNS records alone are not enough.&lt;/strong&gt; Both GitHub Pages and Blogger require you to &lt;em&gt;also&lt;/em&gt; tell them, inside their own settings, which &lt;u&gt;custom domain&lt;/u&gt; you&#39;re using. If DNS points to them but their own settings don&#39;t know about your domain, you&#39;ll get connection errors — not because DNS is wrong, but because the receiving service doesn&#39;t recognize the incoming hostname and refuses it.&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;28:1-28:240;2330-2569&quot; dir=&quot;ltr&quot;&gt;This is the single most common source of confusion in this kind of setup: DNS can be 100% correct and the site can still fail to load, because DNS and application-level domain configuration are two separate steps that both have to be done.&lt;/p&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;32:1-32:30;2576-2605&quot; dir=&quot;ltr&quot;&gt;2. DNS Records You&#39;ll Need&lt;/h2&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;34:1-34:185;2607-2791&quot; dir=&quot;ltr&quot;&gt;Here&#39;s the full set, using &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;bojankomazec.com&lt;/code&gt; as the example domain, &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;blog&lt;/code&gt; as the subdomain, and &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;bojankomazec&lt;/code&gt; as the GitHub username (so the Pages repo is &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;bojankomazec.github.io&lt;/code&gt;).&lt;/p&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;36:1-36:31;2793-2823&quot; dir=&quot;ltr&quot;&gt;Apex domain → GitHub Pages&lt;/h3&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;38:1-38:324;2825-3148&quot; dir=&quot;ltr&quot;&gt;DNS doesn&#39;t allow a &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt; record at the apex/root of a domain (technically, because the apex must also hold other record types like &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;SOA&lt;/code&gt; and often &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;MX&lt;/code&gt;, and a hostname can&#39;t mix &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt; with anything else). So GitHub Pages publishes a fixed set of IP addresses, and you point the apex at those directly with &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;A&lt;/code&gt; records:&lt;/p&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;40:1-45:4;3150-3337&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;Type: A    Host: @    Value: 185.199.108.153
Type: A    Host: @    Value: 185.199.109.153
Type: A    Host: @    Value: 185.199.110.153
Type: A    Host: @    Value: 185.199.111.153&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;47:1-47:189;3339-3527&quot; dir=&quot;ltr&quot;&gt;(&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;@&lt;/code&gt; is the conventional way most DNS panels represent &quot;the apex/root domain itself.&quot;) If your provider supports IPv6 and you want dual-stack support, GitHub also publishes &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;AAAA&lt;/code&gt; records:&lt;/p&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;49:1-54:4;3529-3744&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;Type: AAAA    Host: @    Value: 2606:50c0:8000::153
Type: AAAA    Host: @    Value: 2606:50c0:8001::153
Type: AAAA    Host: @    Value: 2606:50c0:8002::153
Type: AAAA    Host: @    Value: 2606:50c0:8003::153&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;56:1-56:254;3746-3999&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;Important:&lt;/strong&gt; check whether your registrar auto-created a placeholder &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;A&lt;/code&gt; record for &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;@&lt;/code&gt; when the domain was registered (many do, often pointing to a parking page). If so, delete it — a leftover default record will conflict with the four you&#39;re adding.&lt;/p&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;58:1-58:44;4001-4044&quot; dir=&quot;ltr&quot;&gt;www subdomain → GitHub Pages (optional)&lt;/h3&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;60:1-60:101;4046-4146&quot; dir=&quot;ltr&quot;&gt;If you also want &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www.example.com&lt;/code&gt; to work (redirecting to the apex once GitHub verifies both), add:&lt;/p&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;62:1-64:4;4148-4214&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;Type: CNAME    Host: www    Value: bojankomazec.github.io.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;66:1-66:210;4216-4425&quot; dir=&quot;ltr&quot;&gt;Note this points to your &lt;strong&gt;GitHub Pages hostname&lt;/strong&gt; (&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;), not to a URL, and not to the repo&#39;s github.com page. A CNAME value is always a hostname, never a full URL with &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;https://&lt;/code&gt; or a path.&lt;/p&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;68:1-68:29;4427-4455&quot; dir=&quot;ltr&quot;&gt;Blog subdomain → Blogger&lt;/h3&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;70:1-72:4;4457-4516&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;Type: CNAME    Host: blog    Value: ghs.google.com.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;74:1-74:669;4518-5186&quot; dir=&quot;ltr&quot;&gt;This is the one that trips people up: it&#39;s tempting to point this directly at your &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;xxxx.blogspot.com&lt;/code&gt; address, since that&#39;s &quot;where the blog lives.&quot; &lt;strong&gt;Don&#39;t do that.&lt;/strong&gt; Blogger&#39;s custom-domain routing works through a shared Google endpoint (&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ghs.google.com&lt;/code&gt;) that inspects the incoming hostname and routes it to the correct blog based on what&#39;s configured in your Blogger account. Pointing the CNAME straight at your blogspot address sends the request to blogspot-specific infrastructure that doesn&#39;t know how to handle a request arriving under a different hostname — the connection gets abruptly closed rather than serving anything (&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ERR_CONNECTION_CLOSED&lt;/code&gt; in Chrome).&lt;/p&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;76:1-76:38;5188-5225&quot; dir=&quot;ltr&quot;&gt;Blogger domain verification CNAME&lt;/h3&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;78:1-78:247;5227-5473&quot; dir=&quot;ltr&quot;&gt;Separately from the routing CNAME above, Blogger needs to confirm you actually own the subdomain before it&#39;ll accept it. When you enter your custom domain into Blogger&#39;s settings, it will generate a &lt;strong&gt;unique verification CNAME&lt;/strong&gt; — something like:&lt;/p&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;80:1-82:4;5475-5569&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;Type: CNAME    Host: &amp;lt;random-string&amp;gt;    Value: gv-&amp;lt;random-string&amp;gt;.dv.googlehosted.com.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;84:1-84:236;5571-5806&quot; dir=&quot;ltr&quot;&gt;This value is generated per-domain and per-account — you can&#39;t predict or reuse it, you have to copy it exactly from Blogger&#39;s settings screen after entering your custom domain. Add it as its own DNS record alongside the routing CNAME.&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;86:1-86:303;5808-6110&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;Gotcha:&lt;/strong&gt; if you ever change which subdomain Blogger is using (e.g. you originally set it up as &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www.example.com&lt;/code&gt; and later switch to &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;blog.example.com&lt;/code&gt;), the &lt;em&gt;old&lt;/em&gt; verification CNAME becomes stale and should be deleted — Blogger will issue a &lt;em&gt;new&lt;/em&gt; one specific to the new subdomain when you re-save.&lt;/p&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;88:1-88:28;6112-6139&quot; dir=&quot;ltr&quot;&gt;Full example record set&lt;/h3&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;90:1-98:4;6141-6408&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;A       @        185.199.108.153
A       @        185.199.109.153
A       @        185.199.110.153
A       @        185.199.111.153
CNAME   www      bojankomazec.github.io.
CNAME   blog     ghs.google.com.
CNAME   &amp;lt;verify&amp;gt; gv-xxxxxxxxxxxx.dv.googlehosted.com.&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;102:1-102:28;6415-6442&quot; dir=&quot;ltr&quot;&gt;3. The GitHub Pages Side&lt;/h2&gt;

&lt;ol class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;104:1-108:150;6444-7536&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;104:1-104:74;6444-6517&quot;&gt;In your &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt; repository, go to &lt;strong&gt;Settings → Pages&lt;/strong&gt;.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;105:1-105:104;6518-6621&quot;&gt;Under &lt;strong&gt;Custom domain&lt;/strong&gt;, enter your apex domain (e.g. &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;bojankomazec.com&lt;/code&gt; — no &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www&lt;/code&gt;, no &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;https://&lt;/code&gt;).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;106:1-106:249;6622-6870&quot;&gt;Save. GitHub will write a file literally named &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt; (no extension) into the root of your repository, containing just the domain name. This file is how GitHub Pages knows which domain to serve your content for — DNS alone doesn&#39;t tell it this.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;107:1-107:516;6871-7386&quot;&gt;GitHub will attempt to verify your DNS. If your &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;A&lt;/code&gt; records are already correct, this usually succeeds quickly. If you see a &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;DNS valid for primary&lt;/code&gt; message alongside a separate &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www.example.com is improperly configured&lt;/code&gt; warning — that&#39;s normal. The &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www&lt;/code&gt; check is a secondary, automatic check GitHub runs to see if it can also offer &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www&lt;/code&gt; support and a unified certificate; it doesn&#39;t block your primary domain from working. If your primary domain error clears and only the &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www&lt;/code&gt; one remains, your site is live.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;108:1-108:150;7387-7536&quot;&gt;Once verified, tick &lt;strong&gt;Enforce HTTPS&lt;/strong&gt; (this option is often greyed out until GitHub finishes issuing a certificate — see the HTTPS section below).&lt;/li&gt;
&lt;/ol&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;112:1-112:23;7543-7565&quot; dir=&quot;ltr&quot;&gt;4. The Blogger Side&lt;/h2&gt;

&lt;ol class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;114:1-120:243;7567-8406&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;114:1-114:48;7567-7614&quot;&gt;In Blogger, go to &lt;strong&gt;Settings → Publishing&lt;/strong&gt;.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;115:1-115:86;7615-7700&quot;&gt;Under &lt;strong&gt;Custom domain&lt;/strong&gt;, enter your blog subdomain (e.g. &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;blog.bojankomazec.com&lt;/code&gt;).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;116:1-116:130;7701-7830&quot;&gt;Save. Blogger will likely reject the save the first time and show you the verification CNAME described above. Copy it exactly.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;117:1-117:89;7831-7919&quot;&gt;Add both the routing CNAME (&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ghs.google.com&lt;/code&gt;) and the verification CNAME to your DNS.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;118:1-118:110;7920-8029&quot;&gt;Wait for propagation (see below), then go back and save again in Blogger. It should now accept the domain.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;119:1-119:134;8030-8163&quot;&gt;Check the &lt;strong&gt;Redirect domain&lt;/strong&gt; setting on the same page carefully — see the dedicated warning about this below, it&#39;s a common trap.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;120:1-120:243;8164-8406&quot;&gt;Look for an &lt;strong&gt;HTTPS availability&lt;/strong&gt; toggle further down the page. It usually reads &quot;Status: Unknown&quot; for a while after you first set up the domain — this is Blogger&#39;s certificate issuance still in progress, not an error (see HTTPS section).&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;122:1-122:31;8408-8438&quot; dir=&quot;ltr&quot;&gt;The &quot;Redirect domain&quot; trap&lt;/h3&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;124:1-124:550;8440-8989&quot; dir=&quot;ltr&quot;&gt;Blogger&#39;s Publishing settings include a &lt;strong&gt;&quot;Redirect domain&quot;&lt;/strong&gt; option, something like &lt;em&gt;&quot;Redirect &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;example.com&lt;/code&gt; to &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;blog.example.com&lt;/code&gt;.&quot;&lt;/em&gt; This setting, if enabled, makes Blogger redirect your &lt;strong&gt;apex domain&lt;/strong&gt; to your blog. If your apex domain is meant to serve something else entirely (like a GitHub Pages landing page, as in this setup), this option will silently hijack it — visitors to &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;example.com&lt;/code&gt; will get redirected to your blog instead of seeing your landing page, even though your DNS and GitHub Pages configuration are both completely correct.&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;126:1-126:467;8991-9457&quot; dir=&quot;ltr&quot;&gt;This setting can also change target without obviously telling you: if you initially set up Blogger&#39;s custom domain as &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www.example.com&lt;/code&gt; and later switch it to &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;blog.example.com&lt;/code&gt;, the &quot;Redirect domain&quot; checkbox may still be checked, just silently re-pointed at whatever your current custom domain is. &lt;strong&gt;If you don&#39;t want your apex domain touched by Blogger at all, make sure this is switched off&lt;/strong&gt;, and re-check it any time you change Blogger&#39;s custom domain setting.&lt;/p&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;130:1-130:51;9464-9514&quot; dir=&quot;ltr&quot;&gt;5. HTTPS and Certificates: Why It Takes a While&lt;/h2&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;132:1-132:227;9516-9742&quot; dir=&quot;ltr&quot;&gt;Both GitHub Pages and Blogger use automated certificate issuance (GitHub uses Let&#39;s Encrypt; Google&#39;s infrastructure handles Blogger&#39;s) that kicks in &lt;em&gt;after&lt;/em&gt; your domain passes DNS verification — not before, and not instantly.&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;134:1-134:33;9744-9776&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;What this means in practice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;135:1-136:87;9777-10099&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;135:1-135:236;9777-10012&quot;&gt;Your site can be fully reachable over plain &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;http://&lt;/code&gt; while &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;https://&lt;/code&gt; still fails, sometimes with a confusing certificate error, for anywhere from a few minutes to around 24–48 hours after you first set up or change a custom domain.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;136:1-136:87;10013-10099&quot;&gt;The specific error you&#39;ll likely see in Chrome during this window is something like:&lt;/li&gt;
&lt;/ul&gt;

&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; data-sourcepos=&quot;137:1-142:4;10100-10281&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;  Your connection is not private
  net::ERR_CERT_COMMON_NAME_INVALID
  This server could not prove that it is www.example.com;
  its security certificate is from *.github.io&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;143:3-143:283;10284-10564&quot; dir=&quot;ltr&quot;&gt;This happens because the connection &lt;em&gt;is&lt;/em&gt; reaching GitHub&#39;s infrastructure, but GitHub hasn&#39;t yet issued a certificate specifically covering your hostname, so it falls back to serving its generic wildcard certificate — which your browser correctly flags as invalid for your domain.&lt;/p&gt;

&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;144:1-145:97;10565-10828&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;144:1-144:167;10565-10731&quot;&gt;&lt;strong&gt;Don&#39;t repeatedly remove and re-add the custom domain&lt;/strong&gt; while waiting — each change can reset the certificate issuance queue and make the wait longer, not shorter.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;145:1-145:97;10732-10828&quot;&gt;Once issuance completes, &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;https://&lt;/code&gt; starts working with no further action needed on your part.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 class=&quot;mt-2 -mb-1 text-base font-bold&quot; data-sourcepos=&quot;147:1-147:64;10830-10893&quot; dir=&quot;ltr&quot;&gt;A red herring worth knowing about: antivirus HTTPS scanning&lt;/h3&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;149:1-149:561;10895-11455&quot; dir=&quot;ltr&quot;&gt;If you inspect your site&#39;s certificate in Chrome and see something unexpected — like the certificate authority being your antivirus software (Avast, Kaspersky, and others do this) rather than Let&#39;s Encrypt or Google — this is &lt;em&gt;not&lt;/em&gt; a sign of a compromised or misconfigured site. Many antivirus suites include an &quot;HTTPS scanning&quot; or &quot;Web Shield&quot; feature that intercepts encrypted connections locally, decrypting and re-encrypting them with a certificate the antivirus generated and installed into your system&#39;s trust store, in order to scan traffic for threats.&lt;/p&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;151:1-151:432;11457-11888&quot; dir=&quot;ltr&quot;&gt;This substitution happens for &lt;em&gt;every&lt;/em&gt; HTTPS site you visit, not just yours, so it&#39;s not diagnostic of anything wrong with your setup. To see the actual certificate your host issued, either temporarily disable the antivirus&#39;s HTTPS scanning feature, or check from an external tool that doesn&#39;t go through your local machine at all — such as &lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.ssllabs.com/ssltest/&quot;&gt;SSL Labs&#39; SSL Test&lt;/a&gt;, which queries your server directly.&lt;/p&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;155:1-155:30;11895-11924&quot; dir=&quot;ltr&quot;&gt;6. Troubleshooting Toolkit&lt;/h2&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;157:1-157:69;11926-11994&quot; dir=&quot;ltr&quot;&gt;A handful of tools make this whole process far less guess-and-check:&lt;/p&gt;

&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;159:1-162:163;11996-12701&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;159:1-159:227;11996-12222&quot;&gt;&lt;strong&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;dig &amp;lt;domain&amp;gt; &amp;lt;record-type&amp;gt;&lt;/code&gt;&lt;/strong&gt; (Mac/Linux, or Windows via WSL) — queries DNS directly and shows you exactly what&#39;s published, bypassing any local caching. E.g. &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;dig bojankomazec.com A&lt;/code&gt; or &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;dig blog.bojankomazec.com CNAME&lt;/code&gt;.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;160:1-160:66;12223-12288&quot;&gt;&lt;strong&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;nslookup &amp;lt;domain&amp;gt;&lt;/code&gt;&lt;/strong&gt; — similar, built into Windows natively.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;161:1-161:250;12289-12538&quot;&gt;&lt;strong&gt;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://dnschecker.org&quot;&gt;dnschecker.org&lt;/a&gt;&lt;/strong&gt; — checks DNS propagation across many resolvers worldwide simultaneously, which is invaluable for figuring out whether a record change has actually rolled out everywhere yet, or is still mid-propagation.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;162:1-162:163;12539-12701&quot;&gt;&lt;strong&gt;&lt;a class=&quot;underline underline underline-offset-2 decoration-1 decoration-current/40 hover:decoration-current focus:decoration-current&quot; href=&quot;https://www.ssllabs.com/ssltest/&quot;&gt;SSL Labs SSL Test&lt;/a&gt;&lt;/strong&gt; — inspects a site&#39;s actual certificate chain independent of your local browser/antivirus interference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;164:1-164:60;12703-12762&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;General debugging approach when something doesn&#39;t load:&lt;/strong&gt;&lt;/p&gt;

&lt;ol class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-decimal flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; data-sourcepos=&quot;165:1-168:178;12763-13306&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;165:1-165:77;12763-12839&quot;&gt;Confirm the DNS record itself is correct and propagated (dnschecker.org).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;166:1-166:121;12840-12960&quot;&gt;Confirm the &lt;em&gt;receiving service&lt;/em&gt; (GitHub Pages / Blogger) has the domain configured in its own settings, not just DNS.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;167:1-167:168;12961-13128&quot;&gt;Try plain &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;http://&lt;/code&gt; before &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;https://&lt;/code&gt; — this isolates whether the problem is routing/configuration (affects both) or just certificate issuance (only affects https).&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot; data-sourcepos=&quot;168:1-168:178;13129-13306&quot;&gt;If a certificate error shows an unexpected issuer (like an antivirus name instead of Let&#39;s Encrypt/Google), suspect local HTTPS interception before suspecting your DNS setup.&lt;/li&gt;
&lt;/ol&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;172:1-172:48;13313-13360&quot; dir=&quot;ltr&quot;&gt;7. Common Errors and What They Actually Mean&lt;/h2&gt;

&lt;div class=&quot;overflow-x-auto w-full px-2 mb-6 print:overflow-x-visible&quot; data-sourcepos=&quot;174:1-181:154;13362-14553&quot; dir=&quot;ltr&quot;&gt;&lt;table class=&quot;min-w-full border-collapse text-sm leading-[1.7] whitespace-normal&quot;&gt;&lt;thead class=&quot;text-left&quot;&gt;&lt;tr&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;Error&lt;br /&gt;&lt;br /&gt;&lt;/th&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;What it usually means&lt;br /&gt;&lt;br /&gt;&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;InvalidCNAMEError&lt;/code&gt; (GitHub)&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;The named subdomain&#39;s CNAME doesn&#39;t point where GitHub expects, &lt;strong&gt;or&lt;/strong&gt; DNS hasn&#39;t propagated to the resolver GitHub&#39;s checker used yet. Verify with dnschecker.org before assuming misconfiguration.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;NotServedByPagesError&lt;/code&gt; (GitHub)&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;DNS looks fine, but the domain isn&#39;t yet resolving to GitHub Pages from GitHub&#39;s own check — often transient/propagation-related.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ERR_CERT_COMMON_NAME_INVALID&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;The connection reached the right server, but no certificate has been issued for this specific hostname yet — a provisioning delay, not a misconfiguration.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ERR_CONNECTION_CLOSED&lt;/code&gt; (on a Blogger subdomain)&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Usually means the CNAME points somewhere that doesn&#39;t recognize the incoming hostname — most often, pointing directly at a &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.blogspot.com&lt;/code&gt; address instead of &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ghs.google.com&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;A domain unexpectedly redirecting somewhere else entirely&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Check Blogger&#39;s &quot;Redirect domain&quot; setting — it may be silently redirecting your apex domain to your blog.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Certificate issuer shown as an antivirus company&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Local HTTPS scanning/interception by your antivirus software — check via an external tool instead.&lt;br /&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;

&lt;hr class=&quot;border-border-200 border-t-0.5 my-3 mx-1.5&quot; /&gt;
&lt;h2 class=&quot;mt-3 -mb-1 text-[1.125rem] font-bold&quot; data-sourcepos=&quot;185:1-185:24;14560-14583&quot; dir=&quot;ltr&quot;&gt;8. Summary Checklist&lt;/h2&gt;

&lt;ul class=&quot;contains-task-list&quot; data-sourcepos=&quot;187:1-195:171;14585-15466&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;187:1-187:84;14585-14668&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Four &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;A&lt;/code&gt; records for the apex domain, pointing at GitHub Pages&#39; published IPs&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;188:1-188:70;14669-14738&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; (Optional) &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt; for &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;www&lt;/code&gt; pointing at &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;&amp;lt;username&amp;gt;.github.io&lt;/code&gt;&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;189:1-189:118;14739-14856&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt; for your blog subdomain pointing at &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ghs.google.com&lt;/code&gt; (&lt;strong&gt;not&lt;/strong&gt; directly at your &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;.blogspot.com&lt;/code&gt; address)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;190:1-190:85;14857-14941&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Blogger&#39;s unique verification &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt;, copied exactly from its settings screen&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;191:1-191:87;14942-15028&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Custom domain entered in GitHub Pages settings (creates the repo&#39;s &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;CNAME&lt;/code&gt; file)&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;192:1-192:61;15029-15089&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Custom domain entered in Blogger&#39;s Publishing settings&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;193:1-193:119;15090-15208&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Blogger&#39;s &quot;Redirect domain&quot; setting switched off, unless you deliberately want your apex redirected to your blog&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;194:1-194:87;15209-15295&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Patience for HTTPS certificate issuance (minutes to ~48 hours) on both platforms&lt;/li&gt;
&lt;li class=&quot;task-list-item&quot; data-sourcepos=&quot;195:1-195:171;15296-15466&quot;&gt;&lt;input disabled=&quot;&quot; type=&quot;checkbox&quot; /&gt; Verify using &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;http://&lt;/code&gt; first, then &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;https://&lt;/code&gt;, and cross-check unexpected certificate issuers against local antivirus software before assuming a server-side problem&lt;/li&gt;
&lt;/ul&gt;

&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; data-sourcepos=&quot;197:1-197:333;15468-15800&quot; dir=&quot;ltr&quot;&gt;Once all of this settles, you end up with a clean split: your apex domain serving whatever static site or landing page you want full control over, and a subdomain serving your blog — both fully on their own custom domains, both secured with HTTPS, and both surviving independently of where the underlying content is actually hosted.&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/548238883294702384/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/548238883294702384' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/548238883294702384'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/548238883294702384'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/splitting-one-domain-across-github.html' title='Splitting One Domain Across GitHub Pages and Blogger: A Complete DNS Setup Guide'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhi4v4Hz7SDJFunh84iYlHmuzgu9D1mXV6ULhkZEZGlEmr0kuA4ugmW44MPAldZ5zygH3u-Sn6cIg3Ec_iN0VjDZqvYjUIDfnoFa5JK81vaTuYZdJpHyFOAmUI6iss9kV89YWaIte5HWyp6UzyDyOQzzB8FVTfNNkJU3_OZ93srPM7kWFWQWjGEtzPF/s72-w400-h400-c/dns.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-2010096164987042773</id><published>2026-08-05T17:17:41.286+01:00</published><updated>2026-08-05T17:17:41.287+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="kube-state-metrics"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Monitoring"/><category scheme="http://www.blogger.com/atom/ns#" term="OpenMetrics"/><category scheme="http://www.blogger.com/atom/ns#" term="Prometheus"/><title type='text'>kube-state-metrics</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/s1736/Screenshot%202026-08-03%20at%2016.40.36.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1648&quot; data-original-width=&quot;1736&quot; height=&quot;608&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/w640-h608/Screenshot%202026-08-03%20at%2016.40.36.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;&lt;b&gt;kube-state-metrics (KSM)&lt;/b&gt; is an official Kubernetes add-on &lt;u&gt;agent&lt;/u&gt; that listens to the Kubernetes API server and generates Prometheus-format metrics about the state of Kubernetes objects (such as Pods, Deployments, Nodes, StatefulSets, and CronJobs).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unlike agent metrics collectors like cAdvisor or node_exporter, KSM does not measure resource usage (like CPU usage, memory consumption, or disk I/O). Instead, it translates the raw state of objects in the Kubernetes API into structured metrics.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Core Concept: Usage vs. State&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;cAdvisor / Node Exporter (Usage): &quot;How much CPU is Pod X consuming right now?&quot;&lt;/div&gt;&lt;div&gt;kube-state-metrics (State): &quot;How many replicas are ready in Deployment Y?&quot;, &quot;When did CronJob Z last succeed?&quot;, &quot;What phase is Pod A in?&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;How kube-state-metrics Exposes Metrics&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌─────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│ Kubernetes API Server&amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└────────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ Watch / List (Informer Cache)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌─────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp;kube-state-metrics&amp;nbsp; &amp;nbsp; │ ─── (Generates OpenMetrics in-memory)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└────────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ HTTP GET /metrics (Port 8080)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌─────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; Prometheus Server&amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└─────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Consuming the API via Kubernetes Informers&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;KSM connects directly to the Kubernetes API server using standard Client-GO Informers.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Instead of constantly polling the API server with heavy requests, KSM maintains an in-memory cache updated in real-time via long-polling watch streams.&lt;/li&gt;&lt;li&gt;When a resource changes (e.g., a Deployment scales down or a Pod enters CrashLoopBackOff), the local cache updates instantly.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. Generating Prometheus OpenMetrics In-Memory&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;KSM parses the fields of the Kubernetes object specs and status subresources (such as .status.phase, .spec.replicas, .status.conditions) and &lt;u&gt;converts them directly into Prometheus gauge/counter metrics&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3. Serving via HTTP Endpoint&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;u&gt;KSM hosts a lightweight HTTP server&lt;/u&gt; (default port :8080 at path &lt;span style=&quot;font-family: courier;&quot;&gt;/metrics&lt;/span&gt;).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It does not store metrics over time, nor does it push metrics out.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;u&gt;When a Prometheus server scrapes KSM&#39;s &lt;span style=&quot;font-family: courier;&quot;&gt;/metrics &lt;/span&gt;endpoint, KSM reads its in-memory snapshot, formats the raw text payload, and streams it back&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Key Technical Properties&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Property&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Details&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Port &amp;amp; Path&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Port 8080 (/metrics for cluster state), Port 8081 (/metrics for KSM internal performance telemetry)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Data Format&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Prometheus text format / OpenMetrics standard&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Metric Types&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Predominantly Gauge metrics representing instantaneous state (1 or 0 state representations)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Scaling&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Supports Horizontal Sharding and resource filtering (--resources, --namespaces) for large clusters&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Example Metric Output&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A scrape request to kube-state-metrics for a Deployment produces plain-text metrics like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;# HELP kube_deployment_spec_replicas The desired number of pods declared in the deployment spec.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;# TYPE kube_deployment_spec_replicas gauge&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&lt;b&gt;kube_deployment_spec_replicas&lt;/b&gt;{&lt;b&gt;namespace&lt;/b&gt;=&quot;default&quot;,&lt;b&gt;deployment&lt;/b&gt;=&quot;api-server&quot;} 3&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;# HELP kube_deployment_status_replicas_available The number of available pods created by the deployment.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;# TYPE kube_deployment_status_replicas_available gauge&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&lt;b&gt;kube_deployment_status_replicas_available&lt;/b&gt;{&lt;b&gt;namespace&lt;/b&gt;=&quot;default&quot;,&lt;b&gt;deployment&lt;/b&gt;=&quot;api-server&quot;} 2&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Do we need kube-state-metrics if we have Prometheus deployed in the cluster?&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yes, you still need kube-state-metrics (KSM) even if Prometheus is deployed in your cluster.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Prometheus&lt;/b&gt; is the engine that &lt;u&gt;collects, stores, and queries time-series data&lt;/u&gt;, while &lt;b&gt;kube-state-metrics&lt;/b&gt; is the agent that &lt;u&gt;generates metrics about the state of Kubernetes API objects&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;u&gt;Prometheus does not natively inspect Kubernetes API objects on its own—it relies on exporters like KSM to expose that data.&lt;/u&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What Prometheus collects out-of-the-box vs. with KSM?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;&lt;b&gt;Metric Source&lt;/b&gt;&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Responsible Agent&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Examples of Metrics Provided&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Node / OS Metrics&lt;/b&gt;&lt;/div&gt;&lt;div&gt;node_exporter&lt;/div&gt;&lt;div&gt;Node CPU utilization, RAM usage, disk space, network traffic.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Container Usage&lt;/b&gt;&lt;/div&gt;&lt;div&gt;cAdvisor (built into kubelet)&lt;/div&gt;&lt;div&gt;Container CPU throttling, memory usage (RSS), network bytes per container.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Control Plane&lt;/b&gt;&lt;/div&gt;&lt;div&gt;kube-apiserver, etcd, coredns&lt;/div&gt;&lt;div&gt;API latency, request counts, etcd commit durations.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Kubernetes State&lt;/b&gt;&lt;/div&gt;&lt;div&gt;kube-state-metrics&lt;/div&gt;&lt;div&gt;Deployment replica counts, CronJob last success times, Pod restart counts, pending PVCs, ingress statuses, TLS certificate secret expiration.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What happens without kube-state-metrics?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you run Prometheus without KSM, you lose visibility into the high-level health and configuration of your cluster resources. You will not be able to:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Alert on Deployment Health: You won&#39;t know if spec.replicas (desired) doesn&#39;t match status.available_replicas.&lt;/li&gt;&lt;li&gt;Track Job / CronJob Success: Metrics like kube_cronjob_status_last_successful_time or kube_job_status_failed won&#39;t exist.&lt;/li&gt;&lt;li&gt;Monitor Pod Lifecycle States: You won&#39;t have metrics for Pods stuck in Pending, CrashLoopBackOff status codes, or ImagePullBackOff.&lt;/li&gt;&lt;li&gt;Track Resource Requests vs. Limits: You won&#39;t be able to compare requested CPU/memory (kube_pod_container_resource_requests) against node capacity to measure cluster overcommit.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;How They Work Together&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌─────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Kubernetes API&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└────────────────────────────┬────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ Watch API Objects&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; kube-state-metrics&amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;└───────────┬───────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ Exposes /metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;┌───────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp;Prometheus Server&amp;nbsp; &amp;nbsp;│ ◄── (Scrapes KSM)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;└───────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;KSM listens to the API server and generates metrics representing resource states.&lt;/li&gt;&lt;li&gt;Prometheus scrapes KSM&#39;s /metrics endpoint along with cAdvisor, node-exporter, and your application endpoints.&lt;/li&gt;&lt;li&gt;Prometheus evaluates Alertmanager rules and stores the metrics for Grafana dashboards.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Standard Stack Setup&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In standard Kubernetes monitoring deployments (such as the kube-prometheus-stack Helm chart or Prometheus Operator), kube-state-metrics is included by default as a core component alongside Prometheus and Alertmanager.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;How to install kube-state-metrics?&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;kube-state-metrics (KSM) is typically deployed using one of four common approaches:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;1. As Part of the kube-prometheus-stack (Most Common)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you are setting up cluster monitoring with Prometheus and Grafana, kube-state-metrics is usually installed automatically as a bundled sub-chart.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Using the kube-prometheus-stack Helm chart:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;helm repo update&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;helm install prometheus-stack prometheus-community/kube-prometheus-stack&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;kube-state-metrics runs out of the box with ServiceMonitors already configured.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2. Standalone Helm Chart&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you already have a Prometheus instance or another monitoring agent (like Datadog, New Relic, or Grafana Alloy) and just need KSM running, you can deploy the official standalone Helm chart:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;helm repo update&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;helm install kube-state-metrics prometheus-community/kube-state-metrics --namespace kube-system&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;3. Native Kustomize / Standard Manifests (kubectl apply)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For gitops workflows or lightweight clusters where Helm isn&#39;t used, you can apply the official manifests maintained directly in the upstream Git repository using Kustomize:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;git clone https://github.com/kubernetes/kube-state-metrics.git&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;cd kube-state-metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kubectl apply -k examples/standard&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Note: This creates the necessary ServiceAccount, ClusterRole, ClusterRoleBinding, Deployment, and Service in the kube-system namespace.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;4. Cloud Managed Kubernetes Add-ons&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;On managed platforms (like GKE, EKS, or AKS), kube-state-metrics is often enabled as a one-click add-on or managed component integrated into cloud-native observability services (e.g., Google Cloud Observability, AWS CloudWatch Container Insights, or Azure Monitor).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What Gets Created in the Cluster?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Regardless of how you deploy it, KSM will create:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;ServiceAccount &amp;amp; ClusterRole/Binding: Grants read-only access (list, watch, get) to cluster API resources.&lt;/li&gt;&lt;li&gt;Deployment: Runs the kube-state-metrics container.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Service: Exposes the HTTP endpoint (usually on port 8080 at &lt;span style=&quot;font-family: courier;&quot;&gt;/metrics&lt;/span&gt;).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;How to inspect which default metrics KSM exposes or how to disable unused ones?&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Understanding which metrics kube-state-metrics (KSM) emits and how to prune unwanted metrics is key to controlling Prometheus ingestion volume and storage overhead.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Part 1: Inspecting Exposed Metrics&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can inspect the metrics exposed by KSM using three different methods:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;1. Official Documentation Reference&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The KSM GitHub repository contains generated documentation for every supported Kubernetes API group.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Standard Docs: kube-state-metrics/docs contains dedicated files for each resource (e.g., pod-metrics.md, cronjob-metrics.md).&lt;/li&gt;&lt;li&gt;Resource Status: The docs categorize metrics as STABLE (default), EXPERIMENTAL (alpha/beta features), or DEPRECATED.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2. Direct In-Cluster Scraping (curl / port-forward)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To see the exact payload KSM generates in your active cluster, port-forward to the KSM service and fetch /metrics:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# Port-forward the KSM service locally&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kubectl port-forward svc/kube-state-metrics -n monitoring 8080:8080&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# In a separate terminal, curl the metrics endpoint&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;curl http://localhost:8080/metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Filter for specific resource types using grep:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# View all Pod-related metrics exposed by KSM&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;curl -s http://localhost:8080/metrics | grep &quot;^kube_pod_&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# View unique metric family names&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;curl -s http://localhost:8080/metrics | grep -v &quot;^#&quot; | cut -d&#39;{&#39; -f1 | sort -u&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;3. Prometheus Metric Explorer&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In Prometheus, run the following query in the Expression Browser to list all distinct metric names harvested from the KSM job:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;count by (__name__) ({job=&quot;kube-state-metrics&quot;})&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Part 2: Disabling or Filtering Unused Metrics&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There are two primary ways to reduce metric volume: at the source (KSM Flags) or at the scraper (Prometheus Relabeling).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Method 1: Filtering at Source via KSM Flags (Recommended)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can instruct KSM to only collect or expose specific resources or metric families using command-line arguments. This reduces both KSM memory utilization and network output.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;--resources: Restricts KSM to watch specific Kubernetes objects only (e.g., ignore configmaps or secrets).&lt;/div&gt;&lt;div&gt;--metric-allowlist: Explicitly lists metric families to expose (all others are dropped).&lt;/div&gt;&lt;div&gt;--metric-denylist: Drops specific metric families while keeping the rest.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Setting via Helm (values.yaml):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# Helm configuration for prometheus-community/kube-state-metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resources:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; # Watch only specific K8s API resources&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; resources:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - pods&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - deployments&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - statefulsets&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - cronjobs&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - nodes&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# Disable specific metric families across enabled resources&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;extraArgs:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; - --metric-denylist=kube_configmap_info,kube_secret_info,kube_pod_labels&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Setting via Argo CD Application Manifest:YAMLapiVersion: argoproj.io/v1alpha1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kind: Application&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name: kube-state-metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; namespace: argocd&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; # ... (repo configuration omitted)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; source:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; helm:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; extraArgs:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - --resources=pods,deployments,cronjobs,nodes&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - --metric-denylist=kube_pod_created,kube_pod_completion_time&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Method 2: Dropping Metrics at Prometheus Ingestion&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you do not manage KSM CLI flags directly, configure Prometheus or Grafana Agent/Alloy to drop metrics during the scrape phase using metric_relabel_configs:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Prometheus CustomResource (Prometheus / ServiceMonitor):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;apiVersion: monitoring.coreos.com/v1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kind: ServiceMonitor&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name: kube-state-metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; namespace: monitoring&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; endpoints:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - port: http-metrics&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; metricRelabelings:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Drop entire metric families matching a regex pattern&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - action: drop&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sourceLabels: [__name__]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; regex: &quot;(kube_configmap_.*|kube_secret_.*|kube_lease_.*)&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Drop high-cardinality container state metrics if unused&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - action: drop&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; sourceLabels: [__name__]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; regex: &quot;kube_pod_container_state_started&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;kube_job_status_failed Source Filtering vs. Scraper DroppingFeatureKSM CLI Flags (--resources, --metric-denylist)Prometheus metric_relabel_configsKSM Memory UsageLower (KSM does not store or process ignored objects in memory)Unchanged (KSM processes objects regardless)Network TrafficLower (HTTP payload size is smaller)Unchanged (KSM transmits full payload; Prometheus discards post-fetch)Prometheus DB StorageLowerLowerConfig PlacementKSM Deployment manifestPrometheus scrape configuration&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Prometheus Metrics Exposed by kube-state-metrics&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;kube_job_status_succeeded&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kube_job_status_succeeded&lt;/span&gt; is a Prometheus metric exposed by kube-state-metrics that tracks the status of Kubernetes &lt;b&gt;Jobs&lt;/b&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It returns a gauge value representing whether a Job execution succeeded, partitioned by &lt;u&gt;label selectors&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Metric Breakdown&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Metric Name: kube_job_status_succeeded&lt;/li&gt;&lt;li&gt;Type: Gauge&lt;/li&gt;&lt;li&gt;Value:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;1: The Job has completed successfully.&lt;/li&gt;&lt;li&gt;0: The Job is in progress, failed, or has not succeeded.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Key Labels&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Label&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Description&lt;/div&gt;&lt;div&gt;====&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =========&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;job_name&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; The name of the Kubernetes Job resource&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;namespace&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; The namespace where the Job resides&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;condition&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Condition status (typically true when checking successful completion)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Common PromQL Queries&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. List All Currently Succeeded Jobs&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kube_job_status_succeeded{condition=&quot;true&quot;} == 1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Detect Jobs That Failed or Did Not Succeed&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kube_job_status_succeeded{condition=&quot;true&quot;} == 0&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Alerting Rule: Job Failure&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Alert when a Job has completed its execution attempt without succeeding:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- alert: KubernetesJobFailed&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; expr: kube_job_status_failed{condition=&quot;true&quot;} == 1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; for: 5m&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; labels:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; severity: warning&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; annotations:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; summary: &quot;Kubernetes Job {{ $labels.namespace }}/{{ $labels.job_name }} failed&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Track Job Completion Rate Over Time&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Calculate the rate of successful Job completions across a cluster:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;sum(increase(kube_job_status_succeeded{condition=&quot;true&quot;}[1h]))&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;kube_job_status_failed&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kube_job_status_failed&lt;/span&gt; is a Prometheus metric exposed by kube-state-metrics that tracks whether a Kubernetes Job has failed to execute successfully.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It returns a gauge value representing the failure status of a Job, partitioned by label selectors.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Metric Breakdown&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Metric Name: kube_job_status_failed&lt;/li&gt;&lt;li&gt;Type: Gauge&lt;/li&gt;&lt;li&gt;Value:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;1: The Job reached its failure condition (e.g., exceeded backoffLimit or failed execution).&lt;/li&gt;&lt;li&gt;0: The Job has not failed (it is currently running, pending, or succeeded).&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Key Labels&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Label&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Description&lt;/div&gt;&lt;div&gt;====&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =========&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;job_name&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; The name of the Kubernetes Job resource&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;namespace&lt;/span&gt;&amp;nbsp; &amp;nbsp; The namespace where the Job resides&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;condition&lt;/span&gt;&amp;nbsp; &amp;nbsp; Condition status (typically true when evaluating an active failure state)&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;reason&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;The reason for failure if populated (e.g., BackoffLimitExceeded, DeadlineExceeded)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Common PromQL Queries&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. List All Currently Failed Jobs&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kube_job_status_failed{condition=&quot;true&quot;} == 1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Count Failed Jobs by Namespace&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;sum by (namespace) (kube_job_status_failed{condition=&quot;true&quot;} == 1)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Prometheus Alerting Rule for Job Failures&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Alert when a Job has reached a failed condition and remained failed for 5 minutes:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- alert: KubernetesJobFailed&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; expr: kube_job_status_failed{condition=&quot;true&quot;} == 1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; for: 5m&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; labels:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; severity: warning&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; annotations:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; summary: &quot;Kubernetes Job {{ $labels.namespace }}/{{ $labels.job_name }} failed&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; description: &quot;Job {{ $labels.job_name }} in namespace {{ $labels.namespace }} failed to complete.&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Filter CronJobs / Generated Jobs by Prefix&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If Jobs are generated dynamically by CronJobs, group or filter using regular expressions:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kube_job_status_failed{condition=&quot;true&quot;, job_name=~&quot;nightly-backup-.*&quot;} == 1&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;kube_cronjob_status_last_successful_time&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kube_cronjob_status_last_successful_time&lt;/span&gt; is a gauge metric exposed by kube-state-metrics.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It records the Unix timestamp (in seconds) of when a Kubernetes CronJob last completed execution successfully.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Metric Details&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Attribute:Value&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Exporter: kube-state-metrics&lt;/li&gt;&lt;li&gt;Metric Type:Gauge&lt;/li&gt;&lt;li&gt;Value: Unix timestamp (seconds) or omitted if the CronJob has never completed&lt;/li&gt;&lt;li&gt;Labels: cronjob, namespace&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Common PromQL Use Cases&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Time Since Last Successful Run&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Calculates how many seconds have elapsed since the CronJob last succeeded:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;time() - kube_cronjob_status_last_successful_time&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Alert on CronJob Failure or Missed Schedule&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Alerts if the time since the last successful execution exceeds a target threshold (e.g., 1 day / 86,400 seconds):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;(time() - kube_cronjob_status_last_successful_time) &amp;gt; 86400&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Alerting when Schedule Run Failed&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Compares the last scheduled time (kube_cronjob_status_last_schedule_time) against the last successful time. If last_schedule_time is greater than last_successful_time, the most recent run failed or is taking abnormally long:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kube_cronjob_status_last_schedule_time - kube_cronjob_status_last_successful_time &amp;gt; 0&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Filter out Suspended CronJobs&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Combine with &lt;span style=&quot;font-family: courier;&quot;&gt;kube_cronjob_spec_suspend&lt;/span&gt; to suppress false alerts on intentionally paused jobs:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;(&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; (time() - kube_cronjob_status_last_successful_time) &amp;gt; 86400&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;) and on (cronjob, namespace) (&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; kube_cronjob_spec_suspend == 0&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;)&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/2010096164987042773/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/2010096164987042773' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/2010096164987042773'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/2010096164987042773'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/kube-state-metrics.html' title='kube-state-metrics'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/s72-w640-h608-c/Screenshot%202026-08-03%20at%2016.40.36.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-4430571326778736576</id><published>2026-08-04T15:00:41.676+01:00</published><updated>2026-08-04T15:00:41.677+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Monitoring"/><title type='text'>Monitoring term: Dead-man&#39;s switch</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgABnRMS8MEYULk3ZzfCFcQTMLdhmorAk7-EaraXAGQuxBCPPy-uEUhx_tNpsnKY0idB6o-zJe-xIGYl8v9_SPcwwRmkz7cK57pdbhVsd2JNz4eanhuHEzb88i3HceUGD2agH9fGqlXOqST2FVefC41THvmMRV2xks0JrJ7H3dLnWDMjCYK9rUTT6qDT3M/s612/magnifier-612x612.jpg&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;612&quot; data-original-width=&quot;612&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgABnRMS8MEYULk3ZzfCFcQTMLdhmorAk7-EaraXAGQuxBCPPy-uEUhx_tNpsnKY0idB6o-zJe-xIGYl8v9_SPcwwRmkz7cK57pdbhVsd2JNz4eanhuHEzb88i3HceUGD2agH9fGqlXOqST2FVefC41THvmMRV2xks0JrJ7H3dLnWDMjCYK9rUTT6qDT3M/w640-h640/magnifier-612x612.jpg&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;It&#39;s monitoring inverted: &lt;u&gt;instead of alerting when you observe something bad, you alert when you stop observing something good&lt;/u&gt;.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Example: ping on a successful run. If run is unsuccessful, monitoring catches missing ping and triggers alert.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The name comes from industrial safety — the lever on a train&#39;s throttle or a chainsaw that has to be actively held down. If the operator dies or lets go, the machine stops. Safety is the default state; it takes continuous positive action to keep running.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Normal alerting is presence-based. Something goes wrong, it emits a signal, you alert on the signal: error rate spikes, latency crosses a threshold, a pod enters CrashLoopBackOff. It works well when failures are noisy.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A dead-man&#39;s switch is absence-based. The healthy system periodically says &quot;still fine.&quot; You alert when that message doesn&#39;t arrive on time.&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/4430571326778736576/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/4430571326778736576' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/4430571326778736576'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/4430571326778736576'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/monitoring-term-dead-mans-switch.html' title='Monitoring term: Dead-man&#39;s switch'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgABnRMS8MEYULk3ZzfCFcQTMLdhmorAk7-EaraXAGQuxBCPPy-uEUhx_tNpsnKY0idB6o-zJe-xIGYl8v9_SPcwwRmkz7cK57pdbhVsd2JNz4eanhuHEzb88i3HceUGD2agH9fGqlXOqST2FVefC41THvmMRV2xks0JrJ7H3dLnWDMjCYK9rUTT6qDT3M/s72-w640-h640-c/magnifier-612x612.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-7770207981445974637</id><published>2026-08-04T13:33:06.648+01:00</published><updated>2026-08-04T13:41:56.783+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Interview"/><category scheme="http://www.blogger.com/atom/ns#" term="JavaScript"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="V8"/><title type='text'>Kubernetes Debugging Scenario: Node.JS CronJob dies with a V8 JavaScript heap OOM</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/s1178/Screenshot%202024-07-27%20at%2023.26.17.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1178&quot; data-original-width=&quot;1176&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/w638-h640/Screenshot%202024-07-27%20at%2023.26.17.png&quot; width=&quot;638&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;&lt;strong&gt;Problem Scenario&lt;/strong&gt;&lt;/h1&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;A Node.js batch job running as a Kubernetes CronJob aborts with &lt;code&gt;FATAL ERROR: Ineffective mark-compacts near heap limit&lt;/code&gt; at ~4 GB. No &lt;code&gt;NODE_OPTIONS&lt;/code&gt;, no &lt;code&gt;resources&lt;/code&gt; block. Each scheduled run leaves several failed pods behind.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Knowledge required to fix the problem (Q&amp;amp;A)&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Detailed Q&amp;amp;A&lt;/h2&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;span&gt;&lt;/span&gt;&lt;h2&gt;1. Node.js / V8 memory model&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q: What does &lt;code&gt;--max-old-space-size&lt;/code&gt; actually control, and what does it &lt;em&gt;not&lt;/em&gt; control?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;It caps V8&#39;s &lt;strong&gt;old space&lt;/strong&gt; — the &lt;u&gt;long-lived generation of the &lt;b&gt;JS heap&lt;/b&gt;&lt;/u&gt;. It does not cap &lt;b&gt;new space&lt;/b&gt; (&lt;code&gt;--max-semi-space-size&lt;/code&gt;), code space, large object space, or external/off-heap memory such as &lt;code&gt;Buffer&lt;/code&gt; and &lt;code&gt;ArrayBuffer&lt;/code&gt; allocations, native addon memory, thread-pool stacks, or glibc malloc arenas. So &lt;u&gt;a process with a 6 GB old-space ceiling can easily have an RSS well above 6 GB.&lt;/u&gt;&lt;/p&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: initial; border-style: none; border-width: medium; border: none; margin: 0px 0px 0px 40px; padding: 0px;&quot;&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;i&gt;V8 is Google&#39;s open source high-performance JavaScript and WebAssembly engine, written in C++. It is used in Chrome and in Node.js, among others.&lt;/i&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;--max-old-space-size&lt;/span&gt; sets the maximum memory limit (in megabytes) allocated to the Old Generation heap space inside V8, the JavaScript engine powering Node.js.&lt;/p&gt;&lt;p&gt;When&lt;u&gt; V8 allocates memory for your application&lt;/u&gt;, it &lt;u&gt;divides the JavaScript heap into distinct regions based on object lifecycle. This flag configures the largest region where long-lived objects reside&lt;/u&gt;.&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;What It Measures &amp;amp; Controls&lt;/h4&gt;&lt;p&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;--max-old-space-size&lt;/span&gt; explicitly caps memory allocated for:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Old Generation JavaScript Objects: Objects, arrays, functions, closures, and strings that have survived initial garbage collection cycles in the Young Generation space and were promoted to the Old Generation.&lt;/li&gt;&lt;li&gt;Old Pointer Space &amp;amp; Old Data Space: Regions holding objects that contain pointers to other objects and raw data (like numbers or unboxed scalars).&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;What It Does NOT Control&lt;/h4&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A common misconception is that &lt;span style=&quot;font-family: courier;&quot;&gt;--max-old-space-size&lt;/span&gt; caps the entire Resident Set Size (RSS) or system memory footprint of your Node.js process. It does not limit:&lt;/li&gt;&lt;li&gt;Node.js Buffers (ArrayBuffers): Since Node.js v8.0+, binary Buffer allocations use off-heap C++ memory backing stores (ArrayBuffer). While the JavaScript wrapper object lives on the V8 heap, the underlying raw bytes do not count toward the old space limit.&lt;/li&gt;&lt;li&gt;Native C++ Allocations: Memory used by native C++ add-ons, libuv threads, or external libraries compiled into Node.&lt;/li&gt;&lt;li&gt;Other V8 Heap Spaces:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;New Space (Nursery/Young Generation): Where new allocations land (--max-semi-space-size).&lt;/li&gt;&lt;li&gt;Code Space: JIT-compiled bytecode and machine code.&lt;/li&gt;&lt;li&gt;Map/Cell Spaces: V8 internal hidden classes and metadata.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Call Stack Memory: Memory used by execution contexts and local variables on the stack.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;u&gt;Because of off-heap memory, a Node.js process with &lt;span style=&quot;font-family: courier;&quot;&gt;--max-old-space-size=2048&lt;/span&gt; (2 GB) can easily consume 3 GB or more of total system RAM (RSS)&lt;/u&gt;.&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;What Happens When the Limit Is Reached&lt;/h4&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Aggressive &lt;b&gt;Garbage Collection&lt;/b&gt;: As old space usage approaches the limit, V8 triggers blocking, high-overhead Mark-Sweep-Compact garbage collection cycles to reclaim dead objects.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Process Crash&lt;/b&gt;: &lt;u&gt;If V8 cannot free enough memory to fit the next allocation below the configured threshold, Node.js crashes with a fatal error&lt;/u&gt;:&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: red; font-family: courier;&quot;&gt;FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory&lt;/span&gt;&lt;/p&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Default Values &amp;amp; Usage&lt;/h4&gt;&lt;p&gt;Default Behavior: In modern Node.js versions, &lt;u&gt;V8 dynamically sets the limit based on total available system RAM—typically around 2 GB to 4 GB&lt;/u&gt; on 64-bit systems if unspecified.&lt;/p&gt;&lt;p&gt;Command Line Flag:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;node --max-old-space-size=4096 app.js&lt;/span&gt;&lt;/p&gt;&lt;p&gt;Environment Variable:&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;export NODE_OPTIONS=&quot;--max-old-space-size=4096&quot;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Why did the process die at ~4064 MB when nobody configured a heap limit?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;u&gt;V8 picks a default heap ceiling from the memory it believes is available, and on 64-bit builds that lands at roughly 4 GB&lt;/u&gt;. The &lt;code&gt;Mark-Compact 4064.3 MB&lt;/code&gt; line in the log is the giveaway that it hit that default ceiling rather than any limit you set.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Why doesn&#39;t Node just size its heap to the container&#39;s memory limit?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Historically V8 read &lt;em&gt;host&lt;/em&gt; RAM, not the cgroup limit, so a Node process in a 512 Mi container would happily set a multi-gigabyte heap and get OOM-killed. Newer Node versions do consult cgroup constraints, but the behaviour varies by version — which is why the defensive answer is always to set the flag explicitly rather than rely on auto-detection.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: The workload starts with &lt;code&gt;npm run start&lt;/code&gt;. Does setting &lt;code&gt;NODE_OPTIONS&lt;/code&gt; in the container env actually reach the Node process?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Yes — &lt;code&gt;NODE_OPTIONS&lt;/code&gt; is an environment variable, so it&#39;s inherited by every child process npm spawns. Two caveats worth naming: it also applies to the npm wrapper process itself (harmless, just an extra reservation), and not every V8 flag is permitted inside &lt;code&gt;NODE_OPTIONS&lt;/code&gt;. The alternative is passing the flag in the npm script itself, which is more surgical but easier to lose.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;2. Diagnosis: which kind of OOM is this?&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q: How do you tell a V8 heap OOM from a kernel OOMKill from a kubelet eviction?&lt;/strong&gt;&lt;/p&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;V8 heap OOM&lt;/th&gt;
&lt;th&gt;OOMKilled&lt;/th&gt;
&lt;th&gt;Evicted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Log&lt;/b&gt; line&lt;/td&gt;
&lt;td&gt;&lt;code&gt;FATAL ERROR: Ineffective mark-compacts near heap limit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;none from the app — killed mid-flight&lt;/td&gt;
&lt;td&gt;none from the app&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signal / exit&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SIGABRT&lt;/code&gt;, exit 134&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SIGKILL&lt;/code&gt;, exit 137&lt;/td&gt;
&lt;td&gt;pod deleted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod status&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&lt;b&gt;Error&lt;/b&gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&lt;b&gt;OOMKilled&lt;/b&gt;&lt;/code&gt; in &lt;code&gt;lastState.terminated.reason&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&lt;b&gt;Failed&lt;/b&gt;&lt;/code&gt;, reason &lt;code&gt;Evicted&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix direction&lt;/td&gt;
&lt;td&gt;raise heap ceiling &lt;strong&gt;or&lt;/strong&gt; reduce allocation&lt;/td&gt;
&lt;td&gt;raise container limit&lt;/td&gt;
&lt;td&gt;set requests so you aren&#39;t the first target&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;p&gt;The ticket&#39;s evidence — the mark-compact message plus &lt;code&gt;signal SIGABRT&lt;/code&gt; — puts it firmly in column one. That matters because raising the container limit alone would have changed nothing: V8 would still have aborted at 4 GB.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: How would you size the flag rather than guessing?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;b&gt;&lt;u&gt;Instrument before you tune&lt;/u&gt;&lt;/b&gt;. &lt;code&gt;--trace-gc&lt;/code&gt; shows the heap trajectory over the run; &lt;code&gt;process.memoryUsage()&lt;/code&gt; sampled periodically distinguishes &lt;code&gt;heapUsed&lt;/code&gt; from &lt;code&gt;external&lt;/code&gt;; &lt;code&gt;--heapsnapshot-near-heap-limit=1&lt;/code&gt; writes a snapshot right before the abort that you can open in Chrome DevTools to find the retaining structure. That tells you whether the working set is genuinely ~6 GB or whether one unbounded array is the whole problem.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Is raising the heap the right fix at all?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Usually it&#39;s a mitigation, not a fix. A benchmark job whose memory scales with input size will hit any ceiling you pick — the durable fix is streaming, batching, or paginating so peak memory is bounded by chunk size rather than dataset size. Raising the flag is defensible as a stopgap; the honest version says so in the ticket and files the follow-up. Note that in this case the real numbers came out at 10 GB heap with a 5-hour runtime, which is a fairly loud hint that the algorithm is the underlying issue.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;3. Kubernetes resource management&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q: What&#39;s the difference between a memory request and a memory limit?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;The request is what the scheduler reserves — it decides which node the pod fits on and is the baseline the kubelet uses when deciding who to evict. The limit is enforced at runtime by the cgroup; exceed it and the kernel OOM-kills the container. Memory, unlike CPU, is incompressible: there&#39;s no throttling, only killing.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: What QoS class does a pod with no resources block get, and why does that matter here?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;code&gt;&lt;b&gt;BestEffort&lt;/b&gt;&lt;/code&gt; — the first thing evicted under node memory pressure, and it contributes nothing to the scheduler&#39;s accounting so the node can be oversubscribed into pressure in the first place. Setting requests equal to limits gives &lt;code&gt;&lt;b&gt;Guaranteed&lt;/b&gt;&lt;/code&gt;; requests below limits gives &lt;code&gt;&lt;b&gt;Burstable&lt;/b&gt;&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: How do you choose the relationship between the heap flag and the container limit?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Limit strictly above heap ceiling, with headroom for everything &lt;code&gt;--max-old-space-size&lt;/code&gt; doesn&#39;t cover — off-heap buffers, native memory, the npm and node process overhead, plus GC working room. The ticket proposed 6 GB heap under a 7 Gi limit; what actually shipped was 10 GB heap under a 12 Gi limit. Too tight and you convert a clean SIGABRT into a much harder-to-debug OOMKill.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: What&#39;s the risk of setting &lt;code&gt;limits.memory&lt;/code&gt; well above &lt;code&gt;requests.memory&lt;/code&gt;?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;You&#39;re overcommitting the node. It schedules against the request but can consume up to the limit, so several such pods on one node can drive it into memory pressure and trigger evictions of unrelated workloads. Matching them costs you scheduling flexibility but makes the blast radius predictable.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: You set &lt;code&gt;requests.memory: 8Gi&lt;/code&gt; and the pod never starts. What&#39;s your first check?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Whether any node has 8 Gi of &lt;em&gt;allocatable&lt;/em&gt; memory free — &lt;u&gt;&lt;b&gt;allocatable&lt;/b&gt; is &lt;b&gt;capacity&lt;/b&gt; minus &lt;b&gt;kube-reserved&lt;/b&gt;, &lt;b&gt;system-reserved&lt;/b&gt;, and &lt;b&gt;eviction thresholds&lt;/b&gt;&lt;/u&gt;. The pod sits &lt;code&gt;&lt;b&gt;Pending&lt;/b&gt;&lt;/code&gt; with an &lt;code&gt;&lt;b&gt;Insufficient memory&lt;/b&gt;&lt;/code&gt; scheduling event. Big-request batch jobs are a classic case for a dedicated or autoscaling node pool.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;4. CronJobs and Jobs&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q: What produced &lt;em&gt;seven&lt;/em&gt; &lt;code&gt;Error&lt;/code&gt; pods from one scheduled run?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;code&gt;backoffLimit&lt;/code&gt; retries on failure, and a deterministic OOM fails identically every time — so the Job burned through its retries producing one dead pod each. The fix applied was a &lt;code&gt;podFailurePolicy&lt;/code&gt; that fails the Job on the application&#39;s exit code instead of retrying, plus &lt;code&gt;ttlSecondsAfterFinished&lt;/code&gt; so finished Jobs get garbage-collected rather than accumulating.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: What does &lt;code&gt;podFailurePolicy&lt;/code&gt; require to work?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;code&gt;restartPolicy: Never&lt;/code&gt; on the pod template, and rules matching on either container exit codes (&lt;code&gt;onExitCodes&lt;/code&gt;) or pod conditions (&lt;code&gt;onPodConditions&lt;/code&gt;, e.g. &lt;code&gt;DisruptionTarget&lt;/code&gt;). Actions are &lt;code&gt;FailJob&lt;/code&gt;, &lt;code&gt;Ignore&lt;/code&gt;, &lt;code&gt;Count&lt;/code&gt;, and &lt;code&gt;FailIndex&lt;/code&gt;. The point is distinguishing &lt;em&gt;retryable&lt;/em&gt; infrastructure failures from &lt;em&gt;deterministic&lt;/em&gt; application failures — retrying a heap OOM six times is pure waste.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Which CronJob fields govern history and overlap?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;code&gt;successfulJobsHistoryLimit&lt;/code&gt; / &lt;code&gt;failedJobsHistoryLimit&lt;/code&gt; for retained Job objects, &lt;code&gt;ttlSecondsAfterFinished&lt;/code&gt; on the Job for automatic cleanup, &lt;code&gt;concurrencyPolicy&lt;/code&gt; (&lt;code&gt;Allow&lt;/code&gt; / &lt;code&gt;Forbid&lt;/code&gt; / &lt;code&gt;Replace&lt;/code&gt;) for overlapping runs, &lt;code&gt;startingDeadlineSeconds&lt;/code&gt; for missed schedules, and &lt;code&gt;activeDeadlineSeconds&lt;/code&gt; as a wall-clock kill switch. For a job that runs five hours, &lt;code&gt;concurrencyPolicy: Forbid&lt;/code&gt; deserves a hard look.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: The schedule is &lt;code&gt;30 10 */14 * *&lt;/code&gt;. Does that run every 14 days?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;No — and this is the trap. Step values in day-of-month are evaluated within each month, so it fires on the 1st, 15th, and 29th, then resets. The gap between the 29th and the following 1st is two or three days, not fourteen. Genuine &quot;every N days&quot; needs an external scheduler or a daily run that no-ops based on a stored timestamp.&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;5. Container memory accounting&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q: When you read a container&#39;s memory usage, what are you actually seeing?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Under cgroup v2 the kubelet reports working set derived from &lt;code&gt;memory.current&lt;/code&gt; minus inactive file cache; &lt;code&gt;memory.max&lt;/code&gt; is the hard limit. Crucially &lt;code&gt;memory.current&lt;/code&gt; includes page cache, so a process doing heavy file I/O can look alarming without any anonymous-memory problem. RSS is anonymous plus mapped pages for the process specifically, and glibc often doesn&#39;t return freed memory to the OS — so RSS is sticky and lags real usage downward.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Why is &lt;code&gt;container_memory_rss&lt;/code&gt; a poor alerting signal for some workloads?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Because it only captures what lives in RSS. For a JVM or Node process the &lt;b&gt;heap&lt;/b&gt; is &lt;b&gt;anonymous memory&lt;/b&gt; and RSS tracks it reasonably; for something like Percona MongoDB, where WiredTiger&#39;s cache sits in the &lt;b&gt;OS page&lt;/b&gt; cache rather than RSS, the metric is structurally blind to the thing you care about — you want &lt;b&gt;cache fill percentage&lt;/b&gt; instead. &lt;span style=&quot;color: #ff00fe;&quot;&gt;Matching the metric to the &lt;b&gt;&lt;u&gt;workload&#39;s memory architecture&lt;/u&gt;&lt;/b&gt; is the actual skill.&lt;/span&gt;&lt;/p&gt;&lt;hr /&gt;&lt;h2&gt;6. Verification&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q: How do you prove the fix worked?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Trigger a manual run (&lt;code&gt;kubectl create job --from=cronjob/experience-benchmarks&lt;/code&gt;) and confirm the Job reaches &lt;code&gt;Complete&lt;/code&gt; with no SIGABRT and no &lt;code&gt;Error&lt;/code&gt; pods. Then compare peak usage against the limit — completing at 95% of the ceiling is luck, not a fix. Verification model: live CronJob spec matches &lt;code&gt;main&lt;/code&gt;, last three runs all &lt;code&gt;Complete&lt;/code&gt;, runtimes recorded, zero &lt;code&gt;Error&lt;/code&gt; pods.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: How do you confirm what&#39;s actually running in prod matches what&#39;s in the repo?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Diff the live object against the manifest — &lt;code&gt;kubectl get cronjob experience-benchmarks -o yaml&lt;/code&gt; against &lt;code&gt;deploy/prod.yml&lt;/code&gt;. Drift between a merged PR and the running cluster is exactly the kind of gap that lets a &quot;fixed&quot; ticket keep failing, and it&#39;s the check that would have surfaced the tickets overlap before any code was written.&lt;/p&gt;&lt;p&gt;


































&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: What should you have checked before writing a single line for this ticket?&lt;/strong&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Whether the problem still existed. The ticket sat in Backlog for four days, a ticket shipped a superset of the fix during that window, and the work that followed would have &lt;em&gt;lowered&lt;/em&gt; the heap from 10 GB to 6 GB — reintroducing the OOM. Reading &lt;code&gt;main&lt;/code&gt; and the live spec before implementing is the cheapest step in the whole process and the one that was skipped.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2&gt;Brief Q&amp;amp;A&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;V8 / Node&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Q: What does &lt;code&gt;--max-old-space-size&lt;/code&gt; cap?&lt;/strong&gt; Only V8&#39;s old space. Not new space, code space, or off-heap memory (&lt;code&gt;Buffer&lt;/code&gt;, &lt;code&gt;ArrayBuffer&lt;/code&gt;, native addons). RSS can exceed it substantially.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Why die at ~4 GB with no flag set?&lt;/strong&gt; That&#39;s V8&#39;s default ceiling on 64-bit. Node has historically sized it from host RAM, not the cgroup limit — so always set it explicitly.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Does &lt;code&gt;NODE_OPTIONS&lt;/code&gt; reach a process started via &lt;code&gt;npm run start&lt;/code&gt;?&lt;/strong&gt; Yes, it&#39;s inherited by child processes. It also applies to the npm wrapper itself.&lt;/p&gt;&lt;h3&gt;Diagnosis&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Q: Distinguish the three OOM flavours.&lt;/strong&gt; V8 heap OOM → mark-compact message, SIGABRT, exit 134, pod &lt;code&gt;Error&lt;/code&gt;. Kernel kill → no app log, SIGKILL, exit 137, &lt;code&gt;OOMKilled&lt;/code&gt;. Eviction → pod &lt;code&gt;Failed&lt;/code&gt;, reason &lt;code&gt;Evicted&lt;/code&gt;. Only the first is fixed by the heap flag.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: How do you size the flag instead of guessing?&lt;/strong&gt; &lt;code&gt;--trace-gc&lt;/code&gt; for the trajectory, &lt;code&gt;process.memoryUsage()&lt;/code&gt; for heap vs. external, &lt;code&gt;--heapsnapshot-near-heap-limit=1&lt;/code&gt; for a snapshot at the abort.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Is raising the heap the real fix?&lt;/strong&gt; Usually a stopgap. If memory scales with input size, any ceiling eventually fails — stream or batch so peak is bounded by chunk size.&lt;/p&gt;&lt;h3&gt;Kubernetes resources&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Q: Request vs. limit?&lt;/strong&gt; Request drives scheduling and eviction ranking; limit is cgroup-enforced. Memory is incompressible — no throttling, only killing.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: No &lt;code&gt;resources&lt;/code&gt; block means what QoS?&lt;/strong&gt; &lt;code&gt;BestEffort&lt;/code&gt; — first evicted under node pressure, and invisible to scheduler accounting. Requests == limits gives &lt;code&gt;Guaranteed&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: How do heap ceiling and container limit relate?&lt;/strong&gt; Limit strictly above the heap, with headroom for off-heap and process overhead. Too tight converts a clean SIGABRT into a harder-to-debug OOMKill.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Request set high and the pod won&#39;t schedule?&lt;/strong&gt; Check node &lt;em&gt;allocatable&lt;/em&gt; (capacity minus reserved and eviction thresholds). Expect &lt;code&gt;Pending&lt;/code&gt; with &lt;code&gt;Insufficient memory&lt;/code&gt;.&lt;/p&gt;&lt;h3&gt;Jobs / CronJobs&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Q: Why several failed pods per run?&lt;/strong&gt; &lt;code&gt;backoffLimit&lt;/code&gt; retries, and a deterministic OOM fails identically each time. Use &lt;code&gt;podFailurePolicy&lt;/code&gt; with &lt;code&gt;onExitCodes&lt;/code&gt; (requires &lt;code&gt;restartPolicy: Never&lt;/code&gt;) to fail fast, plus &lt;code&gt;ttlSecondsAfterFinished&lt;/code&gt; for cleanup.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: Does &lt;code&gt;30 10 */14 * *&lt;/code&gt; run every 14 days?&lt;/strong&gt; No. Day-of-month steps reset monthly → the 1st, 15th, and 29th. True &quot;every N days&quot; needs external scheduling.&lt;/p&gt;&lt;h3&gt;Verification&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;Q: How do you prove it&#39;s fixed?&lt;/strong&gt; Trigger a manual run from the CronJob, confirm &lt;code&gt;Complete&lt;/code&gt; with no failed pods, and compare &lt;em&gt;peak&lt;/em&gt; usage to the limit — finishing at 95% of the ceiling is luck.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Q: What do you check before writing any code?&lt;/strong&gt; That the problem still exists. Diff the live object against the repo manifest; a stale ticket can lead you to &lt;em&gt;lower&lt;/em&gt; limits that a since-merged fix raised.&lt;/p&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;What happened:&lt;/strong&gt; V8 hit its own default ~4 GB old-space ceiling and aborted at 4064 MB. The container had &lt;em&gt;no&lt;/em&gt; memory limit at all (no &lt;code&gt;resources&lt;/code&gt; block), so nothing external was squeezing it — Node self-limited while there was ~10 Gi of node memory sitting unused. Not a regression either: no app code change since Day/Month, so the dataset had simply grown past 4 GB for a job that aggregates benchmarks over a 5–10 hour run.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;The RAM figures:&lt;/strong&gt;&lt;/p&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;td&gt;&lt;code&gt;m5.xlarge&lt;/code&gt; — 16 GiB total, &lt;strong&gt;14.4 Gi allocatable&lt;/strong&gt;, 4 vCPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container limit&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;none&lt;/strong&gt; — no &lt;code&gt;resources&lt;/code&gt; block, so effectively bounded only by node allocatable, QoS &lt;code&gt;BestEffort&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Effective ceiling&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~4 GB&lt;/strong&gt;, imposed by V8&#39;s default, not by Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;p&gt;&lt;strong&gt;Why the fix landed where it did:&lt;/strong&gt; heap raised to 10240 MB, with requests 8Gi and limit 12Gi — the limit chosen to sit under 14.4 Gi allocatable so the pod still schedules on the same cron node, and the heap chosen well under the limit to leave off-heap headroom.&lt;/p&gt;&lt;p&gt;




&lt;/p&gt;&lt;p&gt;One nuance worth noting against what I wrote earlier: the containers exited with code &lt;strong&gt;1&lt;/strong&gt;, not 134. &lt;code&gt;npm run start&lt;/code&gt; wraps the Node process, so npm catches the child&#39;s SIGABRT and exits 1 itself — the abort signal never reaches the container&#39;s exit status. That&#39;s why the &lt;code&gt;podFailurePolicy&lt;/code&gt; rule had to match on exit code 1, and it&#39;s a good reminder that a wrapper process masks the signal you&#39;d normally use to identify a V8 abort. The mark-compact line in the logs was the real diagnostic.&lt;/p&gt;&lt;span&gt;&lt;!--more--&gt;&lt;/span&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;There are workable rules of thumb, but they&#39;re ordering constraints plus margins rather than fixed numbers. The three values have to be set in a specific order because each one bounds the next.&lt;/p&gt;&lt;h3 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot; dir=&quot;ltr&quot;&gt;The ordering&lt;/h3&gt;&lt;div aria-label=&quot;Code&quot; class=&quot;relative group/copy bg-bg-000/50 border-0.5 border-border-400 rounded-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-accent-100&quot; role=&quot;group&quot; tabindex=&quot;0&quot;&gt;&lt;div class=&quot;sticky opacity-0 group-hover/copy:opacity-100 group-focus-within/copy:opacity-100 top-2 py-2 h-12 w-0 float-right&quot;&gt;&lt;div class=&quot;absolute right-0 h-8 px-2 items-center inline-flex z-10&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;overflow-x-auto&quot;&gt;&lt;pre class=&quot;code-block__code !my-0 !rounded-lg !text-sm !leading-relaxed p-3.5&quot; style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; color: #14181f; font-family: var(--font-mono);&quot;&gt;&lt;code style=&quot;background-attachment: initial; background-clip: initial; background-image: initial; background-origin: initial; background-position: initial; background-repeat: initial; background-size: initial; font-family: var(--font-mono); white-space: pre-wrap;&quot;&gt;working set  &amp;lt;  heap ceiling  &amp;lt;  limit
                                 ↑
                            requests (== limit, or below it)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;1. Heap ceiling ≈ working set × 1.25–1.4.&lt;/strong&gt; V8 needs slack &lt;em&gt;above&lt;/em&gt; your live set to do mark-compact efficiently. Set the ceiling at the observed peak and you get GC thrash — the process burns CPU in near-limit collections for a long time before it finally aborts. Slack is what makes the difference between &quot;slow&quot; and &quot;dead.&quot;&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;2. Heap ceiling ≈ 70–80% of the container limit.&lt;/strong&gt; The remaining 20–30% covers everything &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;--max-old-space-size&lt;/code&gt; doesn&#39;t: new space, code space, &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;Buffer&lt;/code&gt;/&lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;ArrayBuffer&lt;/code&gt; external memory, native addon allocations, thread-pool stacks, and the wrapper process. Equivalently: limit ≈ heap × 1.3.&lt;/p&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;Adjust that ratio to the workload&#39;s off-heap profile:&lt;/p&gt;&lt;div class=&quot;overflow-x-auto w-full px-2 mb-6 print:overflow-x-visible&quot; dir=&quot;ltr&quot;&gt;&lt;table class=&quot;min-w-full border-collapse text-sm leading-[1.7] whitespace-normal&quot;&gt;&lt;thead class=&quot;text-left&quot;&gt;&lt;tr&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;Workload shape&lt;/th&gt;&lt;th class=&quot;text-text-100 border-b-0.5 border-[hsl(var(--border-300)/0.6)] py-2 pr-4 align-top font-bold&quot; scope=&quot;col&quot;&gt;Heap as % of limit&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Plain JSON/object crunching, little I/O buffering&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;75–80%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Heavy streaming, large &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;Buffer&lt;/code&gt; use, many worker threads&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;55–65%&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;Native addons (image processing, compression, crypto)&lt;/td&gt;&lt;td class=&quot;border-b-0.5 border-[hsl(var(--border-300)/0.3)] py-2 pr-4 align-top&quot;&gt;50–60%&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;&lt;strong&gt;3. Requests:&lt;/strong&gt; for a batch or cron job with a known peak, set &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;requests == limits&lt;/code&gt;. You get &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;Guaranteed&lt;/code&gt; QoS, no eviction risk, no overcommit surprises — and a batch job&#39;s cost is dominated by the run, not by bin-packing efficiency. For long-running services with rare spikes, &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;requests&lt;/code&gt; at the p95 steady state and &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;limits&lt;/code&gt; at peak plus margin is reasonable, but keep the ratio under ~2× since memory can&#39;t be reclaimed under pressure the way CPU can.&lt;/p&gt;&lt;h3 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot; dir=&quot;ltr&quot;&gt;How to get the working-set number&lt;/h3&gt;&lt;p class=&quot;font-claude-response-body break-words whitespace-normal&quot; dir=&quot;ltr&quot;&gt;Run it once with generous headroom and &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;--trace-gc&lt;/code&gt;, then read the plateau of &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;heapUsed&lt;/code&gt; from &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;process.memoryUsage()&lt;/code&gt; sampled through the run. Separately note &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;external&lt;/code&gt; — that&#39;s your off-heap figure and it tells you which row of the table above applies. Container-level peak from &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;container_memory_working_set_bytes&lt;/code&gt; gives you the total to size the limit against. One clean run gives you all three numbers; anything else is guessing.&lt;/p&gt;&lt;h3 class=&quot;text-text-100 mt-3 -mb-1 text-[1.125rem] font-bold&quot; dir=&quot;ltr&quot;&gt;Failure modes to avoid&lt;/h3&gt;&lt;p&gt;










&lt;/p&gt;&lt;ul class=&quot;[li_&amp;amp;]:mb-0 [li_&amp;amp;]:mt-1 [li_&amp;amp;]:gap-1 [&amp;amp;:not(:last-child)_ul]:pb-1 [&amp;amp;:not(:last-child)_ol]:pb-1 list-disc flex flex-col gap-1 pl-8 mb-3 print:block print:space-y-1&quot; dir=&quot;ltr&quot;&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Heap ceiling == container limit.&lt;/strong&gt; Converts a clean V8 abort with a diagnosable log line into a &lt;code class=&quot;bg-text-200/5 border border-0.5 border-border-300 text-danger-000 whitespace-pre-wrap rounded-[0.4rem] px-1 py-px text-[0.9rem]&quot;&gt;SIGKILL&lt;/code&gt; OOMKill with no application output.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Limit set, heap flag omitted.&lt;/strong&gt; Depending on Node version, V8 may not read the cgroup limit and will pick its own default — possibly far above or below what you provisioned.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Request larger than ~50% of node allocatable.&lt;/strong&gt; You&#39;ve effectively dedicated a whole node to one pod. Fine if intentional (which it was here: 8Gi request on 14.4Gi allocatable), wasteful if not.&lt;/li&gt;
&lt;li class=&quot;font-claude-response-body whitespace-normal break-words pl-2&quot;&gt;&lt;strong&gt;Raising numbers on a repeating schedule.&lt;/strong&gt; If memory scales with input size, every ceiling has an expiry date. Sizing buys time; streaming or batching is the fix.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/7770207981445974637/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/7770207981445974637' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7770207981445974637'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7770207981445974637'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/kubernetes-debugging-scenario-nodejs.html' title='Kubernetes Debugging Scenario: Node.JS CronJob dies with a V8 JavaScript heap OOM'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/s72-w638-h640-c/Screenshot%202024-07-27%20at%2023.26.17.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-5123390824869191532</id><published>2026-08-03T23:18:08.073+01:00</published><updated>2026-08-03T23:18:08.074+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Elasticsearch"/><category scheme="http://www.blogger.com/atom/ns#" term="Observability"/><category scheme="http://www.blogger.com/atom/ns#" term="Prometheus"/><title type='text'>elasticsearch-exporter</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/s1736/Screenshot%202026-08-03%20at%2016.40.36.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1648&quot; data-original-width=&quot;1736&quot; height=&quot;608&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/w640-h608/Screenshot%202026-08-03%20at%2016.40.36.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Elasticsearch&lt;/b&gt; doesn&#39;t speak &lt;b&gt;Prometheus&lt;/b&gt;. It exposes its internals over its own REST API — GET /_nodes/stats, /_cluster/health, /_cat/indices — as JSON, in Elastic&#39;s own shape. Prometheus can&#39;t scrape that.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The elasticsearch-exporter is a small sidecar-or-Deployment-shaped translator that sits between the two:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;it polls those ES REST endpoints on an interval,&lt;/li&gt;&lt;li&gt;flattens the JSON into Prometheus text-format metrics,&lt;/li&gt;&lt;li&gt;and serves them on &lt;span style=&quot;font-family: courier;&quot;&gt;/metrics&lt;/span&gt; (conventionally :9114) for Prometheus to scrape.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;The canonical implementation is &lt;span style=&quot;font-family: courier;&quot;&gt;prometheus-community/elasticsearch_exporter&lt;/span&gt; (formerly &lt;span style=&quot;font-family: courier;&quot;&gt;justwatchcom/elasticsearch_exporter&lt;/span&gt;), packaged as the&lt;span style=&quot;font-family: courier;&quot;&gt; prometheus-elasticsearch-exporter &lt;/span&gt;Helm chart. Elastic also ships a first-party alternative path — Metricbeat&#39;s elasticsearch module, or the newer Elastic Agent integration — but those ship into Elasticsearch/Kibana&#39;s own monitoring cluster, not into Prometheus, so they don&#39;t help a Grafana-alerting-on-Prometheus setup.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The metrics it produces are for example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;metric&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;what it gives you&amp;nbsp;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;search_jvm_memory_used_bytes{area=&quot;heap&quot;}&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;actual JVM heap in use — the thing that actually predicts an ES OOM&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;elasticsearch_jvm_memory_max_bytes{area=&quot;heap&quot;}&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;configured heap ceiling (-Xmx), so you can take a real ratio&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;elasticsearch_jvm_gc_collection_seconds_*&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;GC pressure; sustained old-gen GC is the pre-OOM tell&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;elasticsearch_breakers_tripped&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;circuit breakers firing — ES rejecting work to avoid OOM&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;elasticsearch_cluster_health_status&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;green/yellow/red, unassigned shards&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The exporter is a separate deployable — ECK does not install it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/5123390824869191532/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/5123390824869191532' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/5123390824869191532'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/5123390824869191532'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/elasticsearch-exporter.html' title='elasticsearch-exporter'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/s72-w640-h608-c/Screenshot%202026-08-03%20at%2016.40.36.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-3528348231012127479</id><published>2026-08-03T23:17:59.220+01:00</published><updated>2026-08-05T09:02:56.122+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Docker"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Metrics"/><category scheme="http://www.blogger.com/atom/ns#" term="Observability"/><category scheme="http://www.blogger.com/atom/ns#" term="Prometheus"/><title type='text'>Prometheus Metrics</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/s1736/Screenshot%202026-08-03%20at%2016.40.36.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1648&quot; data-original-width=&quot;1736&quot; height=&quot;608&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/w640-h608/Screenshot%202026-08-03%20at%2016.40.36.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: left;&quot;&gt;&lt;h1 style=&quot;clear: both; text-align: left;&quot;&gt;The 4 Core Prometheus Metric Types&lt;/h1&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;Prometheus defines four primary metric types. Choosing the right type depends on how the value behaves over time.&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ┌───────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ Prometheus Metric Types&amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └─────────────┬─────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;┌───────────────────┬──────────────┴───────────────┬────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ▼&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ▼&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; ┌──────────────┐&amp;nbsp; &amp;nbsp; ┌──────────────┐&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ┌──────────────┐&amp;nbsp; &amp;nbsp; &amp;nbsp;┌──────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; │&amp;nbsp; &amp;nbsp; Gauge&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp;Counter&amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; Histogram&amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp;Summary&amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; ├──────────────┤&amp;nbsp; &amp;nbsp; ├──────────────┤&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ├──────────────┤&amp;nbsp; &amp;nbsp; &amp;nbsp;├──────────────┤&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; │ Value goes&amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; │ Value ONLY&amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ Groups values│&amp;nbsp; &amp;nbsp; &amp;nbsp;│ Calculates&amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; │ UP and DOWN&amp;nbsp; │&amp;nbsp; &amp;nbsp; │ increases&amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ into buckets │&amp;nbsp; &amp;nbsp; &amp;nbsp;│ quantiles&amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; │ (Snapshot)&amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; │ (Cumulative) │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ (Client-side)│&amp;nbsp; &amp;nbsp; &amp;nbsp;│ (Client-side)│&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; └──────────────┘&amp;nbsp; &amp;nbsp; └──────────────┘&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └──────────────┘&amp;nbsp; &amp;nbsp; &amp;nbsp;└──────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;clear: both; text-align: left;&quot;&gt;1. Gauge&lt;/h2&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Behavior: Can increase, decrease, or stay the same.&lt;/li&gt;&lt;li&gt;Use Case: Current state, temperatures, memory usage, concurrent connections, replica counts.&lt;/li&gt;&lt;li&gt;PromQL Functions: avg_over_time(), max_over_time(), direct evaluation.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;In Prometheus (and OpenMetrics standards), a Gauge is a metric that represents a single numerical value that can arbitrarily go up and down.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It acts like a digital display or a vehicle&#39;s speedometer—it gives you a point-in-time snapshot of a current state.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Understanding the Gauge Type&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because a Gauge can fluctuate freely, it is used to measure snapshot values, current state levels, and instantaneous statuses.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Example Real-World Analogy: A car&#39;s speedometer (can go from 0 to 70 mph, back to 30 mph), ambient temperature, or fuel tank level.&lt;/li&gt;&lt;li&gt;Kubernetes/KSM Examples:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kube_cronjob_status_last_successful_time&lt;/span&gt;: Holds a Unix timestamp (moves forward or stays fixed).&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kube_deployment_status_replicas_available&lt;/span&gt;: Number of ready pods (e.g., 3 → 5 → 2).&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;node_memory_MemAvailable_bytes&lt;/span&gt;: Available system memory in bytes.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Key Feature: Instantaneous Operations&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because gauges fluctuate, you do not run rate or increase functions like rate() or increase() on them in PromQL. Instead, you query their current value directly or perform operations like running averages (avg_over_time()), min/max checks, or scalar math (e.g., time() - gauge).&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;clear: both; text-align: left;&quot;&gt;2. Counter&lt;/h2&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Behavior: A cumulative metric that only increases (or resets to 0 upon process restart). It never decreases naturally.&lt;/li&gt;&lt;li&gt;Use Case: Counting total occurrences of events (e.g., total HTTP requests served, total pipeline errors, network bytes transmitted).&lt;/li&gt;&lt;li&gt;PromQL Functions: rate(), irate(), increase().&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;Why use a Counter instead of a Gauge? A counter allows PromQL&#39;s rate() function to accurately calculate &quot;per-second rates&quot; while automatically handling process restarts (resets).&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;clear: both; text-align: left;&quot;&gt;3. Histogram&lt;/h2&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Behavior: Samples observations (usually things like request durations or payload sizes) and counts them in configurable buckets. It also provides a sum of all observed values.&lt;/li&gt;&lt;li&gt;Exposed Data:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&amp;lt;basename&amp;gt;_bucket{le=&quot;&amp;lt;upper_bound&amp;gt;&quot;}: Counter of observations with value &amp;lt;= upper bound.&lt;/li&gt;&lt;li&gt;&amp;lt;basename&amp;gt;_count: Total number of observations (Counter).&lt;/li&gt;&lt;li&gt;&amp;lt;basename&amp;gt;_sum: Sum of all observed values (Counter).&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Use Case: Measuring latencies, response times, or request sizes where you want to calculate percentiles (e.g., p95, p99) on the server side using PromQL (histogram_quantile()).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;clear: both; text-align: left;&quot;&gt;4. Summary&lt;/h2&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Behavior: Similar to a histogram, but calculates configurable quantiles (e.g., 0.50, 0.90, 0.99) directly on the client application side over a sliding time window.&lt;/li&gt;&lt;li&gt;Exposed Data:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&amp;lt;basename&amp;gt;{quantile=&quot;0.95&quot;}: The 95th percentile value.&lt;/li&gt;&lt;li&gt;&amp;lt;basename&amp;gt;_count: Total observations.&lt;/li&gt;&lt;li&gt;&amp;lt;basename&amp;gt;_sum: Sum of observations.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Use Case: When client-side percentile calculation is required and you don&#39;t need to aggregate quantiles across multiple instances in Prometheus.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;clear: both; text-align: left;&quot;&gt;Summary Comparison Matrix&lt;/h2&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;i&gt;Metric Type&lt;/i&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;i&gt;Can Decrease?&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Typical PromQL Functions&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Best Used For&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;b&gt;Gauge&lt;/b&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Yes&lt;/li&gt;&lt;li&gt;Direct value, avg_over_time()&lt;/li&gt;&lt;li&gt;Current state, memory usage, counts of current objects&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;b&gt;Counter&lt;/b&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;No (only on restart)&lt;/li&gt;&lt;li&gt;rate(), increase()&lt;/li&gt;&lt;li&gt;Total count of events over time, request volume&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;b&gt;Histogram&lt;/b&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;No (bucket counts increase)&lt;/li&gt;&lt;li&gt;histogram_quantile(), rate()&lt;/li&gt;&lt;li&gt;Request latencies, payload sizes (aggregatable)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;b&gt;Summary&lt;/b&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;No (counts/sums increase)&lt;/li&gt;&lt;li&gt;Direct quantile query&lt;/li&gt;&lt;li&gt;Request latencies (pre-calculated on client)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Container Metrics&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss&lt;/span&gt;&lt;/h2&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss&lt;/span&gt; is a Prometheus metric (exposed by &lt;a href=&quot;https://www.bojankomazec.com/2026/08/introduction-to-cadvisor-container.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;cAdvisor&lt;/a&gt;) that measures a container&#39;s &lt;b&gt;Resident Set Size&lt;/b&gt;—specifically, the amount of &lt;u&gt;physical RAM allocated to non-reclaimable, non-file-backed memory&lt;/u&gt;.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Key Technical Breakdown&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Under the hood in Linux cgroups, &lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss&lt;/span&gt; tracks:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Anonymous Memory&lt;/b&gt;: Process heap allocations, execution stack, and memory allocated via malloc or mmap(MAP_ANONYMOUS).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Swap Cache&lt;/b&gt;: Memory swapped out to disk that is being brought back into RAM.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;What it EXCLUDES:&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Unlike standard Linux host RSS metrics, &lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss&lt;/span&gt; in cAdvisor &lt;u&gt;excludes file-backed page &lt;b&gt;caches&lt;/b&gt;&lt;/u&gt; (memory used to cache files read from disk). Because of this,&lt;u&gt; it only measures memory that cannot be automatically freed by the Linux kernel under memory pressure&lt;/u&gt;.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;container_memory_rss vs. Other Container Metrics&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;To understand its role, it helps to see how it fits into cAdvisor&#39;s other core memory metrics:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;i&gt;Metric&lt;/i&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;i&gt;Includes&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Purpose / Key Characteristic&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;container_memory_rss&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Heap + Stack (Anonymous memory)&lt;/li&gt;&lt;li&gt;Stable indicator of process memory footprint. Does not fluctuate with disk I/O.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;container_memory_working_set_bytes&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Heap + Stack + &lt;b&gt;Active Page Cache&lt;/b&gt;&lt;/li&gt;&lt;li&gt;What Kubernetes actually monitors. Fluctuates with file reads/writes.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;container_memory_usage_bytes&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Heap + Stack + Active Cache + &lt;b&gt;Inactive Cache&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Raw total RAM usage. Can be misleading because &lt;u&gt;inactive cache is easily reclaimed by the OS&lt;/u&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Why is container_memory_rss Important?&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Memory Leak Detection&lt;/b&gt;: Because it excludes cached file reads, container_memory_rss provides a much cleaner signal for application-level memory leaks. If this metric steadily climbs over time without dropping, your application (e.g., Go heap, JVM heap, Node process) is holding onto unmanaged memory.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Debugging OOM Kills&lt;/b&gt;: While Kubernetes triggers OOMKills based on &lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_working_set_bytes&lt;/span&gt; hitting resource limits, &lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss&lt;/span&gt; helps you determine why it happened:&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;High RSS + High Working Set --&amp;gt; &lt;u&gt;Application memory leak&lt;/u&gt; or &lt;u&gt;underestimated heap limit&lt;/u&gt;.&lt;/li&gt;&lt;li&gt;Low RSS + High Working Set --&amp;gt; &lt;u&gt;Heavy disk I/O&lt;/u&gt; / &lt;u&gt;file caching &lt;/u&gt;(e.g., reading massive log files or database indexes into memory).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;When &lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss&lt;/span&gt; is NOT a relevant metric?&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Example:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;p&gt;WiredTiger is the default storage engine that MongoDB (here, the Percona Server for MongoDB / PSMDB cluster) uses to actually read and write data to disk. In &amp;lt;ticketID&amp;gt; it matters specifically because of &lt;em&gt;how it uses memory&lt;/em&gt;, which is what breaks those four Grafana alert rules.&lt;/p&gt;
&lt;p&gt;The relevant behavior:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;WiredTiger keeps a large in-memory cache.&lt;/strong&gt; It maintains its own cache of frequently-accessed data and indexes, and &lt;u&gt;it deliberately sizes that cache to roughly half the container&#39;s memory limit&lt;/u&gt; (the issue cites ~10 GiB against a 21Gi limit on rs0/rs2, and it&#39;s tunable per &amp;lt;ticketID&amp;gt;). &lt;u&gt;WiredTiger runs its own eviction, targeting about 80% cache fill under normal operation and triggering aggressive eviction around 95%.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;That cached data shows up as page cache, not RSS.&lt;/strong&gt; This is the crux of the ticket. &lt;code&gt;container_memory_rss&lt;/code&gt; counts anonymous/resident process memory but &lt;em&gt;excludes&lt;/em&gt; the OS page cache — and for a WiredTiger workload the file-backed pages (the cache) are the bulk of the real footprint. So &lt;code&gt;rss / limit&lt;/code&gt; sits structurally pinned around 50-58% no matter how much memory pressure the pod is actually under. An alert keyed on &lt;code&gt;container_memory_rss &amp;gt; 0.90&lt;/code&gt; can therefore never fire for a WiredTiger container. It&#39;s dead. (alert is defined as: &lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_rss{container=&quot;mongod&quot;} / limit &amp;gt; 0.90&lt;/span&gt;)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;But you can&#39;t just switch to working-set either.&lt;/strong&gt; &lt;code&gt;container_memory_working_set_bytes&lt;/code&gt; &lt;em&gt;does&lt;/em&gt; include those active file pages, so on the busy replicas it reads 95-96%. &lt;u&gt;That looks alarming but is expected and healthy: it&#39;s the WiredTiger cache sitting at its designed ~half-of-limit size and WT&#39;s own eviction watermark&lt;/u&gt;. The kernel reclaims those pages under real pressure, and the evidence is that nothing in the &lt;code&gt;mongodb&lt;/code&gt; namespace has ever been OOMKilled. Flipping the metric would just move the rule from never-firing to always-firing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The signal that actually matters is WiredTiger cache fill.&lt;/strong&gt; Because WT stalls user operations when its cache can&#39;t evict fast enough, the meaningful early-warning metric is cache utilization (the existing &lt;code&gt;MongoDB WiredTiger cache fill — above 95% for 30m&lt;/code&gt; rule from &amp;lt;ticketID&amp;gt;), not container RSS or working set. That&#39;s why the ticket argues the RSS rules are &lt;em&gt;redundant&lt;/em&gt;, not just broken, and leans toward deleting them.&lt;/p&gt;
&lt;p&gt;So in this issue&#39;s context, &quot;WiredTiger&quot; is essentially shorthand for &quot;a workload whose memory lives mostly in a large, self-managed, page-cache-backed database cache&quot; — which is exactly the profile that makes RSS-based memory alerting meaningless and makes cache-fill the correct signal instead. (The same logic applies to the Elasticsearch rules, where the JVM heap plays the analogous role.)&lt;/p&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Example PromQL Query&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;To track application heap growth without the noise of filesystem caching, query RSS like this:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# Rate of container RSS growth over 5-minute intervals&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;rate(container_memory_rss{namespace=&quot;production&quot;, container!=&quot;&quot;}[5m])&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_working_set_bytes&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;...&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;container_memory_usage_bytes&lt;/span&gt;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;...&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/3528348231012127479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/3528348231012127479' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3528348231012127479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3528348231012127479'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/prometheus-metrics.html' title='Prometheus Metrics'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinBsGQV-okE53tHtRHRSSp5kD-Hc6pyIp-CU7n3QtwxfMjZjdffKPnsSMjfLWcSRuczfnMJLEzVQJU0wsVVy8mfsNobofU33Q-7cgWnHhChPighYlDQzzsexx_x2_yIcx2X2TymT-9a93RWdhj8q3Kc5o9jZ0DO2NjIrfS8tMxRTz107v_67PxstbxQ78/s72-w640-h608-c/Screenshot%202026-08-03%20at%2016.40.36.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-6395588910990666810</id><published>2026-08-03T16:28:55.615+01:00</published><updated>2026-08-03T16:28:55.615+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="cAdvisor"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Metrics"/><category scheme="http://www.blogger.com/atom/ns#" term="Observability"/><category scheme="http://www.blogger.com/atom/ns#" term="Prometheus"/><title type='text'>Introduction to cAdvisor (Container Advisor)</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHV5BBT4BN_9c4PtFKQwV6GQtcobYMF4cBi1WE_1E4-pI4ahxqNbKQjxsOi3VtRdzo_hKD3ldDiHBlCoZR1muZ8gQJwgNKKilPvusujY7Ysg9PdN04lasudIgn3dqvZnPX2SecHto_1vM1RVZdSSiwqjmdgdCCQbdxAkQ002WK-sHtRjsyIDRliV2_9Rc/s257/cadvisor.webp&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;257&quot; data-original-width=&quot;200&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHV5BBT4BN_9c4PtFKQwV6GQtcobYMF4cBi1WE_1E4-pI4ahxqNbKQjxsOi3VtRdzo_hKD3ldDiHBlCoZR1muZ8gQJwgNKKilPvusujY7Ysg9PdN04lasudIgn3dqvZnPX2SecHto_1vM1RVZdSSiwqjmdgdCCQbdxAkQ002WK-sHtRjsyIDRliV2_9Rc/w498-h640/cadvisor.webp&quot; width=&quot;498&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style=&quot;text-align: center;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;cAdvisor&lt;/b&gt; (short for &lt;b&gt;Container Advisor&lt;/b&gt;) is an open-source tool created by Google to collect, aggregate, process, and export resource usage and performance metrics for running containers.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It acts as a &lt;u&gt;daemon&lt;/u&gt; that monitors resource isolation parameters, historical resource usage, and network statistics directly from the host node.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQkWartRhDB6CcL4GBRW0iu07niJjobr2qRAXRiAMkAdyeYhfQ_6M3wOL8OqGAXzWJ7T6H0ILAExFgb89P05JFhptmJ32mmXxtrkpL25VfgrUfbH3H0jK31R-0byQaIRm01EMzTUQ_qRfXZmkPnwkfxx4y9nw2rxTH3K7aNaZ9StnxTUCCcYY5luAKg4o/s1025/cAdvisor-Dashboard-monitoring-architectural-flow_one-min.jpg&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;632&quot; data-original-width=&quot;1025&quot; height=&quot;394&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQkWartRhDB6CcL4GBRW0iu07niJjobr2qRAXRiAMkAdyeYhfQ_6M3wOL8OqGAXzWJ7T6H0ILAExFgb89P05JFhptmJ32mmXxtrkpL25VfgrUfbH3H0jK31R-0byQaIRm01EMzTUQ_qRfXZmkPnwkfxx4y9nw2rxTH3K7aNaZ9StnxTUCCcYY5luAKg4o/w640-h394/cAdvisor-Dashboard-monitoring-architectural-flow_one-min.jpg&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How It Works&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;cAdvisor doesn&#39;t require instrumenting code inside containers. Instead, it inspects the node environment where containers run:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Queries Linux Kernel Structures&lt;/b&gt;: It pulls raw performance data directly from kernel mechanisms—primarily &lt;b&gt;cgroups&lt;/b&gt; (&lt;b&gt;control groups&lt;/b&gt;) for CPU, memory, and disk utilization, and network interfaces for throughput metrics.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Discovers Running Containers&lt;/b&gt;: It automatically detects running containers across multiple runtimes (Docker, containerd, CRI-O, systemd containers).&lt;/li&gt;&lt;li&gt;&lt;b&gt;Exposes Metrics&lt;/b&gt;: It formats gathered data and exposes it over a /metrics HTTP endpoint (primarily in Prometheus format) for scrapers to ingest.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Role in Kubernetes&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In Kubernetes, &lt;u&gt;cAdvisor is not deployed as a standalone pod. Instead, it is built directly into the &lt;b&gt;kubelet&lt;/b&gt; binary that runs on every node&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The kubelet uses cAdvisor internally to monitor local container resource usage.&lt;/li&gt;&lt;li&gt;It exposes cAdvisor metrics under the &lt;u&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;/metrics/cadvisor&lt;/span&gt; endpoint&lt;/u&gt; on the &lt;u&gt;kubelet API port&lt;/u&gt; (typically 10250).&lt;/li&gt;&lt;li&gt;Tools like &lt;b&gt;Prometheus&lt;/b&gt; scrape this endpoint to collect system-wide container metrics (container_cpu_usage_seconds_total, container_memory_rss, container_network_transmit_bytes_total, etc.).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Core Capabilities&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Resource Usage Monitoring&lt;/b&gt;: Tracks real-time CPU utilization, memory breakdown (RSS, cache, swap), network I/O, and disk space usage &lt;u&gt;per container&lt;/u&gt;.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Historical Trend Aggregation&lt;/b&gt;: Keeps a small buffer of historical telemetry in memory for local inspection.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Multi-Runtime Support&lt;/b&gt;: Works out of the box with containerd, &lt;u&gt;Docker&lt;/u&gt;, and CRI-compliant container engines.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Built-in Web UI&lt;/b&gt;: When run as a &lt;u&gt;standalone binary&lt;/u&gt; or &lt;u&gt;Docker container&lt;/u&gt; &lt;u&gt;outside Kubernetes&lt;/u&gt;, it provides a lightweight built-in dashboard for quick visual inspection of container stats.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Summary of Metric Flow&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Linux Kernel / cgroups --&amp;gt; cAdvisor --&amp;gt; Prometheus --&amp;gt; Grafana&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;cAdvisor sits right at the boundary between the underlying host/kernel layer and the monitoring stack, turning low-level kernel counters into structured metrics for alerting and visualization.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/6395588910990666810/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/6395588910990666810' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6395588910990666810'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6395588910990666810'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/introduction-to-cadvisor-container.html' title='Introduction to cAdvisor (Container Advisor)'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgHV5BBT4BN_9c4PtFKQwV6GQtcobYMF4cBi1WE_1E4-pI4ahxqNbKQjxsOi3VtRdzo_hKD3ldDiHBlCoZR1muZ8gQJwgNKKilPvusujY7Ysg9PdN04lasudIgn3dqvZnPX2SecHto_1vM1RVZdSSiwqjmdgdCCQbdxAkQ002WK-sHtRjsyIDRliV2_9Rc/s72-w498-h640-c/cadvisor.webp" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-8763550686472898701</id><published>2026-08-03T14:03:43.217+01:00</published><updated>2026-08-05T23:53:50.386+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Job"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><title type='text'>Kubernetes Job object</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/s1178/Screenshot%202024-07-27%20at%2023.26.17.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1178&quot; data-original-width=&quot;1176&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/w638-h640/Screenshot%202024-07-27%20at%2023.26.17.png&quot; width=&quot;638&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;A Kubernetes Job is a controller object designed to run a batch task to completion.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unlike &lt;span style=&quot;font-family: courier;&quot;&gt;Deployments&lt;/span&gt; or &lt;span style=&quot;font-family: courier;&quot;&gt;ReplicaSets&lt;/span&gt; (which keep applications running indefinitely) or &lt;span style=&quot;font-family: courier;&quot;&gt;CronJobs&lt;/span&gt; (which trigger tasks on a schedule), a &lt;u&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Job&lt;/span&gt; creates one or more Pods, executes the workload, and ensures they terminate cleanly&lt;/u&gt;. Once the specified number of Pods complete successfully, the Job itself is marked as complete and stops.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Standard Job Manifest&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;apiVersion: batch/v1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;kind: Job&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; name: data-migration-job&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; backoffLimit: 4&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;# Number of retries before marking the job failed&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; completions: 1&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # Number of successful pod completions required&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; parallelism: 1&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; # How many pods run concurrently&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; ttlSecondsAfterFinished: 600 # Clean up job &amp;amp; pods 10 minutes after completion&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; template:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; containers:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; - name: migration-task&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; image: python:3.11-slim&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; command: [&quot;python&quot;, &quot;-c&quot;, &quot;print(&#39;Running database migration...&#39;); import time; time.sleep(10); print(&#39;Done!&#39;)&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; restartPolicy: OnFailure # Required: OnFailure or Never (Always is invalid)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Core Execution Patterns&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Kubernetes Jobs support three primary workload execution models:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;1. &lt;b&gt;Non-Parallel Jobs&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Behavior: Starts a single Pod and waits for it to complete successfully.&lt;/li&gt;&lt;li&gt;Use Case: One-off database schema migrations, report generation, or administrative scripts.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;2. &lt;b&gt;Parallel Jobs with Fixed Completions&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Behavior: Runs multiple Pods in parallel until a total number of successful completions (spec.completions) is reached.&lt;/li&gt;&lt;li&gt;Use Case: Batch processing where $N$ independent tasks need to be completed.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;3. &lt;b&gt;Parallel Jobs with Work Queue&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Behavior: Pods coordinate via an external message queue (e.g., RabbitMQ, Redis, SQS). Each Pod pulls work until the queue is empty, then exits.&lt;/li&gt;&lt;li&gt;Use Case: High-throughput task processing, media transcoding, or distributed data transformation.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Key Configuration Fields&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;Field&lt;/i&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;i&gt;Default&lt;/i&gt;&lt;/li&gt;&lt;li&gt;&lt;i&gt;Description&lt;/i&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;restartPolicy&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Required&lt;/li&gt;&lt;li&gt;Must be OnFailure (restarts container inside same Pod) or Never (spawns a new Pod on failure).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;backoffLimit&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;6&lt;/li&gt;&lt;li&gt;Maximum number of retries before marking the Job as failed.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;completions&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;1&lt;/li&gt;&lt;li&gt;Total number of successful Pod terminations needed for Job completion.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;parallelism&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;1&lt;/li&gt;&lt;li&gt;Max number of Pods allowed to run concurrently at any given moment.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;activeDeadlineSeconds&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Unlimited&lt;/li&gt;&lt;li&gt;Max time allowed for the entire Job (including retries) before terminating all running Pods.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;completionMode&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;NonIndexed&lt;/li&gt;&lt;li&gt;Set to Indexed to assign each Pod a unique completion index ($0$ to $\text{completions}-1$) via environment variables.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ttlSecondsAfterFinished&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Disabled&lt;/li&gt;&lt;li&gt;Automatically deletes the Job and its underlying Pods after $N$ seconds of finishing.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ttlSecondsAfterFinished&lt;/span&gt; is a field in a Kubernetes Job specification (&lt;span style=&quot;font-family: courier;&quot;&gt;spec.ttlSecondsAfterFinished&lt;/span&gt;) that controls the automatic cleanup of completed or failed Jobs and their associated Pods via the TTL-after-finished controller.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It is a good practice to set sensible &lt;span style=&quot;font-family: courier;&quot;&gt;ttlSecondsAfterFinished&lt;/span&gt; / history limits on Jobs / CronJobs &lt;u&gt;so dead pods don&#39;t accumulate&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;How It Works&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Automatic Cleanup: Once a Job reaches a terminal state (Complete or Failed), the TTL controller starts a clock using the completion timestamp. When the specified number of seconds elapses, the Job resource is deleted.&lt;/li&gt;&lt;li&gt;Cascading Garbage Collection: Deleting the Job also automatically cleans up its dependent Pods.&lt;/li&gt;&lt;li&gt;Immediate Deletion (0): Setting &lt;span style=&quot;font-family: courier;&quot;&gt;ttlSecondsAfterFinished: 0&lt;/span&gt; makes the Job eligible for immediate removal upon completion or failure.&lt;/li&gt;&lt;li&gt;Unset / Omitted: If left undefined or set to &lt;span style=&quot;font-family: courier;&quot;&gt;null&lt;/span&gt;, the Job and its Pods remain in the cluster indefinitely until manually deleted.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Manifest Example&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;apiVersion: batch/v1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kind: Job&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name: database-migration&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &lt;b&gt;ttlSecondsAfterFinished&lt;/b&gt;: 300 # Deletes Job and Pods 5 minutes after finishing&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; template:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; containers:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; - name: migration-runner&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; image: my-app-migrations:v1.2.0&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; command: [&quot;npm&quot;, &quot;run&quot;, &quot;db:migrate&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; restartPolicy: Never&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Key Considerations&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Clock Trigger: For successful Jobs, the timer counts down from &lt;span style=&quot;font-family: courier;&quot;&gt;.status.completionTime&lt;/span&gt;. For failed Jobs, it uses the transition timestamp of the Failed condition.&lt;/li&gt;&lt;li&gt;Live Modifications: You can update or extend &lt;span style=&quot;font-family: courier;&quot;&gt;ttlSecondsAfterFinished&lt;/span&gt; while a Job is running or even after it finishes (as long as the original TTL hasn&#39;t already expired).&lt;/li&gt;&lt;li&gt;Log Loss: Because &lt;u&gt;Pods are destroyed during garbage collection&lt;/u&gt;, &lt;u&gt;application logs will be lost unless forwarded to an external logging system&lt;/u&gt; (e.g., Loki, OpenSearch, Datadog).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Essential kubectl Commands&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Operation&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Command&lt;/b&gt;&lt;/div&gt;&lt;div&gt;==================&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;===========&lt;/div&gt;&lt;div&gt;Create a Job imperatively&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;kubectl create job my-job --image=busybox -- echo &quot;Hello World&quot;&lt;/div&gt;&lt;div&gt;Get Job status&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; kubectl get jobs&lt;/div&gt;&lt;div&gt;Inspect job details&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;kubectl describe job my-job&lt;/div&gt;&lt;div&gt;List Pods associated with Job&amp;nbsp; &amp;nbsp;kubectl get pods --selector=batch.kubernetes.io/job-name=my-job&lt;/div&gt;&lt;div&gt;View logs of Job Pods&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; kubectl logs job/my-job&lt;/div&gt;&lt;div&gt;Delete Job and its Pods&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; kubectl delete job my-job&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Jobs vs Deployments vs CronJobs&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ┌─────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; Workload Type&amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └────────────┬────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;┌─────────────────────┴─────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp;Long-Running Services&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Batch Tasks / One-Off&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;(Deployments, StatefulSets)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (Jobs &amp;amp; CronJobs)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; Maintains target Pod&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Executes task, then&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; count indefinitely.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; terminates cleanly.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ┌────────────────┴────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ▼&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Single Run&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Scheduled Run&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (Job)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (CronJob)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: x-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/8763550686472898701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/8763550686472898701' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8763550686472898701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8763550686472898701'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/kubernetes-job-object.html' title='Kubernetes Job object'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/s72-w638-h640-c/Screenshot%202024-07-27%20at%2023.26.17.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-6733144431983132597</id><published>2026-08-03T12:27:07.766+01:00</published><updated>2026-08-03T13:54:44.665+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="cron"/><category scheme="http://www.blogger.com/atom/ns#" term="CronJob"/><category scheme="http://www.blogger.com/atom/ns#" term="crontab"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><title type='text'>Kubernetes CronJob</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/s1178/Screenshot%202024-07-27%20at%2023.26.17.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1178&quot; data-original-width=&quot;1176&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/w638-h640/Screenshot%202024-07-27%20at%2023.26.17.png&quot; width=&quot;638&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;A Kubernetes CronJob creates and manages short-lived Jobs on a scheduled, repeating basis. It is the Kubernetes equivalent of a standard Unix crontab file, making it ideal for &lt;u&gt;periodic tasks&lt;/u&gt; like database backups, report generation, or maintenance scripts.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Minimal Example Manifest&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;apiVersion: batch/v1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kind: CronJob&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name: nightly-backup&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; schedule: &quot;0 2 * * *&quot; # Runs every day at 02:00 UTC&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; timeZone: &quot;Etc/UTC&quot;&amp;nbsp; &amp;nbsp;# Optional: set preferred timezone (Kubernetes 1.27+)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; concurrencyPolicy: Forbid&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; startingDeadlineSeconds: 100&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; successfulJobsHistoryLimit: 3&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; failedJobsHistoryLimit: 1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &lt;b&gt;jobTemplate&lt;/b&gt;:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; template:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; containers:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - name: backup-task&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; image: alpine:latest&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; command:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - /bin/sh&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - -c&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - echo &quot;Running database backup...&quot;; sleep 5&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; restartPolicy: OnFailure&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Schedule Syntax Quick Reference&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The schedule field uses standard cron syntax with 5 fields:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;minute hour day-of-month month day-of-week&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Schedule Format Interpretation&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;*&amp;nbsp; &amp;nbsp; * * * *&amp;nbsp;&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Every minute&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;*/15 * * * *&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Every 15 minutes&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;0&amp;nbsp; &amp;nbsp; 0 * * *&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Every day at midnight&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;0&amp;nbsp; &amp;nbsp; 9 * * 1&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Every Monday at 9:00 AM&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Critical Settings&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;concurrencyPolicy&lt;/span&gt;: Controls how overlapping executions are handled when a previous run hasn&#39;t finished:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Allow&lt;/span&gt; (default): Runs concurrent jobs simultaneously.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Forbid&lt;/span&gt;: Skips the new job if the previous one is still running.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Replace&lt;/span&gt;: Cancels the currently running job and starts the new one.&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;startingDeadlineSeconds&lt;/span&gt;: The deadline (in seconds) for starting a job if it missed its scheduled time (e.g., cluster was temporarily down).&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;successfulJobsHistoryLimit&lt;/span&gt; / &lt;span style=&quot;font-family: courier;&quot;&gt;failedJobsHistoryLimit&lt;/span&gt;: Number of completed or failed Job/Pod records to keep for auditing before automatic cleanup.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;restartPolicy&lt;/span&gt;: Must be set on the pod template spec to either &lt;span style=&quot;font-family: courier;&quot;&gt;OnFailure&lt;/span&gt; or &lt;span style=&quot;font-family: courier;&quot;&gt;Never&lt;/span&gt; (&lt;span style=&quot;font-family: courier;&quot;&gt;Always&lt;/span&gt; is invalid for Jobs).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Helpful kubectl Commands&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Task&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Command&amp;nbsp;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;====&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;========&lt;/b&gt;&lt;/div&gt;&lt;div&gt;List CronJobs&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl get cronjobs&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Inspect configuration&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl describe cronjob &amp;lt;name&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Manually trigger immediately&amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl create job --from=cronjob/&amp;lt;cronjob-name&amp;gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;lt;manual-job-name&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;Pause schedule&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;kubectl patch cronjob &amp;lt;name&amp;gt; -p &#39;{&quot;spec&quot;:&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;{&quot;suspend&quot;:true}}&#39;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;View logs of latest run&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;kubectl logs job/&amp;lt;job-name&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;CronJobs Inner Mechanism&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Under the hood, Kubernetes CronJobs rely on a &lt;b&gt;decentralized control loop&lt;/b&gt; pattern. They are not handled by a traditional Linux cron daemon running on a single server, but rather by the Kubernetes Control Plane through cascading controllers.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How CronJobs Are Implemented&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The implementation follows a 3-tier hierarchical model:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;CronJob Object --&amp;gt; Job Object --&amp;gt; Pod(s)&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Rather than running code directly, a &lt;u&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;CronJob&lt;/span&gt; acts as a factory for &lt;span style=&quot;font-family: courier;&quot;&gt;Job&lt;/span&gt; objects, which in turn manage the Pods where your container actually executes&lt;/u&gt;.&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌─────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;kube-controller-manager&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp;┌────────────────&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─┐&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Creates&amp;nbsp; &amp;nbsp; &amp;nbsp; ┌─────────┐&amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp;│ CronJob Controller│ ─────────────────&amp;gt; │&amp;nbsp; &amp;nbsp;Job&amp;nbsp; &amp;nbsp;│&amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp;└─────────────────&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─┘&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └──&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─&lt;/span&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;─┬───┘&amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└──────────────────────────────────────────────────┼──────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Creates&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ┌─────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp;Pod&amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; └─────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;The Control Loop Mechanism&lt;/h4&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Synchronization Loop: The &lt;b&gt;CronJob Controller&lt;/b&gt; runs inside &lt;b&gt;kube-controller-manager&lt;/b&gt;. Every ~10 seconds, it iterates through all CronJob objects defined in the cluster.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Schedule Checking: The controller parses the schedule field (e.g., 0 * * * *) and compares the current time against the last time the job was executed.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Job Spawning: If a run is due, the CronJob controller reads the embedded jobTemplate and creates an actual &lt;b&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Job&lt;/span&gt;&lt;/b&gt; resource.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Execution: The cluster&#39;s separate &lt;b&gt;Job Controller &lt;/b&gt;detects the newly created &lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;Job&lt;/b&gt;&lt;/span&gt; resource and spawns one or more &lt;b&gt;Pods&lt;/b&gt; to execute your container workload to completion.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Garbage Collection: Depending on &lt;span style=&quot;font-family: courier;&quot;&gt;successfulJobsHistoryLimit&lt;/span&gt; and &lt;span style=&quot;font-family: courier;&quot;&gt;failedJobsHistoryLimit&lt;/span&gt;, the CronJob controller periodically deletes old completed Job objects (and their associated logs/pods).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Who Controls Them?&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Control over CronJobs is split between system components (automation) and users/roles (permissions).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;System Component Control&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;kube-controller-manager&lt;/b&gt;: The core control plane component where the &lt;b&gt;CronJob controller&lt;/b&gt; code actually executes. If this component is down, scheduled triggers will pause until it recovers.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;kube-apiserver&lt;/b&gt;: Stores the desired state in &lt;span style=&quot;font-family: courier;&quot;&gt;etcd&lt;/span&gt; and validates user manifests.&lt;/li&gt;&lt;li&gt;&lt;b&gt;kube-scheduler&lt;/b&gt;: Assigns the individual Pods spawned by the resulting Jobs to healthy worker nodes.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;User &amp;amp; Permission Control (RBAC)&lt;/h4&gt;&lt;div&gt;Human administrators and automated service accounts control CronJobs via Kubernetes Role-Based Access Control (RBAC):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Role / Action&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Required API Permissions (batch/v1)&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;=============&amp;nbsp; &amp;nbsp; &amp;nbsp;==============================&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Manage Schedules&amp;nbsp; &amp;nbsp; &amp;nbsp;create, update, patch, delete on cronjobs&lt;/div&gt;&lt;div&gt;View Status&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; get, list, watch on cronjobs&lt;/div&gt;&lt;div&gt;Manual Trigger&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; create permissions on jobs (to invoke kubectl create job --from=cronjob/...)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;Example RBAC Role for CronJob Operators:&lt;/p&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;apiVersion: rbac.authorization.k8s.io/v1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kind: Role&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; namespace: prod&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name: cronjob-operator&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;rules:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- apiGroups: [&quot;batch&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; resources: [&quot;cronjobs&quot;, &quot;jobs&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; verbs: [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;, &quot;create&quot;, &quot;update&quot;, &quot;patch&quot;, &quot;delete&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Technical Considerations&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;At-Least-Once Execution: Kubernetes schedules are designed around at-least-once execution semantics. Due to control loop timing or network hiccups, a scheduled job might occasionally run twice or run slightly late. &lt;u&gt;Workloads should always be designed to be &lt;b&gt;idempotent&lt;/b&gt;.&amp;nbsp;&lt;/u&gt;&lt;/li&gt;&lt;li&gt;Timezones: Controller clocks default to UTC or the local time of kube-controller-manager unless explicit timezones are passed via spec.timeZone (supported in K8s 1.27+).&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;CronJob is a controller object&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In Kubernetes, a &lt;b&gt;controller&lt;/b&gt; is a &lt;b&gt;control loop&lt;/b&gt; that watches the state of your cluster through the API server and makes changes attempting to move the current state toward the desired state.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is how a CronJob fits into the controller pattern:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Why CronJob is a Controller&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Custom Resource / Spec &amp;amp; Status Model&lt;/b&gt;: Like Deployment, ReplicaSet, and Job, a CronJob has an API object schema (spec defining desired behavior, status tracking execution state).&lt;/li&gt;&lt;li&gt;&lt;b&gt;Control Loop Execution&lt;/b&gt;: The CronJob implementation runs as a control loop inside the &lt;b&gt;kube-controller-manager&lt;/b&gt; component.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Cascading Controller Pattern&lt;/b&gt;: CronJob sits at the top of a controller hierarchy:&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;CronJob Controller&lt;/b&gt; --[creates/manages]--&amp;gt; &lt;b&gt;Job Controller&lt;/b&gt; --[creates/manages]--&amp;gt; &lt;b&gt;Pods&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The CronJob Controller reconciles the CronJob spec: it checks the schedule, creates Job resources when a execution is due, cleans up old jobs based on history limits, and handles concurrency policies.&lt;/li&gt;&lt;li&gt;The Job Controller reconciles those created Job resources to manage individual Pods to completion.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Summary Table&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Controller&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; API Group&amp;nbsp; &amp;nbsp; &amp;nbsp; What it Watches&amp;nbsp; &amp;nbsp; &amp;nbsp; What it Creates/Manages&lt;/b&gt;&lt;/div&gt;&lt;div&gt;========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=========&amp;nbsp; &amp;nbsp; &amp;nbsp;=============&amp;nbsp; &amp;nbsp; &amp;nbsp; ===================&lt;/div&gt;&lt;div&gt;CronJob Controller&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; batch/v1&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; CronJob specs&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Job objects&lt;/div&gt;&lt;div&gt;Job Controller&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; batch/v1&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Job specs&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Pod objects&lt;/div&gt;&lt;div&gt;Deployment Controller&amp;nbsp; &amp;nbsp; &amp;nbsp; apps/v1&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Deployment specs&amp;nbsp; &amp;nbsp; &amp;nbsp; ReplicaSet objects&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/6733144431983132597/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/6733144431983132597' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6733144431983132597'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6733144431983132597'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/kubernetes-cronjob.html' title='Kubernetes CronJob'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjNcIpCzZVp3_zhSgcHAbs4YGdJzLIASdHB-KYbFKNi3lnjVygRjX4fDjnxWOXjrkqWuVOeP1Liq5LHCz-i8dwOfoo8M2otsV1D7UArIXfLoNZkx4MXG7T0u0GEHpHbjBklyjafBfwojeGxQrM7FP2XxSk1SZV5VTSQ4d2YdcVUSTpPUHtHNC7FXNiqQy4/s72-w638-h640-c/Screenshot%202024-07-27%20at%2023.26.17.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-5997442019442971794</id><published>2026-08-03T11:46:28.850+01:00</published><updated>2026-08-03T12:13:38.249+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Grafana"/><category scheme="http://www.blogger.com/atom/ns#" term="Observability"/><title type='text'>Grafana Alerting</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_AoWdZPM-W5WL-HKd3TXhZ2cBC1VzIHNGY3xwKj4J1ptIWzQTNvd_vnAK5OqzEWeuR9ZOzRSEo5hhXniAwg1ENJAnl6I4iWFKtmqZteQFIW-0Oq-L8a4p0mbF3e9ekyjboHKSz_zgL6piQHR4VLIWeyyYjlGGFytM2Nq8qgv9IkvPFVKmux3FJFXOmW0/s1225/Grafana_logo.svg.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1225&quot; data-original-width=&quot;1200&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_AoWdZPM-W5WL-HKd3TXhZ2cBC1VzIHNGY3xwKj4J1ptIWzQTNvd_vnAK5OqzEWeuR9ZOzRSEo5hhXniAwg1ENJAnl6I4iWFKtmqZteQFIW-0Oq-L8a4p0mbF3e9ekyjboHKSz_zgL6piQHR4VLIWeyyYjlGGFytM2Nq8qgv9IkvPFVKmux3FJFXOmW0/w626-h640/Grafana_logo.svg.png&quot; width=&quot;626&quot; /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How it works&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Grafana alerting periodically queries &lt;b&gt;data sources&lt;/b&gt; and evaluates the &lt;b&gt;condition&lt;/b&gt; defined in the &lt;b&gt;alert rule&lt;/b&gt;&lt;/li&gt;&lt;li&gt;If the condition is breached, an &lt;b&gt;alert instance&lt;/b&gt; fires&lt;/li&gt;&lt;li&gt;Firing instances are routed to &lt;b&gt;notification policies&lt;/b&gt; based on matching &lt;b&gt;labels&lt;/b&gt;&lt;/li&gt;&lt;li&gt;Notifications are sent out to the &lt;b&gt;contact points&lt;/b&gt; specified in the notification policy&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How to set alerts&lt;/h2&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Alert rules: Create an alert rule to query a data source and evaluate the condition defined in the alert rule.&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;There are two types of alerts rules:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Grafana-managed. Examples:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;APM&lt;/li&gt;&lt;li&gt;AWS&lt;/li&gt;&lt;li&gt;Data Quality&lt;/li&gt;&lt;li&gt;Kubernetes&lt;/li&gt;&lt;li&gt;MongoDB&lt;/li&gt;&lt;li&gt;Storage&lt;/li&gt;&lt;li&gt;Synthetics&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Data source-managed.&amp;nbsp;&lt;u&gt;Data sources containing configured alerts rules are for example&amp;nbsp;&lt;b&gt;Mimir&lt;/b&gt; or &lt;b&gt;Loki&lt;/b&gt; data sources&lt;/u&gt; where alert rules are stored and evaluated in the data sources itself. In these data sources you can select Manage alerts via Alerting UI to be able to manage these alerts rules in the Grafana UI as well as in the data source where they were configured.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Prometheus&lt;/li&gt;&lt;li&gt;Mimir&lt;/li&gt;&lt;li&gt;Loki&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;Define the condition that must be met before an alert rule fires&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Route alert notifications either directly to a contact point or through notification policies for more flexibility&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Contact points: Configure who receives notifications and how they are sent&lt;/li&gt;&lt;li&gt;Notification policies: Configure how firing alert instances are routed to contact points&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Monitor your alert rules using dashboards and visualizations&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/5997442019442971794/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/5997442019442971794' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/5997442019442971794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/5997442019442971794'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/08/grafana-alerting.html' title='Grafana Alerting'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_AoWdZPM-W5WL-HKd3TXhZ2cBC1VzIHNGY3xwKj4J1ptIWzQTNvd_vnAK5OqzEWeuR9ZOzRSEo5hhXniAwg1ENJAnl6I4iWFKtmqZteQFIW-0Oq-L8a4p0mbF3e9ekyjboHKSz_zgL6piQHR4VLIWeyyYjlGGFytM2Nq8qgv9IkvPFVKmux3FJFXOmW0/s72-w626-h640-c/Grafana_logo.svg.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-7266451145837660859</id><published>2026-07-21T16:49:50.806+01:00</published><updated>2026-07-21T16:49:50.806+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="CRD"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><title type='text'>Kubernetes Custom Resource Definition (CRD)</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfIRIYEgjyLz76Mbd9GA5eca0bENUW_4_AdrnqR1khLYCgHyJ0-avzY-ocHiP3LkrJFi5Q3BXl2C_F8dAHW6zBXBbganZe_hFKzDRrqX4o-e_KI1Gsn-VfWwTsANAu6rDChnM-umSxmHLMnE2njRJAQtATU4cMshWM7E2G6En13SuRJgqpSLdWyf5l7tQ/s1234/Kubernetes_logo_without_workmark.svg.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1199&quot; data-original-width=&quot;1234&quot; height=&quot;622&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfIRIYEgjyLz76Mbd9GA5eca0bENUW_4_AdrnqR1khLYCgHyJ0-avzY-ocHiP3LkrJFi5Q3BXl2C_F8dAHW6zBXBbganZe_hFKzDRrqX4o-e_KI1Gsn-VfWwTsANAu6rDChnM-umSxmHLMnE2njRJAQtATU4cMshWM7E2G6En13SuRJgqpSLdWyf5l7tQ/w640-h622/Kubernetes_logo_without_workmark.svg.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Why are Custom Resource Definitions (CRDs) stored in etcd?&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;Custom Resource Definitions (CRDs) are stored in etcd because &lt;u&gt;etcd serves as the single source of truth and persistent data store for the entire Kubernetes control plane&lt;/u&gt;. Storing CRDs in etcd allows the kube-apiserver to dynamically register, validate, and manage custom resources exactly like native objects without requiring a separate database.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why Kubernetes Stores CRDs in etcd&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Native API Integration: The kube-apiserver inherently reads from and writes to etcd. Storing CRDs here allows custom objects to instantly leverage native features like kubectl support, RBAC security, and namespaces.&lt;/li&gt;&lt;li&gt;Consistent State: Like Pods or Deployments, custom resources represent the &quot;desired state&quot; of a system. etcd provides the strong consistency and distributed consensus needed to safely store this state across cluster nodes.&lt;/li&gt;&lt;li&gt;Watch Mechanism: Custom controllers and operators rely on the Kubernetes watch API to listen for resource changes. Because etcd natively supports watch events, controllers can instantly react when a custom resource is created, updated, or deleted.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How custom controllers interact with the stored CRD data using the reconciliation loop?&lt;/h2&gt;&lt;p&gt;Custom controllers interact with etcd through a continuous synchronization mechanism called the reconciliation loop. This loop constantly drives the actual state of the cluster toward your desired state.&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Here is exactly how custom controllers interact with stored CRD data:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;1. The Relationship to CRDs&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;A CRD (Custom Resource Definition) acts as the database schema. It defines the structure and validation rules for your custom data. When you create a CR (Custom Resource) based on that CRD, its data is stored directly in etcd.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The custom controller is the active software component (the brain) that watches that specific CR data and takes action based on what is stored.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;2. The Reconciliation Loop Step-by-Step&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The controller executes a continuous loop, often formalized as a Reconcile() function, which follows a Observe, Analyze, Act workflow:&amp;nbsp; &amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp; +--------------------------------------------+&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1. OBSERVE&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;|&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp;(Watch etcd via API Server)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; +---------------------+----------------------+&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; v&lt;br /&gt;&amp;nbsp; &amp;nbsp; +---------------------+----------------------+&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2. ANALYZE&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;|&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; (Compare Desired vs. Actual State)&amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; +---------------------+----------------------+&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; v&lt;br /&gt;&amp;nbsp; &amp;nbsp; +---------------------+----------------------+&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3. ACT&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;|&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp;(Create/Update/Delete Infrastructure)&amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; |&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; |&lt;br /&gt;&amp;nbsp; &amp;nbsp; +--------------------------------------------+&lt;/span&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Step 1: Observe (The Watch Mechanism): Instead of constantly polling etcd, the controller establishes a long-lived HTTP connection (a watch) to the kube-apiserver. When a user creates, updates, or deletes a CR in etcd, the API server instantly broadcasts an event. The controller captures this event and queues the resource&#39;s key for processing.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Step 2: Analyze (Diffing the State): The controller fetches the full CR data from etcd. It reads the .spec section, which represents the user&#39;s desired state (e.g., &quot;replicas&quot;: 3). It then looks at the real-world cluster infrastructure to see the actual state (e.g., only 2 pods are running).&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Step 3: Act (Fixing the Drift): The controller executes the necessary logic to close the gap. In this case, it talks to the API server to spin up a 3rd pod.&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;Step 4: Update Status: Once the action is complete, the controller updates the .status subresource of the CR. The API server writes this new status back to etcd, confirming the actual state now matches the desired state.&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Key Characteristics&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Level-Triggered: The loop reacts not just to isolated events, but to the current state of the system. If the controller crashes and restarts, it reads etcd to immediately understand the current state and resume fixing any drift.&lt;/li&gt;&lt;li&gt;Asynchronous: The user gets an immediate &quot;Success&quot; response from the API server when saving a CR to etcd. The controller processes the request in the background asynchronously.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/7266451145837660859/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/7266451145837660859' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7266451145837660859'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7266451145837660859'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/07/kubernetes-custom-resource-definition.html' title='Kubernetes Custom Resource Definition (CRD)'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfIRIYEgjyLz76Mbd9GA5eca0bENUW_4_AdrnqR1khLYCgHyJ0-avzY-ocHiP3LkrJFi5Q3BXl2C_F8dAHW6zBXBbganZe_hFKzDRrqX4o-e_KI1Gsn-VfWwTsANAu6rDChnM-umSxmHLMnE2njRJAQtATU4cMshWM7E2G6En13SuRJgqpSLdWyf5l7tQ/s72-w640-h622-c/Kubernetes_logo_without_workmark.svg.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-6114982166090212725</id><published>2026-07-17T13:10:13.307+01:00</published><updated>2026-07-17T15:55:33.484+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS EKS"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Terraform"/><title type='text'>Accessing AWS EKS Cluster from EC2</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi8WwmUAx8p5XNQtaUg3CDEWtdFb70hoHgNBw3rxEbkiGpH-WPnvz0Ya09qE8B5a82wYui3oJ641ylIM-pXNUY6EV3C1uovGGLNe3oZ-LJQgWPS8HNi89nVLlTlCQRsniIwRaEsbxSAJq6rdmJap9mDX91dAYDb5UBicVZfrFk-hOrcwifBx8K36NVD3Nk&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;639&quot; data-original-width=&quot;638&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi8WwmUAx8p5XNQtaUg3CDEWtdFb70hoHgNBw3rxEbkiGpH-WPnvz0Ya09qE8B5a82wYui3oJ641ylIM-pXNUY6EV3C1uovGGLNe3oZ-LJQgWPS8HNi89nVLlTlCQRsniIwRaEsbxSAJq6rdmJap9mDX91dAYDb5UBicVZfrFk-hOrcwifBx8K36NVD3Nk=w640-h640&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;When an EC2 instance (acting as a CI/CD runner, bastion, or deployment node) runs Terraform or Helm to interact with a &lt;u&gt;private&lt;/u&gt; EKS cluster, it needs two things:&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;network visibility (which it gets by being in the same VPC or connected network) and&amp;nbsp;&lt;/li&gt;&lt;li&gt;proper IAM-to-Kubernetes mappings&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;AWS modern access management feature uses &lt;b&gt;EKS Access Entries&lt;/b&gt;. This native API feature entirely replaces the messy, deprecated aws-auth ConfigMap.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Here is how to configure the &lt;b&gt;IAM Role&lt;/b&gt; and wire it into the cluster&#39;s auth configuration using Terraform.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;1. The EC2 IAM Role (AWS Side)&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The EC2 instance does not need any specific EKS admin permissions attached directly to its IAM role policies. It just needs a standard IAM Role that it can assume via an EC2 Instance Profile. The actual Kubernetes cluster access is granted inside EKS by referencing this role&#39;s Amazon Resource Name (ARN).&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 1. Create the IAM Role for the EC2 Instance&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_iam_role&lt;/b&gt;&quot; &quot;cicd_runner&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name = &quot;eks-cicd-runner-role&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; assume_role_policy = jsonencode({&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Version = &quot;2012-10-17&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Statement = [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Action&amp;nbsp; &amp;nbsp; = &quot;sts:AssumeRole&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Effect&amp;nbsp; &amp;nbsp; = &quot;Allow&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Principal = { Service = &quot;ec2.amazonaws.com&quot; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; ]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; })&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;})&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 2. Create the Instance Profile so the EC2 can use the role&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_iam_instance_profile&lt;/b&gt;&quot; &quot;cicd_runner_profile&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name = &quot;eks-cicd-runner-profile&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; role = aws_iam_role.cicd_runner.name&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2. Wiring it into the Cluster (EKS Side)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To authorize this IAM role to run Helm deployments or manage Kubernetes resources via Terraform, you use &lt;b&gt;EKS Access Entries&lt;/b&gt; (&lt;span style=&quot;font-family: courier;&quot;&gt;aws_eks_access_entry&lt;/span&gt;) combined with Access Policy Associations (&lt;span style=&quot;font-family: courier;&quot;&gt;aws_eks_access_policy_association&lt;/span&gt;).&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;⚠️ Prerequisite: Ensure your &lt;span style=&quot;font-family: courier;&quot;&gt;aws_eks_cluster&lt;/span&gt; resource has &lt;span style=&quot;font-family: courier;&quot;&gt;authentication_mode = &quot;API_AND_CONFIG_MAP&quot;&lt;/span&gt; or &lt;span style=&quot;font-family: courier;&quot;&gt;&quot;API&quot;&lt;/span&gt; enabled so it accepts Access Entries.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Terraform Configuration&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 1. Define the Access Entry mapping the IAM Role ARN to EKS&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_eks_access_entry&lt;/b&gt;&quot; &quot;cicd_runner_entry&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; cluster_name&amp;nbsp; = &quot;my-cluster-name&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; principal_arn = aws_iam_role.cicd_runner.arn&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; type&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;STANDARD&quot; # Standard workflow for users/roles&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 2. Attach an AWS-managed access policy to the Access Entry&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_eks_access_policy_association&lt;/b&gt;&quot; &quot;cicd_admin_mapping&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; cluster_name&amp;nbsp; = &quot;my-cluster-name&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; principal_arn = aws_iam_role.cicd_runner.arn&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; policy_arn&amp;nbsp; &amp;nbsp; = &quot;arn:aws:eks::aws:cluster-access-policy/&lt;b&gt;AmazonEKSClusterAdminPolicy&lt;/b&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; access_scope {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; type = &quot;cluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Another example (in the cluster&#39;s Terraform source code):&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# read-only access entry for the in-VPC runner&#39;s instance role, so operators can kubectl the&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# private cluster via SSM-on-the-runner for diagnostics without an out-of-band `aws eks create-access-entry` grant each time. AmazonEKSAdminViewPolicy = read all resources incl.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# CRDs, excluding Secrets — safe for diagnostics. The runner role is defined in another repo&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# (applications/test/github-runner); it also gets eks:DescribeCluster there so `aws eks update-kubeconfig`&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# works. Writes still require github-actions-role (the CD/harness OIDC principal above).&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_eks_access_entry&lt;/b&gt;&quot; &quot;runner_readonly&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; cluster_name&amp;nbsp; = local.cluster_name&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; principal_arn = &quot;arn:aws:iam::1234567890123:role/github-runner-role&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; type&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;STANDARD&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; depends_on = [module.eks]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_eks_access_policy_association&lt;/b&gt;&quot; &quot;runner_readonly&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; cluster_name&amp;nbsp; = local.cluster_name&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; policy_arn&amp;nbsp; &amp;nbsp; = &quot;arn:aws:eks::aws:cluster-access-policy/&lt;b&gt;AmazonEKSAdminViewPolicy&lt;/b&gt;&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; principal_arn = aws_eks_access_entry.runner_readonly.principal_arn&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; access_scope {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; type = &quot;cluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;and in EC2 Terraform repo:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# eks:DescribeCluster so `aws eks update-kubeconfig` works from the runner. Paired with the&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# read-only EKS access entry for this role on test-default-eks (infra-k8s), it lets operators&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# kubectl the private cluster via SSM for diagnostics without a hand-built kubeconfig or an ad-hoc grant.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# IAM (this) authorises the DescribeCluster call; the access entry (RBAC) authorises what kubectl can read.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;data &quot;&lt;b&gt;aws_iam_policy_document&lt;/b&gt;&quot; &quot;eks_describe&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; statement {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; sid&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= &quot;EksDescribeCluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; actions&amp;nbsp; &amp;nbsp;= [&quot;eks:DescribeCluster&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; resources = [&quot;arn:aws:eks:${var.aws_region}:${data.aws_caller_identity.current.account_id}:cluster/test-default-eks&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_iam_role_policy&lt;/b&gt;&quot; &quot;eks_describe&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name&amp;nbsp; &amp;nbsp;= &quot;${local.name}-eks-describe&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; role&amp;nbsp; &amp;nbsp;= aws_iam_role.runner.id&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; policy = data.aws_iam_policy_document.eks_describe.json&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Alternative: Custom Kubernetes Groups&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you don&#39;t want to grant full &lt;span style=&quot;font-family: courier;&quot;&gt;AmazonEKSClusterAdminPolicy&lt;/span&gt; and prefer &lt;u&gt;mapping the EC2 instance to custom Kubernetes RBAC roles&lt;/u&gt;, you can pass &lt;b&gt;target groups&lt;/b&gt; directly via the access entry:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;&lt;b&gt;aws_eks_access_entry&lt;/b&gt;&quot; &quot;cicd_runner_entry&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; cluster_name&amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;my-cluster-name&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; principal_arn&amp;nbsp; &amp;nbsp; &amp;nbsp;= aws_iam_role.cicd_runner.arn&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &lt;b&gt;kubernetes_groups&lt;/b&gt; = [&quot;my-custom-helm-deployers-group&quot;]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; type&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;STANDARD&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;(You would then manage a native Kubernetes ClusterRoleBinding pointing to my-custom-helm-deployers-group using the kubernetes provider.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;3. How the EC2 Instance Connects&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once the IAM and EKS access configurations are applied, you don&#39;t need to pass raw AWS access keys to the EC2 instance. The tools leverage the Instance Profile automatically.&lt;/div&gt;&lt;div&gt;For Helm &amp;amp; kubectl:Log onto the EC2 instance (or configure your shell script/user-data) to update the local kubeconfig using the AWS CLI:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;aws eks update-kubeconfig --region us-east-1 --name my-cluster-name&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When Helm or kubectl is invoked, it calls the aws eks get-token command under the hood, uses the EC2&#39;s IAM profile to generate a signed token, routes over the private VPC endpoint network, and authenticates flawlessly.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;For the Terraform Kubernetes/Helm Providers:&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If Terraform itself is running on that EC2 instance and deploying helm charts into EKS, configure your Terraform provider block to pull tokens dynamically using the instance profile credentials:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;data &quot;aws_eks_cluster_auth&quot; &quot;cluster&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name = &quot;my-cluster-name&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;provider &quot;helm&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; kubernetes {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; host&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= &quot;https://ABC123XYZ.gr7.us-east-1.eks.amazonaws.com&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; cluster_ca_certificate = base64decode(&quot;CLUSTER_CA_DATA&quot;)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; token&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = data.aws_eks_cluster_auth.cluster.token&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Just-In-Time (JIT) Access (Least Privilege Workflow)&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Another approach is a &lt;b&gt;Just-In-Time (JIT) access&lt;/b&gt; or &lt;b&gt;Least Privilege workflow&lt;/b&gt;. It is highly secure, but it can quickly become an operational headache for a DevOps engineer if it isn&#39;t completely automated.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Instead of giving your GitHub Actions runner continuous, permanent admin access to your private EKS cluster, you &lt;u&gt;only grant access for the exact duration of an ad-hoc task, and then immediately rip it away&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is exactly how that breakdown works mechanically step-by-step, why someone would build it, and the hidden risks you should look out for.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;The Workflow Cycle Breakdown&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When you trigger a workflow or step that needs to run an ad-hoc kubectl command, an automation tool (like a pipeline script, a step function, or a privileged security wrapper) executes this lifecycle:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;[Pipeline Starts]&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/div&gt;&lt;div&gt;1. CREATE ACCESS ENTRY ──► (Allows &#39;github-runner-role&#39; network identity into EKS)&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/div&gt;&lt;div&gt;2. ASSOCIATE POLICY&amp;nbsp; &amp;nbsp; ──► (Binds ClusterAdmin or specific RBAC permissions)&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/div&gt;&lt;div&gt;3. RUN KUBECTL COMMAND ──► (The runner executes its ad-hoc tasks safely)&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/div&gt;&lt;div&gt;4. DISASSOCIATE POLICY ──► (Strip away the RBAC permissions)&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/div&gt;&lt;div&gt;5. DELETE ACCESS ENTRY ──► (Completely remove the IAM role mapping from EKS)&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▼&lt;/div&gt;&lt;div&gt;[Pipeline Finishes]&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. The Gate: create-access-entry&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;By default, your runner&#39;s IAM role (github-runner-role) is completely blocked at the EKS front door. Even if it can physically route to the API endpoint, EKS will return a 401 Unauthorized.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The setup script calls the AWS API to create an EKS Access Entry. This tells EKS: &quot;Be ready to recognize this specific IAM role ARN.&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. The Permission: associate-access-policy&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Creating the entry just gets the runner past the front door; it still has zero privileges inside the cluster. The next API call binds an access policy (like &lt;span style=&quot;font-family: courier;&quot;&gt;AmazonEKSClusterAdminPolicy&lt;/span&gt; or a custom namespace policy) to that entry. Now kubectl commands will actually work.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3. The Execution: Ad-hoc Commands&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The runner runs &lt;span style=&quot;font-family: courier;&quot;&gt;aws eks update-kubeconfig&lt;/span&gt;, grabs its temporary token, and runs the necessary &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl&lt;/span&gt; or &lt;span style=&quot;font-family: courier;&quot;&gt;helm&lt;/span&gt; actions.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;4. The Clean-up: Revoking Access&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Once the kubectl step finishes, the pipeline runs a cleanup block (usually wrapped in a always() or finally clause to ensure it runs even if the deployment fails). It reverses the process by disassociating the policy and deleting the access entry entirely. The runner is now unauthorized again.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Why would someone architecture it this way?&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Blast Radius Reduction: If that specific GitHub runner instance or the AWS role credentials are ever compromised, the attacker cannot access your Kubernetes cluster because the role has no standing permissions.&lt;/li&gt;&lt;li&gt;Audit Trails: Every single time access is granted, used, and revoked, a permanent trail is logged in AWS CloudTrail. It makes passing compliance checks exceptionally easy because you can prove no system has persistent, unchallenged access.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;The Catch: Why this pattern can trip you up&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While it sounds bulletproof on paper, this setup has a few major operational trade-offs that you have to manage closely:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Pipeline Failures Leave Backdoors: If the runner gets abruptly killed (e.g., the GitHub Actions job is manually canceled mid-run, or the runner machine loses power), the cleanup step might never run. The role will remain an admin of your cluster until someone goes in and manually deletes the entry.&lt;/li&gt;&lt;li&gt;Race Conditions on Concurrent Runs: If you have multiple workflows trying to use that same runner role at the same time, Run A might delete the access entry while Run B is right in the middle of executing a kubectl apply.&lt;/li&gt;&lt;li&gt;AWS API Throttling: If you scale up your CI/CD pipelines and are constantly creating and destroying access entries every few minutes, you can easily hit AWS API rate limits (throttled EKS control plane requests).&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;For the same EC2 instance (github-runner-role) to grant itself access via the JIT loop, it must possess two entirely separate sets of permissions:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Control Plane Permissions (AWS IAM): It needs permission to call the AWS EKS APIs (CreateAccessEntry, AssociateAccessPolicy) to tell AWS, &quot;Hey, let me in.&quot;&lt;/li&gt;&lt;li&gt;Data Plane Permissions (Kubernetes RBAC): This is what it actually gives itself inside the cluster to view pods, run Helm charts, or apply manifests.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is how this works in practice, how to configure the IAM policy so the runner can self-authorize, and why you must be extremely cautious with this pattern.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;1. The Setup: The Instance&#39;s Base IAM Policy&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To make this happen, the IAM role attached to the EC2 instance must be pre-provisioned with &lt;u&gt;standard AWS permissions allowing it to modify the cluster&#39;s access configuration&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You must attach an IAM policy like this to the github-runner-role:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;{&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;Version&quot;: &quot;2012-10-17&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;Statement&quot;: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Sid&quot;: &quot;AllowEKSAccessEntrySelfManagement&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Effect&quot;: &quot;Allow&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Action&quot;: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;eks:&lt;b&gt;CreateAccessEntry&lt;/b&gt;&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;eks:DeleteAccessEntry&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;eks:AssociateAccessPolicy&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;eks:DisassociateAccessPolicy&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;eks:DescribeCluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Resource&quot;: &quot;arn:aws:eks:us-east-1:123456789012:cluster/my-cluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Sid&quot;: &quot;AllowSTSAssumeRoleSelf&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Effect&quot;: &quot;Allow&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Action&quot;: &quot;sts:AssumeRole&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &quot;Resource&quot;: &quot;arn:aws:iam::123456789012:role/github-runner-role&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; ]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;💡 Note on the STS block: This allows the runner role to assume itself to generate those unique, concurrent session ARNs (.../github-runner-role/gh-run-123) we discussed earlier.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;2. The Execution Flow&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When your automation script starts on the EC2 instance, it uses its own local AWS credentials (provided by the EC2 Instance Profile) to run the exact commands that grant it access:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 1. The EC2 instance talks to AWS IAM &amp;amp; STS to get a unique session ARN&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# (This isolates concurrent runs from each other)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;ASSUMED_ROLE_ARN=&quot;arn:aws:sts::123456789012:assumed-role/github-runner-role/gh-run-${GITHUB_RUN_ID}&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 2. The EC2 instance calls the AWS EKS API using its base IAM permissions&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;b&gt;aws eks create-access-entry&lt;/b&gt; \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --cluster-name my-cluster \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --principal-arn &quot;$ASSUMED_ROLE_ARN&quot; \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --type STANDARD&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 3. The EC2 instance elevates its own session&#39;s privileges inside Kubernetes&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;b&gt;aws eks associate-access-policy&lt;/b&gt; \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --cluster-name my-cluster \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --principal-arn &quot;$ASSUMED_ROLE_ARN&quot; \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --policy-arn &quot;arn:aws:eks::aws:cluster-access-policy/&lt;b&gt;AmazonEKSClusterAdminPolicy&lt;/b&gt;&quot; \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --access-scope type=cluster&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;We could also assign a cluster read-only policy,&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;AmazonEKSAdminViewPolicy&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;At this point, the instance has successfully leveraged its permanent AWS-level permissions to dynamically grant itself Kubernetes-level permissions.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;h3&gt;3. The Security Catch: &quot;The Illusion of Security&quot;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While this satisfies the requirement of keeping the Kubernetes cluster clean of permanent admin entries, you need to look closely at the security trade-off here.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If an attacker compromises this EC2 instance, they inherit the base IAM role. Because that base role has permission to call CreateAccessEntry and AssociateAccessPolicy, the attacker can simply run the exact same script to grant themselves cluster admin privileges whenever they want.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;How to make it actually secure:&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To make this pattern truly robust, the EC2 instance should not have direct permission to grant itself access. Instead, a external &quot;Gatekeeper&quot; should handle it:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Lambda Gatekeeper: The runner sends a request to an internal AWS Lambda function (or an internal API gateway). The Lambda verifies the job context (e.g., checks if there is a valid, active GitHub Action job running right now), and if verified, the Lambda creates the EKS Access Entry on behalf of the runner.&lt;/li&gt;&lt;li&gt;OIDC/IRSA Approach: If this runner is executing jobs from GitHub, skip the EC2 instance profile entirely. Use GitHub&#39;s OIDC token to exchange directly for a short-lived AWS session that EKS recognizes natively. This removes the &quot;self-granting&quot; risk entirely.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/6114982166090212725/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/6114982166090212725' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6114982166090212725'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6114982166090212725'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/07/accessing-aws-eks-cluster-from-ec2.html' title='Accessing AWS EKS Cluster from EC2'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEi8WwmUAx8p5XNQtaUg3CDEWtdFb70hoHgNBw3rxEbkiGpH-WPnvz0Ya09qE8B5a82wYui3oJ641ylIM-pXNUY6EV3C1uovGGLNe3oZ-LJQgWPS8HNi89nVLlTlCQRsniIwRaEsbxSAJq6rdmJap9mDX91dAYDb5UBicVZfrFk-hOrcwifBx8K36NVD3Nk=s72-w640-h640-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-533126759264904169</id><published>2026-07-17T12:18:40.038+01:00</published><updated>2026-07-17T12:18:40.039+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS EKS"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><title type='text'>AWS EKS Cluster API</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi8WwmUAx8p5XNQtaUg3CDEWtdFb70hoHgNBw3rxEbkiGpH-WPnvz0Ya09qE8B5a82wYui3oJ641ylIM-pXNUY6EV3C1uovGGLNe3oZ-LJQgWPS8HNi89nVLlTlCQRsniIwRaEsbxSAJq6rdmJap9mDX91dAYDb5UBicVZfrFk-hOrcwifBx8K36NVD3Nk&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;639&quot; data-original-width=&quot;638&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEi8WwmUAx8p5XNQtaUg3CDEWtdFb70hoHgNBw3rxEbkiGpH-WPnvz0Ya09qE8B5a82wYui3oJ641ylIM-pXNUY6EV3C1uovGGLNe3oZ-LJQgWPS8HNi89nVLlTlCQRsniIwRaEsbxSAJq6rdmJap9mDX91dAYDb5UBicVZfrFk-hOrcwifBx8K36NVD3Nk=w640-h640&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;In AWS EKS, managing the cluster&#39;s API endpoint correctly is one of the most critical steps for balancing day-to-day DevOps convenience with robust security.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Here is exactly how the EKS API works, what public vs. private means, how subnets factor into the equation, and how authentication is handled.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;1. What is the Cluster&#39;s API?&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The cluster API is the Kubernetes API Server control plane hosted and &lt;u&gt;managed by AWS&lt;/u&gt;. It is the single entry point for all administrative actions. Every time you run a command via &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl&lt;/span&gt;, deploy a Helm chart, or when worker nodes send heartbeats, they are talking to this API server endpoint. AWS gives you a unique HTTPS URL for this endpoint (e.g., &lt;span style=&quot;font-family: courier;&quot;&gt;https://ABC123XYZ.gr7.us-east-1.eks.amazonaws.com&lt;/span&gt;&lt;span style=&quot;font-family: inherit;&quot;&gt;)&lt;/span&gt;.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Public vs. Private Endpoints&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;This setting dictates network-level visibility—essentially, who can physically route traffic to that HTTPS URL.&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;p style=&quot;text-align: left;&quot;&gt;EKS supports three access modes:&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Public Only (Default)&lt;/b&gt;: The API URL resolves to a public IP address. Anyone on the internet can physically reach the login page, but they still must authenticate to get in. Worker nodes inside your VPC must route out to the internet (often via a NAT Gateway) to talk back to the control plane.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Public and Private (Recommended for most production setups)&lt;/b&gt;: The URL resolves to a public IP for external users, but within your VPC, it resolves directly to private IP addresses via internal Cross-Account ENIs (Elastic Network Interfaces). Your worker nodes communicate completely internally, while you can still run kubectl from your local machine without a VPN. You can also allowlist specific CIDRs/IPs on the public side to lock it down to your office or home IP.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Private Only&lt;/b&gt;: The public internet endpoint is completely shut off. The &lt;u&gt;API URL resolves only to private IPs inside your VPC&lt;/u&gt;. To run a &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl&lt;/span&gt; command, &lt;u&gt;you must be inside the VPC&lt;/u&gt; network (e.g., via AWS Client VPN, Transit Gateway, or a bastion host).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;How to change it:&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;You can switch this at any time with no downtime via the AWS Console (Cluster -&amp;gt; Networking -&amp;gt; Manage Endpoint Access) or via the AWS CLI:&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Example: Switch to Public + Private recommended mode&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;aws eks update-cluster-config \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --name my-cluster \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; --resources-vpc-config endpointPublicAccess=true,endpointPrivateAccess=true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Does it matter which Subnets the cluster runs on?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yes, but do not confuse Cluster Subnets with the API Endpoint setting. They are two distinct layers.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When you create an EKS cluster, you must provide a set of subnets (usually at least two in different Availability Zones). EKS drops its managed network interfaces (X-ENIs) into these subnets so the control plane can communicate with your worker nodes.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Best Practice: Always provide private subnets (subnets that route outbound via a NAT gateway and don&#39;t assign public IPs to instances) for your actual worker nodes and EKS ENIs. This keeps your EC2 nodes completely hidden from the internet.&lt;/li&gt;&lt;li&gt;The Disconnect: You can have an EKS cluster running on entirely private subnets, but still have a Public API endpoint. The API endpoint is hosted by AWS&#39;s managed infrastructure, not directly inside your subnets.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Does the API require authentication? Who can access it?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yes, absolutely. Network visibility (Public/Private) is just the perimeter fence. Even if your API endpoint is wide-open public, nobody can do anything without explicit authentication and authorization.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;EKS uses a dual-layer system to handle access:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Layer 1: Authentication (Who are you?) via AWS IAM&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;EKS natively leverages AWS IAM for authentication. When you run kubectl, your local setup uses the AWS CLI to generate a temporary, signed token based on your current AWS IAM identity (User or Role). It passes this token in the HTTPS header to the EKS API. EKS verifies this token with AWS IAM to prove you are who you say you are.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Layer 2: Authorization (What can you do?) via Kubernetes RBAC&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Just having a valid IAM identity isn&#39;t enough. Once EKS knows your identity, it hands the request off to the internal Kubernetes Role-Based Access Control (RBAC) engine.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;By default, the IAM identity that originally created the EKS cluster is automatically granted full cluster administrator (system:masters) privileges.&lt;/li&gt;&lt;li&gt;For anyone else (other team members, CI/CD tools), you must map their AWS IAM ARN to specific Kubernetes Groups using the EKS Access Entries feature (or the legacy aws-auth ConfigMap) and assign them RBAC Roles/ClusterRoles.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Summary of the flow:&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;kubectl get pods --&amp;gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Local AWS CLI generates an IAM token --&amp;gt;&lt;/div&gt;&lt;div&gt;Request hits EKS API (routes via public internet or private VPC depending on endpoint settings) --&amp;gt;&lt;/div&gt;&lt;div&gt;EKS checks token validity via IAM --&amp;gt;&lt;/div&gt;&lt;div&gt;Kubernetes RBAC verifies if your user profile has permission to view pods --&amp;gt;&lt;/div&gt;&lt;div&gt;Success&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/533126759264904169/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/533126759264904169' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/533126759264904169'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/533126759264904169'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/07/aws-eks-cluster-api.html' title='AWS EKS Cluster API'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEi8WwmUAx8p5XNQtaUg3CDEWtdFb70hoHgNBw3rxEbkiGpH-WPnvz0Ya09qE8B5a82wYui3oJ641ylIM-pXNUY6EV3C1uovGGLNe3oZ-LJQgWPS8HNi89nVLlTlCQRsniIwRaEsbxSAJq6rdmJap9mDX91dAYDb5UBicVZfrFk-hOrcwifBx8K36NVD3Nk=s72-w640-h640-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-6654749790428970543</id><published>2026-07-16T15:39:06.615+01:00</published><updated>2026-07-16T15:39:06.616+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Helm"/><category scheme="http://www.blogger.com/atom/ns#" term="Terraform"/><title type='text'>Helm and Infrastructure</title><content type='html'>&lt;div style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhswreDQalWQwAdR9epq7HyIr52bZLifZo6vl8wY5M4dmHqcR-DsfxZh-F_BWBP8wxVOBY7JJU1QCPCMvBZkQjmKhKNpd8N_FJxntB7Fk7PCOjyMjZc6bM5PCEtmtutUFHn-T_mbIIr3nsKEKkD7QNkLicLXqfMu4XIVFJJxZfgxVrIW_QfJNF5NZi9KRg/s512/helm-logo.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;512&quot; data-original-width=&quot;512&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhswreDQalWQwAdR9epq7HyIr52bZLifZo6vl8wY5M4dmHqcR-DsfxZh-F_BWBP8wxVOBY7JJU1QCPCMvBZkQjmKhKNpd8N_FJxntB7Fk7PCOjyMjZc6bM5PCEtmtutUFHn-T_mbIIr3nsKEKkD7QNkLicLXqfMu4XIVFJJxZfgxVrIW_QfJNF5NZi9KRg/w640-h640/helm-logo.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Does Helm provision aws infrastructure?&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;The short answer is no, Helm does not natively provision AWS cloud infrastructure.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Helm is a package manager designed specifically for Kubernetes. Its job is to template, package, and deploy Kubernetes-native YAML manifests (like Deployments, Services, ConfigMaps, and Ingresses) onto an already-running cluster. It does not natively understand or manage AWS resources like VPCs, EC2 instances, RDS databases, or S3 buckets.&amp;nbsp; However, because the cloud native ecosystem is highly integrated, the line can sometimes look blurry. Here is how that boundary gets crossed and how the two worlds interact.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &quot;Grey Area&quot;: When Helm Indirectly Triggers AWS Provisioning&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;While Helm itself only talks to the Kubernetes API, certain Kubernetes operators and controllers can watch for Helm-deployed resources and provision AWS infrastructure in response:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Cloud-Provider Load Balancers&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If your Helm chart deploys a Kubernetes Service of type: LoadBalancer:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Helm simply submits that Service YAML to the EKS control plane.&lt;/li&gt;&lt;li&gt;The cloud-controller-manager inside EKS sees this and automatically provisions an AWS Classic Load Balancer or Network Load Balancer (NLB) in your AWS account.&lt;/li&gt;&lt;li&gt;Similarly, deploying an Ingress resource alongside the AWS Load Balancer Controller (often installed via Helm itself) triggers the creation of an AWS Application Load Balancer (ALB).&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. Custom Resource Definitions (CRDs) &amp;amp; Operators&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can use Kubernetes operators that are explicitly designed to manage cloud infrastructure through Kubernetes. If you install these operators (often using Helm), you can then use Helm to deploy manifests that provision AWS resources:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;AWS Controllers for Kubernetes (ACK): This framework allows you to define AWS resources (like an S3 bucket or an RDS instance) directly as Kubernetes custom resources. If you bundle an ACK custom resource inside a Helm chart, running helm install will tell the ACK operator to provision that infrastructure in AWS.&lt;/li&gt;&lt;li&gt;Crossplane: Similar to ACK, Crossplane turns your cluster into a control plane. You can package Crossplane claims inside Helm charts to spin up databases, networks, and queues.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Standard Best Practice: &quot;Separation of Concerns&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because managing cloud infrastructure via Kubernetes manifests can get complex quickly, the industry-standard pattern is to keep these layers strictly separated:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;App Layer (Helm)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; - Deployments, Services, Ingress, ConfigMaps, etc.&amp;nbsp; &amp;nbsp; |&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└───────────────────────────┬────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ Runs inside&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌───────────────────────────▼────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Kubernetes Cluster (EKS)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└───────────────────────────┬────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ Provisioned by&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;┌───────────────────────────▼────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Infra Layer (IaC - Terraform)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;│&amp;nbsp; - VPC, Subnets, EKS Cluster, IAM, RDS, S3, etc.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;└────────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Infrastructure as Code (Terraform, OpenTofu, CloudFormation, Pulumi)&lt;/b&gt;: Used to provision the underlying AWS infrastructure—the VPC, subnets, IAM roles, EKS cluster, and managed node groups.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Helm&lt;/b&gt;: Used once the cluster is up and running to deploy and configure applications, ingress controllers, logging agents, and service meshes on top of that infrastructure.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;Note: If you want a single tool to bridge the gap, Terraform actually has a Helm Provider. This allows you to use Terraform to provision your EKS cluster first, and then immediately call Helm charts within the same Terraform code to bootstrap your cluster applications.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How to use Terraform to install a Helm chart?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;To bridge the gap between infrastructure and application deployment, you can use the HashiCorp Helm Provider directly inside your Terraform configuration.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This approach ensures strict ordering: Terraform first provisions the AWS network and EKS cluster, and only after the cluster is fully active does it initialize Helm to install your charts.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here is a clean, production-ready example showing how to connect your AWS provider, configure the Kubernetes/Helm providers dynamically using the cluster&#39;s output token, and deploy a chart.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 1. PROVIDERS CONFIGURATION&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# We configure Kubernetes and Helm providers dynamically using tokens&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# generated from the newly created AWS EKS cluster.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;provider &quot;aws&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; region = &quot;eu-west-2&quot; # London&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;provider &quot;kubernetes&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; host&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= aws_eks_cluster.main.endpoint&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; token&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = data.aws_eks_cluster_auth.main.token&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;provider &quot;helm&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; kubernetes {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; host&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= aws_eks_cluster.main.endpoint&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; token&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = data.aws_eks_cluster_auth.main.token&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# Data source to fetch authentication tokens for EKS dynamically&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;data &quot;aws_eks_cluster_auth&quot; &quot;main&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name = aws_eks_cluster.main.name&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 2. AWS EKS CLUSTER PROVISIONING (Simplified)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;aws_eks_cluster&quot; &quot;main&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name&amp;nbsp; &amp;nbsp; &amp;nbsp;= &quot;production-core-cluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; role_arn = aws_iam_role.eks_cluster.arn&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; vpc_config {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; subnet_ids = [&quot;subnet-12345678&quot;, &quot;subnet-87654321&quot;] # Replace with your VPC subnet IDs&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# 3. HELM RELEASE PROVISIONING&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;# Terraform treats this Helm chart as a resource managed within its state file.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;helm_release&quot; &quot;ingress_nginx&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= &quot;ingress-nginx&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; repository&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= &quot;https://kubernetes.github.io/ingress-nginx&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; chart&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;ingress-nginx&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; version&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;4.10.0&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; namespace&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;ingress-system&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; create_namespace = true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; # Explicit dependency guarantees the cluster exists before Helm tries to talk to it&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; depends_on = [aws_eks_cluster.main]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; # Passing configuration values into the Helm chart&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; set {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name&amp;nbsp; = &quot;controller.replicaCount&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; value = &quot;3&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; set {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name&amp;nbsp; = &quot;controller.service.annotations.service\\.beta\\.kubernetes\\.io/aws-load-balancer-type&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; value = &quot;nlb&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why this pattern works so well:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Token-Based Auth Lifecycle: By using the aws_eks_cluster_auth data source, Terraform fetches a short-lived administrative token on the fly during the apply phase, ensuring Helm can authenticate without storing static kubeconfig files on disk or in version control.&lt;/li&gt;&lt;li&gt;Escaping Helm Keys: Note the double backslash (\\) in the annotation key. Helm uses dot notation to traverse nested values, so if an AWS annotation key contains a dot (like aws-load-balancer-type), you must escape it in Terraform so Helm interprets it as a single key string rather than a nested map.&lt;/li&gt;&lt;li&gt;State Management: When you run terraform destroy, Terraform is smart enough to reverse the order: it calls Helm to cleanly uninstall the charts, deletes any cloud-provider resources (like NLBs) created by those charts, and then tears down the EKS cluster.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/6654749790428970543/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/6654749790428970543' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6654749790428970543'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/6654749790428970543'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/07/helm-and-infrastructure.html' title='Helm and Infrastructure'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhswreDQalWQwAdR9epq7HyIr52bZLifZo6vl8wY5M4dmHqcR-DsfxZh-F_BWBP8wxVOBY7JJU1QCPCMvBZkQjmKhKNpd8N_FJxntB7Fk7PCOjyMjZc6bM5PCEtmtutUFHn-T_mbIIr3nsKEKkD7QNkLicLXqfMu4XIVFJJxZfgxVrIW_QfJNF5NZi9KRg/s72-w640-h640-c/helm-logo.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-1363528114113100932</id><published>2026-07-02T11:48:23.892+01:00</published><updated>2026-07-02T11:48:23.892+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Ingress"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Load Balancing"/><title type='text'>Mitigating Load Balancer Routing Clamping</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSJSM-iOPqVmPdDzFbrSufE6r0RkCQc9H_Ikp-NWSKPyua4Yfnj--YE6bWACpd05qpEn4HeACR8OZZwQfuis529r4oKLZk-WSCTleHcEexFaskQedIAbeW-vOuWb50lGGx97E_vRvltlbN1FnN918TC86Ir5GY7dNGoAqAH_xEpbbvPBZtgR_FILSS/s310/alb_routing_1.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;262&quot; data-original-width=&quot;310&quot; height=&quot;541&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSJSM-iOPqVmPdDzFbrSufE6r0RkCQc9H_Ikp-NWSKPyua4Yfnj--YE6bWACpd05qpEn4HeACR8OZZwQfuis529r4oKLZk-WSCTleHcEexFaskQedIAbeW-vOuWb50lGGx97E_vRvltlbN1FnN918TC86Ir5GY7dNGoAqAH_xEpbbvPBZtgR_FILSS/w640-h541/alb_routing_1.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;In computing and networking, routing clumping (also known as traffic clumping or clustering) refers to the phenomenon where a load balancer or router unevenly sends bursts of traffic to the same backend servers instead of distributing requests evenly across the entire available server pool.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;While a load balancer is designed to act as a traffic cop, routing clumping defeats this purpose, momentarily overloading specific servers while leaving others idle.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Primary Causes of Routing Clumping&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Hashing Bias&lt;/b&gt;: Algorithms like 5-tuple hashing (which use source IP, destination IP, ports, and protocols) calculate a fixed value to select a routing path. If thousands of users share a proxy or exit node, the load balancer may calculate the same hash and clump all their traffic onto a single backend server.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Sticky Sessions&lt;/b&gt;: If you use session persistence (sticky sessions) based on a client&#39;s IP, multiple requests from the same office or network range will clump to a single server to maintain session continuity.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Connection Multiplexing&lt;/b&gt;: Load balancers maintain long-lived backend connections to save resources. When a new batch of requests comes in, the load balancer routes them through existing open connections, causing requests to cluster on specific nodes rather than opening new connections to idle servers.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Inconsistent Pod or Instance Count&lt;/b&gt;: In environments like Kubernetes, if traffic is processed node-by-node, unequal distribution of application pods across nodes or restrictive node affinities can cause traffic to clump in specific zones or servers.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How to Prevent and Mitigate Clumping&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Use Round Robin or Least Connections&lt;/b&gt;: Switch from IP-hashing or fixed-hash methods to AWS Least Connections or round-robin algorithms, which evaluate server availability in real-time rather than relying on mathematical routing properties.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Enable Cross-Zone Load Balancing&lt;/b&gt;: For cloud architectures (like AWS Elastic Load Balancing), enabling cross-zone load balancing forces your load balancers to distribute traffic evenly across all registered targets in all enabled Availability Zones.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Use Layer 7 Intelligent Routing&lt;/b&gt;: Instead of Layer 4 packet forwarding, use a Layer 7 Application Load Balancer to inspect HTTP headers and distribute traffic based on request context, allowing for finer-grained distribution.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Deploy Hierarchical Splitting&lt;/b&gt;: For massive, hyperscale systems, implementing a hierarchical L4 + L7 structure (ultra-fast L4 hardware balancers routing to a software-based L7 pool) helps prevent clumping while maintaining high raw throughput.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Why round robin routing didn&#39;t work well in this scenario so we need to use least connections routing?&amp;nbsp;&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;Good question — the answer is about the kind of workload service3 is. Round robin isn&#39;t broken in general; it&#39;s the wrong fit for this specific traffic shape.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; What each algorithm does&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Round robin&lt;/b&gt;: hands each new request to the next target in rotation (pod A, B, C, A, B, C…), purely by count — blind to how busy each target currently is.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Least outstanding requests (LOR)&lt;/b&gt;: sends each new request to the target with the fewest in-flight requests right now — i.e., the least-busy one.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why round robin fails here&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Round robin distributes by count, which only equals distributing by load when requests are uniform and short. service3 is the opposite — it has the toxic combination:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Highly variable, long service times — a spaCy extraction is anywhere from ~0.3s to multi-second depending on text length/entity count. One heavy request ties a pod up for seconds.&lt;/div&gt;&lt;div&gt;2. Low per-pod concurrency — each pod does ~1–2 concurrent extractions (2 workers, GIL-bound). It saturates almost immediately.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Under those conditions, round robin keeps handing new requests to a pod that&#39;s still grinding a heavy multi-second extraction, just because it&#39;s &quot;next in rotation&quot; — while a pod that just finished sits idle. The new request then queues head-of-line behind the heavy one for seconds, even though free capacity exists elsewhere. Over a burst this compounds: unlucky pods accumulate a backlog while others drain and idle → exactly the clumping / 33%-CPU-but-3s-latency pattern we measured.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The analogy: round robin is a supermarket sending shoppers to checkout lanes strictly 1‑2‑3‑1‑2‑3, ignoring that lane 2 has someone with a giant cart. You get stuck behind the big cart while lane 3 is empty. LOR is &quot;go to the lane with the fewest items in it right now.&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Why LOR fixes it&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;LOR tracks in-flight requests per target and skips the busy pod — a pod mid-heavy-extraction has a high outstanding count, so new requests go to the idle/draining pods instead. It self-corrects for variance (whichever pod is busy, for any reason, automatically gets fewer new requests until it catches up) and it reaches the idle capacity round robin was stranding.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The catch — why we also needed IP mode&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;LOR only helps if the ALB is choosing among the right units. In instance mode, the ALB&#39;s targets are nodes, so it&#39;d balance outstanding requests across nodes — then kube-proxy randomly picks a pod within the node, re-scrambling the per-pod balance. LOR literally can&#39;t see per-pod busyness in instance mode.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;IP mode makes the targets pods, so LOR&#39;s &quot;least outstanding&quot; is measured per pod → it can actually steer to the least-busy pod. That&#39;s why the fix is ip mode + LOR together: ip mode gives LOR the right granularity; LOR gives ip mode the right decision rule. Either alone wouldn&#39;t do it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Two footnotes&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;- The other 5 services keep round robin — deliberately. auth/data/game/webhook are fast (&amp;lt;0.5s), uniform, high-concurrency — round robin distributes them fine, and there&#39;s no reason to touch them. The algorithm choice is workload-specific: LOR for&lt;/div&gt;&lt;div&gt;&amp;nbsp; slow/variable/expensive backends, round robin for fast/uniform ones.&lt;/div&gt;&lt;div&gt;- Terminology: you said &quot;least connections&quot; — the ALB (L7) equivalent is least outstanding requests, which counts in-flight HTTP requests, not TCP connections. That distinction matters here: the ALB reuses keep-alive connections (one connection carries many sequential requests), so &quot;least connections&quot; would be misleading — LOR counts the actual requests, which is the right signal for this.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;In which cases round-robin doesn&#39;t help so we need to use least outstanding requests routing?&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;While Round-Robin works perfectly for uniform traffic and identical servers, it fails when the system experiences variations in request complexity or server capacity. You must switch from Round-Robin to Least Outstanding Requests (LOR) (also known as Least Connections) in the following four specific engineering scenarios:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Varying Request Processing Times (Asymmetric Workloads)&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Problem: In many applications, some API calls take 5 milliseconds (e.g., fetching a profile cached in memory), while others take 5 seconds (e.g., generating a heavy PDF report or running a complex database query).Why Round-Robin Fails: Round-Robin blindly hands out requests in a strict, alternating sequence. If Server A randomly receives a consecutive string of heavy PDF requests while Server B receives fast cache requests, Server A&#39;s queue will spike, causing high latency or timeouts, while Server B sits mostly idle.&lt;/li&gt;&lt;li&gt;How LOR Helps: LOR actively tracks the active connection count. It will notice Server A is backed up with pending work and will divert all new incoming traffic to Server B until Server A finishes its heavy processing.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Heterogeneous Server Capacities (Mixed Server Sizes)&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Problem: Production clusters often use mixed hardware. For example, during an auto-scaling event, you might temporarily mix older 4-core virtual machines with newer, high-performance 16-core instances.Why Round-Robin Fails: Round-Robin treats every backend target as equal. It sends exactly 1,000 requests to the weak 4-core machine and exactly 1,000 requests to the powerful 16-core machine. The weaker machine will quickly choke, run out of memory, or drop packets, while the stronger machine remains underutilised.&lt;/li&gt;&lt;li&gt;How LOR Helps: The faster, more powerful server processes and closes connections much quicker than the weaker server. Because its outstanding connection count drops rapidly, LOR naturally funnels a significantly higher volume of traffic to the stronger hardware without needing manual weight configurations.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Persistent and Long-Lived Connections (WebSocket &amp;amp; gRPC)&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Problem: Modern applications rely heavily on persistent connections like WebSockets, HTTP/2 multiplexing, server-sent events (SSE), or gRPC streams. These connections stay open for minutes or hours.Why Round-Robin Fails: Round-Robin only counts the initial connection establishment. If Server A hosts 50 clients who stay connected for 3 hours chatting, and Server B hosts 50 clients who disconnect after 30 seconds, Round-Robin will continue to feed new connections to both servers equally. Server A will slowly crush under the cumulative weight of long-lived active sessions.&lt;/li&gt;&lt;li&gt;How LOR Helps: LOR continuously monitors active, open connections rather than connection arrival rates. It will see that Server A has 50 active outstanding connections and Server B has 0, immediately routing all new clients to Server B.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Unpredictable Backend &quot;Cold Starts&quot; and Drifts&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The Problem: When a new application container or server boots up (e.g., during a Kubernetes deployment or AWS auto-scaling event), it often suffers from a &quot;cold start&quot; where it runs slowly while warming up caches or compiling code just-in-time (JIT).Why Round-Robin Fails: Round-Robin immediately floods the newly booted server with its full share of production traffic. Because the server is not yet performing at 100% capacity, this sudden burst easily overwhelms it, causing it to crash immediately after launching.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;How LOR Helps: LOR naturally throttles traffic to the warming server. Since the cold server processes its first few requests slowly, its outstanding request count will naturally rise, signaling the load balancer to back off and route traffic elsewhere until the server catches up.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Algorithm Comparison Table&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Scenario&lt;/li&gt;&lt;li&gt;Round-Robin Behavior&lt;/li&gt;&lt;li&gt;Least Outstanding Requests Behavior&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: initial; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;All requests take equal time&amp;nbsp;&lt;/li&gt;&lt;li&gt;Perfect, completely equal distribution.&lt;/li&gt;&lt;li&gt;Excellent, identical result to Round-Robin.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: initial; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Mix of short &amp;amp; long requests&lt;/li&gt;&lt;li&gt;Overloads random servers (creates traffic clumps).&lt;/li&gt;&lt;li&gt;Dynamically balances the processing queue.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: initial; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Mixed server capacities&lt;/li&gt;&lt;li&gt;Overwhelms smaller, weaker backend servers.&lt;/li&gt;&lt;li&gt;Directs more traffic to faster instances automatically.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;border-color: currentcolor; border-image: initial; border-style: none; border-width: medium; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;&quot;&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Long-lived WebSockets / gRPC&lt;/li&gt;&lt;li&gt;Ignores session duration; causes massive load imbalance.&lt;/li&gt;&lt;li&gt;Tracks real-time active sessions perfectly.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;If you are using a cloud platform, you can learn more about implementing this via the AWS Least Outstanding Requests Documentation or the NGINX Least Connections Guide.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;k8s ingress - AWS ALB&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To implement Least Outstanding Requests (LOR) routing with an AWS Application Load Balancer (ALB) inside a Kubernetes cluster, you must configure it using the AWS Load Balancer Controller.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;By default, the AWS ALB uses Round-Robin routing. You can change this behavior by applying a specific routing annotation to your Kubernetes TargetGroupBinding or Ingress resource.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Ingress Configuration Example&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Apply the alb.ingress.kubernetes.io/target-group-attributes annotation to your Ingress manifest. This tells the AWS controller to configure the underlying target groups to use the load_balancing.algorithm.type=least_outstanding_requests setting.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;apiVersion: networking.k8s.io/v1&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kind: Ingress&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name: my-app-ingress&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; namespace: production&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; annotations:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; # Essential ALB Controller configurations&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; kubernetes.io/ingress.class: alb&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; alb.ingress.kubernetes.io/scheme: internet-facing&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; alb.ingress.kubernetes.io/target-type: &lt;b&gt;ip&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; # Enable Least Outstanding Requests Routing&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; alb.ingress.kubernetes.io/target-group-attributes: load_balancing.algorithm.type=least_outstanding_requests&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;spec:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; rules:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; - http:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; paths:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; - path: /&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; pathType: Prefix&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; backend:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; service:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; name: my-app-service&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; port:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; number: 80&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Crucial Step: Use &quot;IP&quot; Target Type&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When running AWS ALB in Kubernetes, you should always set &lt;span style=&quot;font-family: courier;&quot;&gt;alb.ingress.kubernetes.io/target-type: ip&lt;/span&gt; instead of &lt;span style=&quot;font-family: courier;&quot;&gt;instance&lt;/span&gt; mode.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Why? In &lt;u&gt;&lt;b&gt;instance&lt;/b&gt; mode&lt;/u&gt;, the ALB routes traffic to NodePorts on the EC2 worker nodes. The node then uses kube-proxy (iptables or IPVS) to randomly route the packet to a pod. This completely breaks the math behind Least Outstanding Requests because the &lt;u&gt;ALB can only see outstanding connections to the node, not the actual pods&lt;/u&gt;.&lt;/li&gt;&lt;li&gt;The Fix: Using &lt;u&gt;&lt;b&gt;ip&lt;/b&gt; mode&lt;/u&gt; configures the ALB to &lt;u&gt;bypass the node network entirely and route traffic directly to the Pod IPs&lt;/u&gt;. This gives the ALB true visibility into the exact outstanding request count for each application container.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Verify the Configuration&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;After applying the manifest via &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl apply -f ingress.yaml&lt;/span&gt;, you can verify the changes are active in AWS:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Open the AWS EC2 Console and navigate to Target Groups.&lt;/li&gt;&lt;li&gt;Select the target group automatically generated by your Kubernetes Ingress.&lt;/li&gt;&lt;li&gt;Click on the Attributes tab.&lt;/li&gt;&lt;li&gt;Verify that Routing algorithm is set to Least outstanding requests.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Alternatively, check the AWS Load Balancer Controller logs to ensure the modification was successfully reconciled:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;kubectl logs -n kube-system deployment/aws-load-balancer-controller&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;---&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/1363528114113100932/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/1363528114113100932' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/1363528114113100932'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/1363528114113100932'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/07/mitigating-load-balancer-routing.html' title='Mitigating Load Balancer Routing Clamping'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSJSM-iOPqVmPdDzFbrSufE6r0RkCQc9H_Ikp-NWSKPyua4Yfnj--YE6bWACpd05qpEn4HeACR8OZZwQfuis529r4oKLZk-WSCTleHcEexFaskQedIAbeW-vOuWb50lGGx97E_vRvltlbN1FnN918TC86Ir5GY7dNGoAqAH_xEpbbvPBZtgR_FILSS/s72-w640-h541-c/alb_routing_1.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-7856347956029631307</id><published>2026-07-01T14:51:07.912+01:00</published><updated>2026-07-21T16:29:46.786+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS EKS"/><category scheme="http://www.blogger.com/atom/ns#" term="Backup"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Disaster Recovery"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Restore"/><category scheme="http://www.blogger.com/atom/ns#" term="Velero"/><title type='text'>Introduction to Velero</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;table align=&quot;center&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; class=&quot;tr-caption-container&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifUb1zEVjjUbTia_rDZU90OxNIRkTiGvUK4qjY0Ba3IDMFQlCketK3DyF2r-w1lYQchW3lehaRSbtTUjLKPu3mLDoKbkUeTiMTswIBFb2TPC-Wp5U8Rqs1ZosjVk-3AlGnlCVg-GSFp4mXYgz-5-qnNus8J9PHecYBQ6b9n9d0mj9xFkqaTA5_OBNovH8/s500/velero.jpg&quot; style=&quot;margin-left: auto; margin-right: auto;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;500&quot; data-original-width=&quot;500&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifUb1zEVjjUbTia_rDZU90OxNIRkTiGvUK4qjY0Ba3IDMFQlCketK3DyF2r-w1lYQchW3lehaRSbtTUjLKPu3mLDoKbkUeTiMTswIBFb2TPC-Wp5U8Rqs1ZosjVk-3AlGnlCVg-GSFp4mXYgz-5-qnNus8J9PHecYBQ6b9n9d0mj9xFkqaTA5_OBNovH8/w640-h640/velero.jpg&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class=&quot;tr-caption&quot; style=&quot;text-align: center;&quot;&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;&lt;p class=&quot;PDq2pG_selectionAnchorContainer&quot; data-end=&quot;240&quot; data-start=&quot;0&quot;&gt;&lt;strong data-end=&quot;10&quot; data-start=&quot;0&quot;&gt;Velero&lt;/strong&gt; is an open-source disaster recovery, Kubernetes-native backup, restore and migration tool for Kubernetes. It allows you to back up and restore both your Kubernetes cluster resources and, optionally, the persistent volumes (PVs) that hold application data.&lt;span aria-hidden=&quot;true&quot; class=&quot;PDq2pG_selectionAnchor&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-end=&quot;424&quot; data-start=&quot;242&quot;&gt;It was originally created by Heptio (the company founded by two of Kubernetes&#39; creators) and is now maintained by &lt;span class=&quot;hover:entity-accent entity-underline inline cursor-pointer align-baseline&quot;&gt;VMware&lt;/span&gt; and the open-source community.&lt;/p&gt;
&lt;h2 data-end=&quot;454&quot; data-section-id=&quot;2dpwn8&quot; data-start=&quot;426&quot;&gt;What does Velero back up?&lt;/h2&gt;
&lt;p data-end=&quot;475&quot; data-start=&quot;456&quot;&gt;Velero can back up:&lt;/p&gt;
&lt;ul data-end=&quot;651&quot; data-start=&quot;477&quot;&gt;
&lt;li data-end=&quot;651&quot; data-section-id=&quot;1oa9b42&quot; data-start=&quot;477&quot;&gt;
Kubernetes resources (Cluster API objects):
&lt;/li&gt;&lt;ul data-end=&quot;651&quot; data-start=&quot;503&quot;&gt;
&lt;li data-end=&quot;516&quot; data-section-id=&quot;1hf8ewy&quot; data-start=&quot;503&quot;&gt;
Deployments
&lt;/li&gt;
&lt;li data-end=&quot;533&quot; data-section-id=&quot;7m7bf5&quot; data-start=&quot;519&quot;&gt;
StatefulSets
&lt;/li&gt;
&lt;li data-end=&quot;546&quot; data-section-id=&quot;d0wa06&quot; data-start=&quot;536&quot;&gt;
Services
&lt;/li&gt;
&lt;li data-end=&quot;560&quot; data-section-id=&quot;11e6okp&quot; data-start=&quot;549&quot;&gt;
Ingresses
&lt;/li&gt;
&lt;li data-end=&quot;575&quot; data-section-id=&quot;rqpcjx&quot; data-start=&quot;563&quot;&gt;
ConfigMaps
&lt;/li&gt;
&lt;li data-end=&quot;587&quot; data-section-id=&quot;1veds5p&quot; data-start=&quot;578&quot;&gt;
Secrets
&lt;/li&gt;
&lt;li data-end=&quot;596&quot; data-section-id=&quot;1j40y26&quot; data-start=&quot;590&quot;&gt;
CRDs
&lt;/li&gt;
&lt;li data-end=&quot;611&quot; data-section-id=&quot;1wqge8o&quot; data-start=&quot;599&quot;&gt;
Namespaces
&lt;/li&gt;
&lt;li data-end=&quot;630&quot; data-section-id=&quot;13zx89v&quot; data-start=&quot;614&quot;&gt;
RBAC resources
&lt;/li&gt;
&lt;li data-end=&quot;651&quot; data-section-id=&quot;1t3gyua&quot; data-start=&quot;633&quot;&gt;
Custom Resources
&lt;/li&gt;
&lt;/ul&gt;

&lt;/ul&gt;&lt;div&gt;They are&amp;nbsp;stored as tarballs in an S3 bucket.&lt;/div&gt;
&lt;p data-end=&quot;685&quot; data-start=&quot;653&quot;&gt;Optionally, it can also back up:&lt;/p&gt;
&lt;ul data-end=&quot;877&quot; data-start=&quot;687&quot; style=&quot;text-align: left;&quot;&gt;
&lt;li data-end=&quot;877&quot; data-section-id=&quot;4wcrjl&quot; data-start=&quot;687&quot;&gt;
Persistent Volumes (application data)
&lt;ul data-end=&quot;877&quot; data-start=&quot;729&quot;&gt;
&lt;li data-end=&quot;801&quot; data-section-id=&quot;1m7pubc&quot; data-start=&quot;729&quot;&gt;
via storage snapshots (AWS EBS, Azure Disk, GCP Persistent Disk, etc.)&lt;/li&gt;&lt;ul&gt;&lt;li data-end=&quot;801&quot; data-section-id=&quot;1m7pubc&quot; data-start=&quot;729&quot;&gt;cloud snapshots (EBS snapshots through the CSI driver)&lt;/li&gt;&lt;/ul&gt;
&lt;li data-end=&quot;877&quot; data-section-id=&quot;1330v6c&quot; data-start=&quot;804&quot;&gt;
or via a file-level backup tool called &lt;strong data-end=&quot;859&quot; data-start=&quot;845&quot;&gt;Node Agent&lt;/strong&gt; (formerly Restic)
&lt;/li&gt;&lt;ul&gt;&lt;li data-end=&quot;877&quot; data-section-id=&quot;1330v6c&quot; data-start=&quot;804&quot;&gt;file-level backup with the built-in Kopia/Restic uploader for non-snapshottable volumes (EFS, hostPath, etc.)&lt;/li&gt;&lt;/ul&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;882&quot; data-start=&quot;879&quot; /&gt;
&lt;h2 data-end=&quot;899&quot; data-section-id=&quot;nysmmy&quot; data-start=&quot;884&quot;&gt;How it works&lt;/h2&gt;
&lt;p data-end=&quot;941&quot; data-start=&quot;901&quot;&gt;A typical Velero deployment consists of:&lt;/p&gt;&lt;p data-end=&quot;941&quot; data-start=&quot;901&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;1600&quot; data-start=&quot;943&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;pe-11 pt-3&quot;&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;                    +----------------+&lt;br /&gt;                    | Kubernetes API |&lt;br /&gt;                    +--------+-------+&lt;br /&gt;                             |&lt;br /&gt;                        Velero Server&lt;br /&gt;                             |&lt;br /&gt;         +-------------------+-------------------+&lt;br /&gt;         |                                       |&lt;br /&gt; Metadata Backup                        Volume Backup&lt;br /&gt;         |                                       |&lt;br /&gt;         v                                       v&lt;br /&gt;   Object Storage                    Snapshot or File Backup&lt;br /&gt;   (S3, Azure Blob,                  (EBS, CSI Snapshot,&lt;br /&gt;    GCS, MinIO...)                    Node Agent/Restic)&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;1614&quot; data-start=&quot;1602&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p data-end=&quot;1614&quot; data-start=&quot;1602&quot;&gt;For example:&lt;/p&gt;
&lt;ul data-end=&quot;1725&quot; data-start=&quot;1616&quot;&gt;
&lt;li data-end=&quot;1659&quot; data-section-id=&quot;1h4iq68&quot; data-start=&quot;1616&quot;&gt;
Cluster metadata → stored in an S3 bucket
&lt;/li&gt;
&lt;li data-end=&quot;1725&quot; data-section-id=&quot;1hb4hqe&quot; data-start=&quot;1660&quot;&gt;
PV data → stored as EBS snapshots or uploaded to object storage
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1730&quot; data-start=&quot;1727&quot; /&gt;
&lt;h2 data-end=&quot;1752&quot; data-section-id=&quot;i8yokb&quot; data-start=&quot;1732&quot;&gt;Typical use cases&lt;/h2&gt;
&lt;h3 data-end=&quot;1778&quot; data-section-id=&quot;zdkkdx&quot; data-start=&quot;1754&quot;&gt;1. Disaster recovery&lt;/h3&gt;
&lt;p data-end=&quot;1821&quot; data-start=&quot;1780&quot;&gt;Your EKS cluster is accidentally deleted.&lt;/p&gt;
&lt;p data-end=&quot;1843&quot; data-start=&quot;1823&quot;&gt;With Velero you can:&lt;/p&gt;
&lt;ul data-end=&quot;1934&quot; data-start=&quot;1845&quot;&gt;
&lt;li data-end=&quot;1867&quot; data-section-id=&quot;1mvbmqw&quot; data-start=&quot;1845&quot;&gt;
recreate the cluster
&lt;/li&gt;
&lt;li data-end=&quot;1884&quot; data-section-id=&quot;o1tqsu&quot; data-start=&quot;1868&quot;&gt;
install Velero
&lt;/li&gt;
&lt;li data-end=&quot;1908&quot; data-section-id=&quot;1ywohhx&quot; data-start=&quot;1885&quot;&gt;
restore all workloads
&lt;/li&gt;
&lt;li data-end=&quot;1934&quot; data-section-id=&quot;f414z9&quot; data-start=&quot;1909&quot;&gt;
restore persistent data
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1939&quot; data-start=&quot;1936&quot; /&gt;
&lt;h3 data-end=&quot;1967&quot; data-section-id=&quot;1ehg9rk&quot; data-start=&quot;1941&quot;&gt;2. Accidental deletion&lt;/h3&gt;
&lt;p data-end=&quot;1982&quot; data-start=&quot;1969&quot;&gt;Someone runs:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;2031&quot; data-start=&quot;1984&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;kubectl delete namespace production&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2075&quot; data-start=&quot;2033&quot;&gt;Instead of recreating everything manually:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;2146&quot; data-start=&quot;2077&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;velero restore create \&lt;br /&gt;  &lt;span class=&quot;ͼ12&quot;&gt;--from-backup&lt;/span&gt; production-backup&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;2151&quot; data-start=&quot;2148&quot; /&gt;
&lt;h3 data-end=&quot;2177&quot; data-section-id=&quot;g0xa3h&quot; data-start=&quot;2153&quot;&gt;3. Cluster migration&lt;/h3&gt;
&lt;p data-end=&quot;2199&quot; data-start=&quot;2179&quot;&gt;Move workloads from:&lt;/p&gt;
&lt;ul data-end=&quot;2254&quot; data-start=&quot;2201&quot;&gt;
&lt;li data-end=&quot;2212&quot; data-section-id=&quot;t3kigq&quot; data-start=&quot;2201&quot;&gt;
EKS → EKS
&lt;/li&gt;
&lt;li data-end=&quot;2224&quot; data-section-id=&quot;t3kltq&quot; data-start=&quot;2213&quot;&gt;
EKS → AKS
&lt;/li&gt;
&lt;li data-end=&quot;2236&quot; data-section-id=&quot;t3kk4u&quot; data-start=&quot;2225&quot;&gt;
EKS → GKE
&lt;/li&gt;
&lt;li data-end=&quot;2254&quot; data-section-id=&quot;7ma6ml&quot; data-start=&quot;2237&quot;&gt;
On-prem → cloud
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2312&quot; data-start=&quot;2256&quot;&gt;Velero restores Kubernetes objects into the new cluster.&lt;/p&gt;
&lt;hr data-end=&quot;2317&quot; data-start=&quot;2314&quot; /&gt;
&lt;h3 data-end=&quot;2343&quot; data-section-id=&quot;pviops&quot; data-start=&quot;2319&quot;&gt;4. Scheduled backups&lt;/h3&gt;
&lt;p data-end=&quot;2353&quot; data-start=&quot;2345&quot;&gt;Example:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;2441&quot; data-start=&quot;2355&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;pe-11 pt-3&quot;&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;Every night at 2 AM&lt;br /&gt;&lt;br /&gt;↓&lt;br /&gt;&lt;br /&gt;Backup namespaces:&lt;br /&gt;- production&lt;br /&gt;- monitoring&lt;br /&gt;- logging&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;2484&quot; data-start=&quot;2443&quot;&gt;Retention can be configured, for example:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;2547&quot; data-start=&quot;2486&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;pe-11 pt-3&quot;&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;Keep 30 daily backups&lt;br /&gt;Delete older ones automatically&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;2552&quot; data-start=&quot;2549&quot; /&gt;
&lt;h2 data-end=&quot;2581&quot; data-section-id=&quot;4ufys&quot; data-start=&quot;2554&quot;&gt;What it does NOT back up&lt;/h2&gt;
&lt;p data-end=&quot;2625&quot; data-start=&quot;2583&quot;&gt;Velero does &lt;strong data-end=&quot;2602&quot; data-start=&quot;2595&quot;&gt;not&lt;/strong&gt; automatically back up:&lt;/p&gt;
&lt;ul data-end=&quot;2809&quot; data-start=&quot;2627&quot;&gt;
&lt;li data-end=&quot;2642&quot; data-section-id=&quot;1s3u7hi&quot; data-start=&quot;2627&quot;&gt;
etcd directly
&lt;/li&gt;
&lt;li data-end=&quot;2710&quot; data-section-id=&quot;ivusij&quot; data-start=&quot;2643&quot;&gt;
cloud infrastructure (VPCs, Load Balancers, IAM, Security Groups)
&lt;/li&gt;
&lt;li data-end=&quot;2739&quot; data-section-id=&quot;1x84pit&quot; data-start=&quot;2711&quot;&gt;
managed databases like RDS
&lt;/li&gt;
&lt;li data-end=&quot;2789&quot; data-section-id=&quot;1l7hzt1&quot; data-start=&quot;2740&quot;&gt;
container images (they remain in your registry)
&lt;/li&gt;
&lt;li data-end=&quot;2809&quot; data-section-id=&quot;199aaxn&quot; data-start=&quot;2790&quot;&gt;
external services
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2852&quot; data-start=&quot;2811&quot;&gt;Those require separate backup strategies.&lt;/p&gt;
&lt;hr data-end=&quot;2857&quot; data-start=&quot;2854&quot; /&gt;
&lt;h2 data-end=&quot;2879&quot; data-section-id=&quot;9qwwry&quot; data-start=&quot;2859&quot;&gt;Storage providers&lt;/h2&gt;
&lt;p data-end=&quot;2926&quot; data-start=&quot;2881&quot;&gt;Velero supports many object storage backends:&lt;/p&gt;
&lt;ul data-end=&quot;3041&quot; data-start=&quot;2928&quot;&gt;
&lt;li data-end=&quot;2939&quot; data-section-id=&quot;1nvslzy&quot; data-start=&quot;2928&quot;&gt;
Amazon S3
&lt;/li&gt;
&lt;li data-end=&quot;2947&quot; data-section-id=&quot;16vz12c&quot; data-start=&quot;2940&quot;&gt;
MinIO
&lt;/li&gt;
&lt;li data-end=&quot;2968&quot; data-section-id=&quot;1blh8vf&quot; data-start=&quot;2948&quot;&gt;
Azure Blob Storage
&lt;/li&gt;
&lt;li data-end=&quot;2991&quot; data-section-id=&quot;1vne895&quot; data-start=&quot;2969&quot;&gt;
Google Cloud Storage
&lt;/li&gt;
&lt;li data-end=&quot;3012&quot; data-section-id=&quot;l0wt41&quot; data-start=&quot;2992&quot;&gt;
OCI Object Storage
&lt;/li&gt;
&lt;li data-end=&quot;3041&quot; data-section-id=&quot;1cl4g3y&quot; data-start=&quot;3013&quot;&gt;
many S3-compatible systems
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;3046&quot; data-start=&quot;3043&quot; /&gt;
&lt;h2 data-end=&quot;3083&quot; data-section-id=&quot;1el6d3e&quot; data-start=&quot;3048&quot;&gt;Persistent Volume backup methods&lt;/h2&gt;
&lt;p data-end=&quot;3115&quot; data-start=&quot;3085&quot;&gt;There are two main approaches.&lt;/p&gt;
&lt;h3 data-end=&quot;3149&quot; data-section-id=&quot;11ajltn&quot; data-start=&quot;3117&quot;&gt;1. CSI snapshots (preferred)&lt;/h3&gt;
&lt;p data-end=&quot;3237&quot; data-start=&quot;3151&quot;&gt;If your storage class supports the &lt;span class=&quot;hover:entity-accent entity-underline inline cursor-pointer align-baseline&quot;&gt;&lt;b&gt;Container Storage Interface (CSI)&lt;/b&gt;&lt;/span&gt; snapshot API:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;3286&quot; data-start=&quot;3239&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;pe-11 pt-3&quot;&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;PVC&lt;br /&gt; ↓&lt;br /&gt;VolumeSnapshot&lt;br /&gt; ↓&lt;br /&gt;Cloud snapshot&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3299&quot; data-start=&quot;3288&quot;&gt;Advantages:&lt;/p&gt;
&lt;ul data-end=&quot;3386&quot; data-start=&quot;3301&quot;&gt;
&lt;li data-end=&quot;3312&quot; data-section-id=&quot;vocx4g&quot; data-start=&quot;3301&quot;&gt;
very fast
&lt;/li&gt;
&lt;li data-end=&quot;3357&quot; data-section-id=&quot;1hwu887&quot; data-start=&quot;3313&quot;&gt;
incremental (depending on storage backend)
&lt;/li&gt;
&lt;li data-end=&quot;3372&quot; data-section-id=&quot;8hrud1&quot; data-start=&quot;3358&quot;&gt;
cloud-native
&lt;/li&gt;
&lt;li data-end=&quot;3386&quot; data-section-id=&quot;dk7i71&quot; data-start=&quot;3373&quot;&gt;
recommended&lt;/li&gt;&lt;/ul&gt;
&lt;h3 data-end=&quot;3428&quot; data-section-id=&quot;lcn6j0&quot; data-start=&quot;3393&quot;&gt;2. Node Agent (formerly Restic)&lt;/h3&gt;
&lt;p data-end=&quot;3460&quot; data-start=&quot;3430&quot;&gt;If snapshots aren&#39;t available:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;3532&quot; data-start=&quot;3462&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;pe-11 pt-3&quot;&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;PVC&lt;br /&gt; ↓&lt;br /&gt;Read filesystem&lt;br /&gt; ↓&lt;br /&gt;Compress&lt;br /&gt; ↓&lt;br /&gt;Upload to object storage&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;3545&quot; data-start=&quot;3534&quot;&gt;Advantages:&lt;/p&gt;
&lt;ul data-end=&quot;3594&quot; data-start=&quot;3547&quot;&gt;
&lt;li data-end=&quot;3572&quot; data-section-id=&quot;13cxlz6&quot; data-start=&quot;3547&quot;&gt;
works almost everywhere
&lt;/li&gt;
&lt;li data-end=&quot;3594&quot; data-section-id=&quot;oaa6sa&quot; data-start=&quot;3573&quot;&gt;
storage-independent
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;3610&quot; data-start=&quot;3596&quot;&gt;Disadvantages:&lt;/p&gt;
&lt;ul data-end=&quot;3657&quot; data-start=&quot;3612&quot;&gt;
&lt;li data-end=&quot;3620&quot; data-section-id=&quot;1luzzko&quot; data-start=&quot;3612&quot;&gt;
slower
&lt;/li&gt;
&lt;li data-end=&quot;3657&quot; data-section-id=&quot;1qaq7l7&quot; data-start=&quot;3621&quot;&gt;
consumes CPU and network bandwidth
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;3662&quot; data-start=&quot;3659&quot; /&gt;
&lt;h2 data-end=&quot;3694&quot; data-section-id=&quot;13mewfu&quot; data-start=&quot;3664&quot;&gt;Example architecture in AWS&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4008&quot; data-start=&quot;3696&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute end-1.5 top-1 z-2 md:end-2 md:top-1&quot;&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;pe-11 pt-3&quot;&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;                 Amazon EKS&lt;br /&gt;                      |&lt;br /&gt;          +-----------+-----------+&lt;br /&gt;          |                       |&lt;br /&gt;     Kubernetes API          Persistent Volumes&lt;br /&gt;          |                       |&lt;br /&gt;      Velero Server         EBS Snapshots&lt;br /&gt;          |&lt;br /&gt;          |&lt;br /&gt;      S3 Bucket&lt;br /&gt;      backups/&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;4013&quot; data-start=&quot;4010&quot; /&gt;
&lt;h2 data-end=&quot;4038&quot; data-section-id=&quot;bu3my9&quot; data-start=&quot;4015&quot;&gt;Example installation&lt;/h2&gt;
&lt;p data-end=&quot;4056&quot; data-start=&quot;4040&quot;&gt;Install the CLI:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4089&quot; data-start=&quot;4058&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;brew install velero&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4118&quot; data-start=&quot;4091&quot;&gt;Velero CLI operates on the &lt;u&gt;current kubectl context&lt;/u&gt; by default. It automatically inherits your active cluster credentials and configurations from your default kubeconfig file. You can override this behavior using the -&lt;span style=&quot;font-family: courier;&quot;&gt;-kubecontext&lt;/span&gt; or &lt;span style=&quot;font-family: courier;&quot;&gt;--kubeconfig&lt;/span&gt; flags&lt;/p&gt;&lt;p data-end=&quot;4118&quot; data-start=&quot;4091&quot;&gt;Deploy into an EKS cluster:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4278&quot; data-start=&quot;4120&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;velero install \&lt;br /&gt;  &lt;span class=&quot;ͼ12&quot;&gt;--provider&lt;/span&gt; aws \&lt;br /&gt;  &lt;span class=&quot;ͼ12&quot;&gt;--plugins&lt;/span&gt; velero/velero-plugin-for-aws \&lt;br /&gt;  &lt;span class=&quot;ͼ12&quot;&gt;--bucket&lt;/span&gt; my-backups \&lt;br /&gt;  &lt;span class=&quot;ͼ12&quot;&gt;--backup-location-config&lt;/span&gt; &lt;span class=&quot;ͼ11&quot;&gt;region&lt;/span&gt;&lt;span class=&quot;ͼv&quot;&gt;=&lt;/span&gt;eu-west-2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;4283&quot; data-start=&quot;4280&quot; /&gt;
&lt;h2 data-end=&quot;4302&quot; data-section-id=&quot;1t5ogbn&quot; data-start=&quot;4285&quot;&gt;Example backup&lt;/h2&gt;
&lt;p data-end=&quot;4330&quot; data-start=&quot;4304&quot;&gt;Back up an entire cluster:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4377&quot; data-start=&quot;4332&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;velero backup create full-cluster&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;p data-end=&quot;4399&quot; data-start=&quot;4379&quot;&gt;Back up a namespace:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4482&quot; data-start=&quot;4401&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;velero backup create production \&lt;br /&gt;    &lt;span class=&quot;ͼ12&quot;&gt;--include-namespaces&lt;/span&gt; production&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;4487&quot; data-start=&quot;4484&quot; /&gt;
&lt;h2 data-end=&quot;4507&quot; data-section-id=&quot;2yx4g5&quot; data-start=&quot;4489&quot;&gt;Example restore&lt;/h2&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4575&quot; data-start=&quot;4509&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;velero restore create \&lt;br /&gt;    &lt;span class=&quot;ͼ12&quot;&gt;--from-backup&lt;/span&gt; full-cluster&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;4580&quot; data-start=&quot;4577&quot; /&gt;
&lt;h2 data-end=&quot;4595&quot; data-section-id=&quot;1d9165h&quot; data-start=&quot;4582&quot;&gt;Scheduling&lt;/h2&gt;
&lt;p data-end=&quot;4621&quot; data-start=&quot;4597&quot;&gt;Create a nightly backup:&lt;/p&gt;
&lt;pre class=&quot;overflow-visible! px-0!&quot; data-end=&quot;4694&quot; data-start=&quot;4623&quot;&gt;&lt;div class=&quot;relative w-full mt-4 mb-1&quot;&gt;&lt;div&gt;&lt;div class=&quot;contents&quot;&gt;&lt;div class=&quot;border border-token-border-light border-radius-3xl corner-superellipse/1.1 rounded-3xl&quot;&gt;&lt;div class=&quot;relative h-full w-full border-radius-3xl bg-token-bg-elevated-secondary corner-superellipse/1.1 overflow-clip rounded-3xl lxnfua_clipPathFallback&quot;&gt;&lt;div class=&quot;pointer-events-none absolute inset-x-4 top-12 bottom-4&quot;&gt;&lt;div class=&quot;pointer-events-none sticky z-40 shrink-0 z-1!&quot;&gt;&lt;div class=&quot;sticky bg-token-border-light&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div class=&quot;h-full min-h-0 min-w-0&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative&quot;&gt;&lt;div&gt;&lt;div class=&quot;relative z-0 flex max-w-full&quot;&gt;&lt;div class=&quot;q9tKkq_viewer cm-editor z-10 light:cm-light dark:cm-light flex h-full w-full flex-col items-stretch ͼs ͼ16&quot; dir=&quot;ltr&quot; id=&quot;code-block-viewer&quot;&gt;&lt;div class=&quot;cm-scroller&quot;&gt;&lt;pre class=&quot;cm-content q9tKkq_readonly m-0&quot;&gt;&lt;code&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;velero schedule create nightly \&lt;br /&gt;    &lt;span class=&quot;ͼ12&quot;&gt;--schedule&lt;/span&gt;&lt;span class=&quot;ͼv&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;ͼz&quot;&gt;&quot;0 2 * * *&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/pre&gt;
&lt;hr data-end=&quot;4699&quot; data-start=&quot;4696&quot; /&gt;
&lt;h2 data-end=&quot;4731&quot; data-section-id=&quot;c7nz5s&quot; data-start=&quot;4701&quot;&gt;When should you use Velero?&lt;/h2&gt;
&lt;p data-end=&quot;4769&quot; data-start=&quot;4733&quot;&gt;Velero is a good fit if you want to:&lt;/p&gt;
&lt;ul data-end=&quot;5090&quot; data-start=&quot;4771&quot;&gt;
&lt;li data-end=&quot;4847&quot; data-section-id=&quot;1hguspf&quot; data-start=&quot;4771&quot;&gt;
Recover Kubernetes workloads after accidental deletion or cluster failure.
&lt;/li&gt;
&lt;li data-end=&quot;4913&quot; data-section-id=&quot;sb2dt7&quot; data-start=&quot;4848&quot;&gt;
Back up application manifests and, optionally, persistent data.
&lt;/li&gt;
&lt;li data-end=&quot;4981&quot; data-section-id=&quot;ddjnto&quot; data-start=&quot;4914&quot;&gt;
Migrate workloads between Kubernetes clusters or cloud providers.
&lt;/li&gt;
&lt;li data-end=&quot;5035&quot; data-section-id=&quot;1fb0pfc&quot; data-start=&quot;4982&quot;&gt;
Automate recurring backups with retention policies.
&lt;/li&gt;
&lt;li data-end=&quot;5090&quot; data-section-id=&quot;1tjpqko&quot; data-start=&quot;5036&quot;&gt;
Protect stateful applications running on Kubernetes.
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;5315&quot; data-start=&quot;5092&quot;&gt;If your applications also depend on external systems (for example, managed databases, message brokers, or cloud resources), Velero should be part of a broader disaster recovery strategy rather than the only backup solution.&lt;/p&gt;
&lt;h2 data-end=&quot;5342&quot; data-section-id=&quot;18jf7sh&quot; data-start=&quot;5317&quot;&gt;Velero vs. etcd backup&lt;/h2&gt;
&lt;div class=&quot;TyagGW_tableContainer&quot;&gt;&lt;div class=&quot;group TyagGW_tableWrapper flex flex-col-reverse w-fit&quot; tabindex=&quot;-1&quot;&gt;&lt;table class=&quot;w-fit min-w-(--thread-content-width)&quot; data-end=&quot;5696&quot; data-start=&quot;5344&quot;&gt;&lt;thead data-end=&quot;5378&quot; data-start=&quot;5344&quot;&gt;&lt;tr data-end=&quot;5378&quot; data-start=&quot;5344&quot;&gt;&lt;th class=&quot;last:pe-10&quot; data-col-size=&quot;sm&quot; data-end=&quot;5354&quot; data-start=&quot;5344&quot;&gt;Feature&lt;/th&gt;&lt;th class=&quot;last:pe-10&quot; data-col-size=&quot;sm&quot; data-end=&quot;5363&quot; data-start=&quot;5354&quot;&gt;Velero&lt;/th&gt;&lt;th class=&quot;last:pe-10&quot; data-col-size=&quot;sm&quot; data-end=&quot;5378&quot; data-start=&quot;5363&quot;&gt;etcd backup&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody data-end=&quot;5696&quot; data-start=&quot;5415&quot;&gt;&lt;tr data-end=&quot;5447&quot; data-start=&quot;5415&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5438&quot; data-start=&quot;5415&quot;&gt;Kubernetes resources&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5442&quot; data-start=&quot;5438&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5447&quot; data-start=&quot;5442&quot;&gt;✅&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5482&quot; data-start=&quot;5448&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5473&quot; data-start=&quot;5448&quot;&gt;Persistent volume data&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5477&quot; data-start=&quot;5473&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5482&quot; data-start=&quot;5477&quot;&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5524&quot; data-start=&quot;5483&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5515&quot; data-start=&quot;5483&quot;&gt;Restore individual namespaces&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5519&quot; data-start=&quot;5515&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5524&quot; data-start=&quot;5519&quot;&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5568&quot; data-start=&quot;5525&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5559&quot; data-start=&quot;5525&quot;&gt;Restore individual applications&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5563&quot; data-start=&quot;5559&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5568&quot; data-start=&quot;5563&quot;&gt;❌&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5611&quot; data-start=&quot;5569&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5596&quot; data-start=&quot;5569&quot;&gt;Migrate between clusters&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5600&quot; data-start=&quot;5596&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5611&quot; data-start=&quot;5600&quot;&gt;Limited&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5643&quot; data-start=&quot;5612&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5629&quot; data-start=&quot;5612&quot;&gt;Cloud agnostic&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5633&quot; data-start=&quot;5629&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5643&quot; data-start=&quot;5633&quot;&gt;Mostly&lt;/td&gt;&lt;/tr&gt;&lt;tr data-end=&quot;5696&quot; data-start=&quot;5644&quot;&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5681&quot; data-start=&quot;5644&quot;&gt;Disaster recovery for applications&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5685&quot; data-start=&quot;5681&quot;&gt;✅&lt;/td&gt;&lt;td data-col-size=&quot;sm&quot; data-end=&quot;5696&quot; data-start=&quot;5685&quot;&gt;Partial&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p data-end=&quot;6047&quot; data-is-last-node=&quot;&quot; data-is-only-node=&quot;&quot; data-start=&quot;5698&quot;&gt;For managed Kubernetes services such as &lt;span class=&quot;hover:entity-accent entity-underline inline cursor-pointer align-baseline&quot;&gt;Amazon Elastic Kubernetes Service&lt;/span&gt; (EKS), Velero is generally the preferred backup tool because it focuses on application-level recovery rather than restoring the control plane itself. In contrast, direct etcd backups are more common in self-managed Kubernetes clusters where you control the control plane.&lt;/p&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Would we benefit from Velero if we already have everything in Terraform?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yes, because Terraform and Velero back up different layers. Terraform captures the desired infrastructure (the cluster, node groups, add-ons) and our GitOps/Helm captures workload definitions. Neither captures the &lt;u&gt;runtime state that lives inside the cluster&lt;/u&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;SecretsConfigMaps created in-cluster rather than from Git&lt;/li&gt;&lt;ul&gt;&lt;li&gt;If source of truth is AWS Secrets Manager and the definitions are in Git, we recover every secret by redeploying external-secrets and waiting ~1 minute for re-sync. Velero adds little here.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;ConfigMaps created in-cluster rather than from Git&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&amp;nbsp;If they are100% in Git: they can be recovered either via&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;kubernetes_config_map&lt;/span&gt; Terraform resources (e.g. the kube-prometheus-stack one with computed IAM ARNs) or static YAML applied via kubectl_manifest (e.g. MSK scrape targets). No imperative creation.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Dynamically provisioned PVCs and the data on their volumes&lt;/li&gt;&lt;li&gt;Operator-managed CRs and other objects mutated after (terraform) apply&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So Terraform lets us rebuild an empty, correctly-shaped cluster; it does not restore what was running and stored in it at a point in time. Velero fills exactly that gap — plus it gives granular (namespace/PVC-level) and fast restores that a Terraform rebuild can&#39;t.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;span&gt;&lt;!--more--&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Do we actually need cluster (k8s object definitions + storage) backups?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yes — most acutely on the stateful clusters e.g. those running MongoDB and Clickhouse, where large EBS-backed PVs hold real data. In non-stateful clusters the main value is namespace/object-level DR (Disaster Recovery) and fast granular restore (e.g. an accidentally deleted namespace or PVC). The EBS CSI driver is already installed on all clusters, so PV snapshots are straightforward to enable.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;!--more--&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How does Velero&#39;s PVC backup differ from taking snapshots of all volumes associated with all PVCs in cluster? What&#39;s the value that Velero brings here?&amp;nbsp;&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;The short version: snapshotting every volume gives you a pile of disk images; Velero gives you disk images that are still wired into the Kubernetes objects that make them usable. The bytes are the easy part — the value is everything around them.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Concretely, here&#39;s what a &quot;loop &lt;span style=&quot;font-family: courier;&quot;&gt;aws ec2 create-snapshot&lt;/span&gt; over all PVC-backed volumes&quot; script leaves you to build yourself, and Velero does for free:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. The snapshot↔object binding (the big one)&lt;/h4&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;A raw EBS snapshot is an orphan — a snap-0abc… with no memory of which PVC, PV, namespace, StorageClass, or pod it belonged to. To restore it you must, per volume, by hand:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 1. create-volume from the snapshot (right AZ, right type),&lt;/div&gt;&lt;div&gt;&amp;nbsp; 2. hand-craft a PV pointing at the new vol-… ID,&lt;/div&gt;&lt;div&gt;&amp;nbsp; 3. craft a matching PVC with the correct size/StorageClass/claimRef,&lt;/div&gt;&lt;div&gt;&amp;nbsp; 4. fix the binding so they actually pair up,&lt;/div&gt;&lt;div&gt;&amp;nbsp; 5. restart the workload so it mounts it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For one volume that&#39;s annoying; for a whole cluster it&#39;s an error-prone afternoon during an incident. Velero records the full PVC→PV→StorageClass→namespace mapping in its backup manifest and reconstructs all of it on restore — you get a bound PVC attached to a running pod, not a disk image.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Consistency &amp;amp; point-in-time grouping&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A create-snapshot loop is staggered — volume 1 snapped at T, volume 50 at T+30s, no coordination. For a multi-volume app (say data + WAL on separate PVs) that&#39;s an inconsistent set. &lt;u&gt;Velero treats a backup as one logical operation and supports pre/post hooks&lt;/u&gt; (&lt;span style=&quot;font-family: courier;&quot;&gt;fsfreeze&lt;/span&gt;, &lt;span style=&quot;font-family: courier;&quot;&gt;db.fsyncLock()&lt;/span&gt;) to quiesce the app first (&lt;i&gt;to quiesce&lt;/i&gt; = to pause or alter a device or application to achieve a consistent state, usually in preparation for a backup or other maintenance), so you can get application-consistent — not just crash-consistent — snapshots. This is exactly the nuance behind your earlier &quot;do we still need the mongodump if we snapped the PVCs&quot; question: a bare snapshot is crash-consistent; Velero + hooks tightens that.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Discovery &amp;amp; scoping via the K8s API&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Velero enumerates PVCs through the Kubernetes API, so it knows which volumes are &lt;b&gt;PVCs&lt;/b&gt; (vs &lt;b&gt;node root disks&lt;/b&gt; vs &lt;b&gt;unrelated EBS&lt;/b&gt;) and can scope by namespace or label — &quot;back up just the mongodb namespace.&quot; A raw script has to reconstruct the volume→PVC mapping from EBS tags and risks grabbing node OS disks or missing dynamically-provisioned ones. There&#39;s no namespace concept at the EBS layer at all.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Selective &amp;amp; cross-cluster restore&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because Velero holds the &lt;b&gt;object graph&lt;/b&gt;, a restore can remap — into a different namespace, a different StorageClass (gp2→gp3, or a renamed SC [Storage Class] in the target cluster), or an entirely different cluster/region. This is what makes the blue/green K8s-upgrade path work. Raw snapshots have none of this; you&#39;d script every remap yourself.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;5. Lifecycle, catalog, portability&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;- Retention/GC: Velero backups carry a TTL and expired snapshots are garbage-collected. A snapshot script means you own tagging, retention, and GC — and snapshot sprawl (cost) if you get it wrong.&lt;/div&gt;&lt;div&gt;- Catalog: velero backup describe / restore gives you a queryable history. Raw snapshots are just IDs in EC2 you have to correlate.&lt;/div&gt;&lt;div&gt;- Portability (DataMover): Velero&#39;s CSI snapshot data mover can push the snapshot data into S3 via Kopia, so backups aren&#39;t locked to regional, account-bound EBS snapshots — enabling cheaper long retention and cross-region DR. It also covers non-EBS volumes (EFS, etc.) nthat create-snapshot can&#39;t touch.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&lt;/div&gt;&lt;div&gt;In short: Raw volume snapshots back up the data; Velero backs up the data plus everything needed to turn it back into a running, bound, correctly-classed volume in the right namespace — atomically, with consistency hooks, retention, and cross-cluster remapping. The reattachment/orchestration layer is the value, not the snapshot itself.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;!--more--&gt;&lt;/span&gt;&lt;div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Can Velero be used in cluster K8s version upgrades?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Yes, in two distinct ways:&lt;/div&gt;&lt;div&gt;&lt;ol style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Pre-upgrade safety net (in place): take a full Velero backup immediately before an EKS control-plane/version bump. If the upgrade goes wrong, we have an instant restore target for workloads + PV data, independent of the control-plane change.&lt;/li&gt;&lt;li&gt;Blue/green upgrade (migration): stand up a new cluster on the target version and velero restore the workloads into it, then cut traffic over — instead of an in-place upgrade. Velero remaps storage classes and strips node-specific fields on restore, which makes this cross-cluster move practical. This is the cleaner pattern for risky major jumps.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Caveat: on version upgrades, &lt;u&gt;watch for deprecated/removed APIs&lt;/u&gt; — objects backed up under an old &lt;span style=&quot;font-family: courier;&quot;&gt;apiVersion&lt;/span&gt; may need conversion on restore into a newer cluster; test-restore before relying on it.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;&lt;!--more--&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Show Case: 4 Kubernetes Clusters in AWS EKS&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;For e.g. a 4-EKS-cluster estate on AWS, the concrete wins:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Disaster recovery&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If a cluster gets corrupted, mis-applied, or accidentally deleted, you can stand up a fresh EKS cluster and Velero restore the whole thing — workloads, configs, PVs — instead of hoping every namespace is reproducible from Git. Even with GitOps, Velero captures the runtime state (secrets created in-cluster, dynamically provisioned PVCs, operator-managed CRs) that Git doesn&#39;t.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Namespace / PVC-level granular restore&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Someone deleted the prod PVC / dropped a namespace&quot; — restore just that namespace or volume from last night&#39;s backup. Much faster than rebuilding.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Cluster migration &amp;amp; cloning&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Lift workloads from one cluster to another — e.g. clone prod into a staging cluster for testing, or migrate to a new cluster during an EKS version upgrade or AZ/region move. Velero remaps storage classes and strips node-specific fields on restore.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Scheduled backups with retention&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Cron-style Schedule CRs (e.g. nightly full, hourly for critical namespaces) with TTL-based expiry. One bucket per cluster (or prefixes within one), lifecycle-policied in S3.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;5. Pre-upgrade safety net&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Take a backup right before an EKS control-plane bump, Helm release, or risky &lt;span style=&quot;font-family: courier;&quot;&gt;terraform apply&lt;/span&gt; that touches cluster resources — instant rollback target.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;How it fits this AWS setup&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Plugin: velero-plugin-for-aws:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;object store = S3&lt;/li&gt;&lt;li&gt;volume snapshotter = EBS&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Auth: IRSA (IAM Roles for Service Accounts) per cluster — no static keys. This should ideally match how the rest of the stack is wired.&lt;/li&gt;&lt;li&gt;CSI snapshots: since you&#39;re on EBS, enable the CSI snapshot data mover so PV backups are real EBS snapshots, not just file copies.&lt;/li&gt;&lt;li&gt;One S3 bucket, per-cluster prefix (or one bucket each) with versioning + a lifecycle rule for expiry.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Honest caveats&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;It is not an etcd backup — EKS already manages the control plane / etcd for you, so that&#39;s fine, but don&#39;t think of Velero as &quot;backing up the cluster brain.&quot;&lt;/li&gt;&lt;li&gt;It is not a substitute for application-level DB backups (RDS snapshots, Mongo dumps). Treat Velero as the Kubernetes object + PV layer; keep your existing DB backup story separate.&lt;/li&gt;&lt;li&gt;Restores of stateful apps need testing — ordering, hooks, and re-attach behaviour matter. &lt;b&gt;&lt;u&gt;A backup you&#39;ve never test-restored isn&#39;t a backup.&lt;/u&gt;&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A sensible rollout for 4 clusters: install via the Velero Helm chart through your existing Terraform/Helm pipeline, IRSA per cluster, nightly scheduled backups to S3 with a 30-day TTL, CSI snapshot data mover on, and one documented test-restore into a throwaway cluster to prove the loop.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;!--more--&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How does Velero k8s objects backup differentiate from &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl get all --all-namespaces -o yaml &amp;gt; &quot;$backup_file_name&quot;&lt;/span&gt;?&amp;nbsp;&lt;/h2&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kubectl get all&lt;/span&gt; dump is a far weaker thing than it looks, and the gap is exactly where Velero earns its keep. Breaking it down:&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl get all&lt;/span&gt; doesn&#39;t actually get &quot;all&quot;&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;all&lt;/span&gt; is a small, hardcoded category — roughly pods, services, deployments, replicasets, daemonsets, statefulsets, jobs, cronjobs. It silently omits most of what you&#39;d need to reconstruct a namespace:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;Secrets&lt;/b&gt; and &lt;b&gt;ConfigMaps&lt;/b&gt; ← this alone makes the dump useless for DR&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;PVCs&lt;/b&gt;, &lt;b&gt;Ingresses&lt;/b&gt;, &lt;b&gt;NetworkPolicies&lt;/b&gt;, &lt;b&gt;ResourceQuotas&lt;/b&gt;, &lt;b&gt;LimitRanges&lt;/b&gt;, &lt;b&gt;HPAs&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;ServiceAccounts&lt;/b&gt;, &lt;b&gt;Roles&lt;/b&gt;, &lt;b&gt;RoleBindings&lt;/b&gt; (all RBAC)&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;Namespaces&lt;/b&gt; themselves (you&#39;re dumping resources inside namespaces)&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Cluster-scoped objects entirely — &lt;b&gt;CRDs&lt;/b&gt;, &lt;b&gt;ClusterRoles&lt;/b&gt;, &lt;b&gt;ClusterRoleBindings&lt;/b&gt;, &lt;b&gt;PVs&lt;/b&gt;, &lt;b&gt;StorageClasses&lt;/b&gt;, &lt;b&gt;webhooks&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;CRD&lt;/b&gt; instances (your custom resources — cert-manager &lt;b&gt;Certificates&lt;/b&gt;, &lt;b&gt;external-secrets&lt;/b&gt;, &lt;b&gt;Karpenter&lt;/b&gt; &lt;b&gt;NodePools&lt;/b&gt;, etc.)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;u&gt;So &lt;span style=&quot;font-family: courier;&quot;&gt;--all-namespaces&lt;/span&gt; is a misnomer twice over. &lt;/u&gt;Velero, by contrast, enumerates the API discovery list and backs up every resource type (namespaced + cluster-scoped), with include/exclude filters if you want to trim.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. The biggest gap: no volume data&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A YAML dump captures the &lt;b&gt;PVC object&lt;/b&gt; — a few hundred bytes of metadata saying &quot;I want 300Gi&quot;. It does not capture the 300Gi of bytes on the &lt;b&gt;EBS volume&lt;/b&gt;. Velero snapshots the actual disk (EBS snapshot via CSI, or file-level via Kopia). For e.g. Mongo/ClickHouse clusters this is the entire point — the dump backs up the description of your data, Velero backs up your data.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. The dump isn&#39;t restorable as-is&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;kubectl get -o yaml&lt;/span&gt; includes live runtime cruft that a blind &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl apply&lt;/span&gt; chokes on:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;status&lt;/b&gt;: blocks, resourceVersion, uid, creationTimestamp, managedFields&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;Cluster-assigned values&lt;/b&gt;: Service clusterIP/nodePort, PV volumeName bindings, node-specific nodeName on pods&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;Objects that shouldn&#39;t be recreated at all&lt;/b&gt; (running pods owned by a ReplicaSet, endpoint objects)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Re-applying that fails on immutable fields and conflicts. Velero strips and regenerates these on restore (drops status/managedFields, re-allocates ClusterIPs, rebinds PVCs to freshly-restored PVs, lets controllers re-adopt pods).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;4. Restore ordering &amp;amp; dependencies&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A single giant file applied top-to-bottom breaks: you can&#39;t create a CR before its CRD is Established, a PVC before its PV, a RoleBinding before its namespace/ServiceAccount. Velero restores in a dependency-aware order (namespaces → CRDs → then the rest, PVs before PVCs) and waits for prerequisites.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;5. Selective / cross-cluster restore&lt;/h4&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Velero restores are first-class operations:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;restore one namespace&lt;/li&gt;&lt;li&gt;remap namespace names&lt;/li&gt;&lt;li&gt;filter by label&lt;/li&gt;&lt;li&gt;restore into a different cluster (&lt;u&gt;this is what makes the &lt;b&gt;blue/green K8s-upgrade&lt;/b&gt; story work&lt;/u&gt;)&lt;/li&gt;&lt;li&gt;skip-existing-resources&lt;/li&gt;&lt;li&gt;run restore hooks&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The dump is all-or-nothing text with none of that.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;6. Lifecycle, not a loose file&lt;/h4&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Velero stores backups to S3 as tracked objects with TTL/retention, checksums, and a queryable history (&lt;span style=&quot;font-family: courier;&quot;&gt;velero backup get&lt;/span&gt;), plus scheduling, metrics, and consistency hooks (fsfreeze / db.fsyncLock() pre-snapshot). The kubectl dump is one file you now have to name, rotate, secure (it contains plaintext secrets!), and hope is internally consistent since it was assembled by looping over the API.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; ---&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Where the dump is fine: a quick &quot;let me eyeball/grep the current state of a namespace&quot; or copying one manifest between environments. As a disaster-recovery mechanism it fails on the two things that matter most — it doesn&#39;t capture secrets or volume data, and what it does capture isn&#39;t cleanly restorable.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;BackupStorageLocation (BSL)&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;BackupStorageLocation (BSL) is a Velero custom resource (CRD, backupstoragelocations.velero.io) that tells Velero where in object storage to read/write backups.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;Each BSL defines:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;provider (e.g. aws),&lt;/li&gt;&lt;li&gt;bucket (e.g. test-velero-backups),&lt;/li&gt;&lt;li&gt;prefix (a path within the bucket, e.g. default-k8s, so multiple clusters can share one bucket),&lt;/li&gt;&lt;li&gt;region, and&lt;/li&gt;&lt;li&gt;an access mode — ReadWrite (default) or ReadOnly.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;When you run &lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;velero backup create&lt;/b&gt;&lt;/span&gt;, Velero writes the backup (metadata + object YAML + Kopia file data) to the default BSL&#39;s bucket. When you velero restore, it reads from the BSL the backup lives in.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To list backup locations:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &lt;b&gt;velero backup-location get&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; NAME&amp;nbsp; &amp;nbsp; &amp;nbsp; PHASE&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;...&amp;nbsp; &amp;nbsp;DEFAULT&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; default&amp;nbsp; &amp;nbsp;Available&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Phase Available means that Velero validated it can reach the bucket.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Restoring namespace from one cluster into another cluster needs a second BSL on the target cluster — a ReadOnly one pointing at the source bucket (e.g. prod-velero-backups) — so the target cluster&#39;s Velero can read source&#39;s backups and restore them locally, without any risk of writing to source&#39;s backup store. ReadOnly also means Velero won&#39;t run garbage collection / TTL deletion against that bucket — it can only restore from it, never modify it.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;% &lt;b&gt;velero describe schedules&lt;/b&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Name:&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;daily-full&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Namespace:&amp;nbsp; &amp;nbsp; velero&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Labels:&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Annotations:&amp;nbsp; kubectl.kubernetes.io/last-applied-configuration={&quot;apiVersion&quot;:&quot;velero.io/v1&quot;,&quot;kind&quot;:&quot;Schedule&quot;,&quot;metadata&quot;:{&quot;annotations&quot;:{},&quot;name&quot;:&quot;daily-full&quot;,&quot;namespace&quot;:&quot;velero&quot;},&quot;spec&quot;:{&quot;schedule&quot;:&quot;0 1 * * *&quot;,&quot;template&quot;:{&quot;defaultVolumesToFsBackup&quot;:false,&quot;includedNamespaces&quot;:[&quot;*&quot;],&quot;snapshotVolumes&quot;:false,&quot;storageLocation&quot;:&quot;default&quot;,&quot;ttl&quot;:&quot;720h0m0s&quot;}}}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Phase:&amp;nbsp; Enabled&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Paused:&amp;nbsp; false&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Schedule:&amp;nbsp; 0 1 * * *&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Backup Template:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Namespaces:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Included:&amp;nbsp; *&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Excluded:&amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Resources:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Included cluster-scoped:&amp;nbsp; &amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Excluded cluster-scoped:&amp;nbsp; &amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Included namespace-scoped:&amp;nbsp; *&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; Excluded namespace-scoped:&amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Label selector:&amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Or label selector:&amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Storage Location:&amp;nbsp; default&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Velero-Native Snapshot PVs:&amp;nbsp; &amp;nbsp; false&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; File System Backup (Default):&amp;nbsp; false&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Snapshot Move Data:&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; auto&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Data Mover:&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; velero&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; TTL:&amp;nbsp; 720h0m0s&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; CSISnapshotTimeout:&amp;nbsp; &amp;nbsp; 0s&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; ItemOperationTimeout:&amp;nbsp; 0s&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; Hooks:&amp;nbsp; &amp;lt;none&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Last Backup:&amp;nbsp; &amp;lt;never&amp;gt;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/7856347956029631307/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/7856347956029631307' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7856347956029631307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7856347956029631307'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/07/introduction-to-velero.html' title='Introduction to Velero'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEifUb1zEVjjUbTia_rDZU90OxNIRkTiGvUK4qjY0Ba3IDMFQlCketK3DyF2r-w1lYQchW3lehaRSbtTUjLKPu3mLDoKbkUeTiMTswIBFb2TPC-Wp5U8Rqs1ZosjVk-3AlGnlCVg-GSFp4mXYgz-5-qnNus8J9PHecYBQ6b9n9d0mj9xFkqaTA5_OBNovH8/s72-w640-h640-c/velero.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-8705070272795564561</id><published>2026-06-26T15:36:17.077+01:00</published><updated>2026-08-04T23:31:08.392+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Interview"/><title type='text'>DevOps Interview Questions - k8s ALB alarm </title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjw19vD-ImCQt1_4ftfF_ZFp5uHbV_56GO0zN4QVM_NXNHwkjhqGELto39_kVb2vdmvmA8E9OFn3bRe99dCi6O3aBvdUqgZgC1yi6XZcnhfupz9Tg22AYwd9nLSZyqL2XYQVy8k30IlswdnCxGb5KJhv8fNe-O1Vuk1NfhGvFVPE2uix29_aiJTLBRGka0&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;360&quot; data-original-width=&quot;640&quot; height=&quot;360&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEjw19vD-ImCQt1_4ftfF_ZFp5uHbV_56GO0zN4QVM_NXNHwkjhqGELto39_kVb2vdmvmA8E9OFn3bRe99dCi6O3aBvdUqgZgC1yi6XZcnhfupz9Tg22AYwd9nLSZyqL2XYQVy8k30IlswdnCxGb5KJhv8fNe-O1Vuk1NfhGvFVPE2uix29_aiJTLBRGka0=w640-h360&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Post-incident review / technical interview questions.&lt;/div&gt;&lt;p data-path-to-node=&quot;1&quot;&gt;These questions and answers were generated by Claude, upon it analyzed and fixed one CloudWatch alarm which have been flipping. Prompt I used:&lt;/p&gt;&lt;p data-path-to-node=&quot;1&quot;&gt;I was following your analysis and resolution of this issue in order to acquire your knowledge. I would like to test my knowledge now. Can you compile a list of questions which cover every aspect of the issue and solution here? Don&#39;t be shy of creating a really long list of questions. If I am able to answer them all that means I am able to fix the issue on my own next time. Also prepare the answer key.&lt;/p&gt;&lt;p data-path-to-node=&quot;1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p data-path-to-node=&quot;1&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;h2 data-path-to-node=&quot;3&quot;&gt;A. The alarm itself (CloudWatch / ALB fundamentals)&lt;/h2&gt;&lt;ol data-path-to-node=&quot;4&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,0,0&quot;&gt;What does the metric &lt;code data-index-in-node=&quot;21&quot; data-path-to-node=&quot;4,0,0&quot;&gt;TargetResponseTime&lt;/code&gt; actually measure, and from whose perspective (client → ALB → target)?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,1,0&quot;&gt;The alarm name was &lt;code data-index-in-node=&quot;19&quot; data-path-to-node=&quot;4,1,0&quot;&gt;k8s-api-prod-core-7db0ccf2c3-target-response-time&lt;/code&gt;. What do the &lt;code data-index-in-node=&quot;82&quot; data-path-to-node=&quot;4,1,0&quot;&gt;k8s-&lt;/code&gt; prefix and the hash portion tell you about how it was created?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,2,0&quot;&gt;The alarm config was: threshold 0.8, &lt;code data-index-in-node=&quot;37&quot; data-path-to-node=&quot;4,2,0&quot;&gt;GreaterThanThreshold&lt;/code&gt;, period 60s, 2 evaluation periods, statistic &lt;code data-index-in-node=&quot;103&quot; data-path-to-node=&quot;4,2,0&quot;&gt;Average&lt;/code&gt;. In plain English, what condition makes it fire?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,3,0&quot;&gt;Why does the alarm use the &lt;code data-index-in-node=&quot;27&quot; data-path-to-node=&quot;4,3,0&quot;&gt;LoadBalancer&lt;/code&gt; dimension only, and not a &lt;code data-index-in-node=&quot;66&quot; data-path-to-node=&quot;4,3,0&quot;&gt;TargetGroup&lt;/code&gt; dimension? What consequence did that have for our investigation?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,4,0&quot;&gt;What is &quot;flapping,&quot; and why does this particular threshold/period combination make flapping likely for a bursty workload?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,5,0&quot;&gt;How do you pull an alarm&#39;s state-transition history, and what did 9 &lt;code data-index-in-node=&quot;68&quot; data-path-to-node=&quot;4,5,0&quot;&gt;OK&lt;/code&gt;→&lt;code data-index-in-node=&quot;71&quot; data-path-to-node=&quot;4,5,0&quot;&gt;ALARM&lt;/code&gt; cycles in 5 hours tell you?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,6,0&quot;&gt;The alarm fires on &lt;code data-index-in-node=&quot;19&quot; data-path-to-node=&quot;4,6,0&quot;&gt;Average&lt;/code&gt;. Why is that distinction (vs &lt;code data-index-in-node=&quot;56&quot; data-path-to-node=&quot;4,6,0&quot;&gt;Maximum&lt;/code&gt;/&lt;code data-index-in-node=&quot;64&quot; data-path-to-node=&quot;4,6,0&quot;&gt;p99&lt;/code&gt;) absolutely central to both the diagnosis and the fix?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;4,7,0&quot;&gt;What is the &quot;low-traffic statistical artifact&quot; pattern, where a handful of slow requests inflate the average on a near-idle target — and what evidence did we use to rule it out here?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;5&quot;&gt;B. Narrowing from ALB to one service&lt;/h2&gt;&lt;ol data-path-to-node=&quot;6&quot; start=&quot;9&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;6,0,0&quot;&gt;The ALB fronted six target groups. Name the technique we used to find which one was responsible, and the AWS CLI call behind it.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;6,1,0&quot;&gt;Why can a single ALB serve six different Kubernetes services? What AWS-LB-Controller concept ties them together (hint: &lt;code data-index-in-node=&quot;119&quot; data-path-to-node=&quot;6,1,0&quot;&gt;group.name&lt;/code&gt;)?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;6,2,0&quot;&gt;Given the target group name &lt;code data-index-in-node=&quot;28&quot; data-path-to-node=&quot;6,2,0&quot;&gt;k8s-default-dataservice-88e28b4b6c&lt;/code&gt;, how do you map it back to a Kubernetes Service and namespace?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;6,3,0&quot;&gt;During the burst, &lt;code data-index-in-node=&quot;18&quot; data-path-to-node=&quot;6,3,0&quot;&gt;data-service&lt;/code&gt; showed 5.83s avg / 29.7s max while every other service was &amp;lt;0.5s. Why did that immediately exonerate the shared ALB/ingress as the cause?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;7&quot;&gt;C. First look at the workload&lt;/h2&gt;&lt;ol data-path-to-node=&quot;8&quot; start=&quot;13&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,0,0&quot;&gt;What does &lt;code data-index-in-node=&quot;10&quot; data-path-to-node=&quot;8,0,0&quot;&gt;kubectl top pods&lt;/code&gt; show, and why is a single snapshot from it dangerous as evidence?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,1,0&quot;&gt;The first snapshot showed one pod at 997m and three near-idle. What two different explanations are consistent with that, and why can&#39;t a snapshot distinguish them?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,2,0&quot;&gt;What&#39;s the difference between a CPU request and a CPU limit in Kubernetes?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,3,0&quot;&gt;How did we check whether the pod was being CPU-throttled at its limit, and what file did we read inside the container? What did &lt;code data-index-in-node=&quot;128&quot; data-path-to-node=&quot;8,3,0&quot;&gt;nr_throttled&lt;/code&gt; / &lt;code data-index-in-node=&quot;143&quot; data-path-to-node=&quot;8,3,0&quot;&gt;throttled_usec&lt;/code&gt; tell us?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,4,0&quot;&gt;A deploy to &lt;code data-index-in-node=&quot;12&quot; data-path-to-node=&quot;8,4,0&quot;&gt;v1.14.3-prod&lt;/code&gt; had happened ~15 min earlier. Why was it a red herring, and what evidence dated the flapping as pre-existing?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,5,0&quot;&gt;Distinguish the three probe types (liveness, readiness, startup). What does each one control?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;8,6,0&quot;&gt;The deployment had a &lt;code data-index-in-node=&quot;21&quot; data-path-to-node=&quot;8,6,0&quot;&gt;startupProbe&lt;/code&gt; and &lt;code data-index-in-node=&quot;38&quot; data-path-to-node=&quot;8,6,0&quot;&gt;livenessProbe&lt;/code&gt; but no &lt;code data-index-in-node=&quot;59&quot; data-path-to-node=&quot;8,6,0&quot;&gt;readinessProbe&lt;/code&gt;. Operationally, what can&#39;t the system do without a readiness probe?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;9&quot;&gt;D. The load-balancing theory (and why it was wrong)&lt;/h2&gt;&lt;ol data-path-to-node=&quot;10&quot; start=&quot;20&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;10,0,0&quot;&gt;Explain why round-robin load balancing degrades when request durations are highly variable. Use the &quot;request count vs. total work&quot; framing.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;10,1,0&quot;&gt;What is the feedback-loop difference between round-robin and least-outstanding-requests (LOR)?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;10,2,0&quot;&gt;What older, well-known algorithm is LOR equivalent to in nginx/HAProxy terms, and what does AWS&#39;s own guidance say about when to use LOR vs round-robin?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;10,3,0&quot;&gt;&quot;Head-of-line blocking&quot; appeared twice in this incident at two different layers. Name both layers and how each one blocks.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;10,4,0&quot;&gt;Why is a single &lt;code data-index-in-node=&quot;16&quot; data-path-to-node=&quot;10,4,0&quot;&gt;kubectl top&lt;/code&gt; snapshot insufficient to prove round-robin is causing imbalance, and what data would actually prove or refute it?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;10,5,0&quot;&gt;We initially called round-robin the &quot;smoking gun,&quot; then withdrew it. What specifically made that conclusion wrong?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;11&quot;&gt;E. Target mode: instance vs IP (the topology that broke the theory)&lt;/h2&gt;&lt;ol data-path-to-node=&quot;12&quot; start=&quot;26&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,0,0&quot;&gt;What&#39;s the difference between an ALB target group in &lt;code data-index-in-node=&quot;53&quot; data-path-to-node=&quot;12,0,0&quot;&gt;instance&lt;/code&gt; mode vs &lt;code data-index-in-node=&quot;70&quot; data-path-to-node=&quot;12,0,0&quot;&gt;ip&lt;/code&gt; mode? How can you tell which one you have from the registered targets and the target-group port?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,1,0&quot;&gt;The target group registered 10 EC2 instances on port 31892. What does that tell you about the request path from ALB to pod?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,2,0&quot;&gt;What is a &lt;code data-index-in-node=&quot;10&quot; data-path-to-node=&quot;12,2,0&quot;&gt;NodePort&lt;/code&gt; service, and what is &lt;code data-index-in-node=&quot;40&quot; data-path-to-node=&quot;12,2,0&quot;&gt;externalTrafficPolicy: Cluster&lt;/code&gt; vs &lt;code data-index-in-node=&quot;74&quot; data-path-to-node=&quot;12,2,0&quot;&gt;Local&lt;/code&gt;?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,3,0&quot;&gt;With &lt;code data-index-in-node=&quot;5&quot; data-path-to-node=&quot;12,3,0&quot;&gt;instance&lt;/code&gt; mode + &lt;code data-index-in-node=&quot;21&quot; data-path-to-node=&quot;12,3,0&quot;&gt;Cluster&lt;/code&gt; policy, describe the full path of a request from client to the backend process, including every hop and who load-balances at each hop.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,4,0&quot;&gt;Given that topology, explain precisely why switching the ALB to &lt;code data-index-in-node=&quot;64&quot; data-path-to-node=&quot;12,4,0&quot;&gt;least_outstanding_requests&lt;/code&gt; would not fix per-pod imbalance.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,5,0&quot;&gt;What two changes together would enable load-aware per-pod routing, and why is that a much bigger change than a one-line annotation?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;12,6,0&quot;&gt;Why were ALB access logs not useful for confirming per-pod distribution in this setup?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;13&quot;&gt;F. Getting the proof (Prometheus / time series)&lt;/h2&gt;&lt;ol data-path-to-node=&quot;14&quot; start=&quot;33&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;14,0,0&quot;&gt;What is &lt;code data-index-in-node=&quot;8&quot; data-path-to-node=&quot;14,0,0&quot;&gt;kube-prometheus-stack&lt;/code&gt;, and where did it live in the cluster?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;14,1,0&quot;&gt;Why is &lt;code data-index-in-node=&quot;7&quot; data-path-to-node=&quot;14,1,0&quot;&gt;kubectl port-forward&lt;/code&gt; an acceptable read-only way to query Prometheus, and what does it actually do?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;14,2,0&quot;&gt;Write (conceptually) the PromQL that gives per-pod CPU usage over time. Why &lt;code data-index-in-node=&quot;76&quot; data-path-to-node=&quot;14,2,0&quot;&gt;rate(container_cpu_usage_seconds_total[...])&lt;/code&gt; rather than the raw counter?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;14,3,0&quot;&gt;The time series showed all four pods evenly at 0.5–0.95 cores during the burst. Why did that refute the imbalance theory in one stroke?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;14,4,0&quot;&gt;Each pod plateaued at ~0.9 cores despite a 1.5-core limit. What does that plateau strongly imply about the process model inside the pod?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;15&quot;&gt;G. The real root cause (app server / GIL / async)&lt;/h2&gt;&lt;ol data-path-to-node=&quot;16&quot; start=&quot;38&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,0,0&quot;&gt;What is an application server worker (e.g., Gunicorn), and what&#39;s the difference between the master process and a worker process?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,1,0&quot;&gt;What is a Global Interpreter Lock (GIL) or similar single-threaded runtime constraint, and why does it mean one worker process ≈ one core of CPU-bound throughput?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,2,0&quot;&gt;The config had &lt;code data-index-in-node=&quot;15&quot; data-path-to-node=&quot;16,2,0&quot;&gt;workers = 1&lt;/code&gt; and an asynchronous event-loop worker class specified. Explain what each line does.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,3,0&quot;&gt;What is the difference between a synchronous worker and an asynchronous worker in an application server? When does each shine?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,4,0&quot;&gt;Why is an async (event-loop) worker the wrong model for heavy, synchronous CPU-bound data processing? What does &quot;blocking the event loop&quot; mean concretely?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,5,0&quot;&gt;So with one async worker doing CPU-bound work, what is the per-pod concurrency for heavy requests — and how did that produce the fleet-wide ceiling of ~4 concurrent requests?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,6,0&quot;&gt;How did we confirm the worker count and the CPU quota from inside a running pod (what command, what does &lt;code data-index-in-node=&quot;105&quot; data-path-to-node=&quot;16,6,0&quot;&gt;cpu.max = 150000 100000&lt;/code&gt; mean, what does worker count = 2 mean)?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,7,0&quot;&gt;Why was the 1.5-core CPU limit effectively unusable given &lt;code data-index-in-node=&quot;58&quot; data-path-to-node=&quot;16,7,0&quot;&gt;workers = 1&lt;/code&gt;?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;16,8,0&quot;&gt;Tie it together: explain the full causal chain from &quot;traffic burst&quot; to &quot;alarm fires,&quot; in one paragraph, using the confirmed root cause.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;17&quot;&gt;H. Designing the fix&lt;/h2&gt;&lt;ol data-path-to-node=&quot;18&quot; start=&quot;47&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,0,0&quot;&gt;We considered vertical (more workers/CPU per pod), horizontal (more pods via HPA), and both. What&#39;s the trade-off, and why did &quot;both&quot; win?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,1,0&quot;&gt;Why couldn&#39;t we just change the worker class to synchronous (or offload to a process pool) as part of this immediate infrastructure fix? What kind of change would that be?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,2,0&quot;&gt;We set &lt;code data-index-in-node=&quot;7&quot; data-path-to-node=&quot;18,2,0&quot;&gt;workers = 2&lt;/code&gt;. Why did the CPU limit have to go up to &lt;code data-index-in-node=&quot;59&quot; data-path-to-node=&quot;18,2,0&quot;&gt;2000m&lt;/code&gt; at the same time? What would happen if we&#39;d set &lt;code data-index-in-node=&quot;113&quot; data-path-to-node=&quot;18,2,0&quot;&gt;workers = 2&lt;/code&gt; but left the limit at &lt;code data-index-in-node=&quot;147&quot; data-path-to-node=&quot;18,2,0&quot;&gt;1500m&lt;/code&gt;?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,3,0&quot;&gt;The pods used ~2.5Gi RSS each at one worker. Why did we expect ~5Gi with two workers, and why wouldn&#39;t worker-fork preloading save us here? (What did we learn about when the application memory caches are built?)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,4,0&quot;&gt;There&#39;s one import-time load we found (e.g., heavy model loading in a utility file). Why is that one shareable-via-fork but the bulk of the runtime memory is not?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,5,0&quot;&gt;The HPA was &lt;code data-index-in-node=&quot;12&quot; data-path-to-node=&quot;18,5,0&quot;&gt;autoscaling/v1&lt;/code&gt;, target 80%, min 4 / max 7. We measured bursts peaking at ~75% of the 1200m request. Explain mechanically why the HPA never scaled.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,6,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;18,6,0&quot;&gt;targetCPUUtilizationPercentage&lt;/code&gt; is a percentage of what? Recompute: at the new 1500m request and a pod using ~0.9 cores, what utilization does the HPA see?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,7,0&quot;&gt;Why did we lower the target to 50% and raise max to 10, rather than just one of those?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,8,0&quot;&gt;What does a &lt;code data-index-in-node=&quot;12&quot; data-path-to-node=&quot;18,8,0&quot;&gt;topologySpreadConstraints&lt;/code&gt; with &lt;code data-index-in-node=&quot;43&quot; data-path-to-node=&quot;18,8,0&quot;&gt;maxSkew: 1&lt;/code&gt;, &lt;code data-index-in-node=&quot;55&quot; data-path-to-node=&quot;18,8,0&quot;&gt;topologyKey: kubernetes.io/hostname&lt;/code&gt;, &lt;code data-index-in-node=&quot;92&quot; data-path-to-node=&quot;18,8,0&quot;&gt;whenUnsatisfiable: ScheduleAnyway&lt;/code&gt; do — and why soft (&lt;code data-index-in-node=&quot;145&quot; data-path-to-node=&quot;18,8,0&quot;&gt;ScheduleAnyway&lt;/code&gt;) rather than hard (&lt;code data-index-in-node=&quot;179&quot; data-path-to-node=&quot;18,8,0&quot;&gt;DoNotSchedule&lt;/code&gt;)?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,9,0&quot;&gt;We deliberately did not add a readiness probe. Explain the failure mode that a naive &lt;code data-index-in-node=&quot;85&quot; data-path-to-node=&quot;18,9,0&quot;&gt;/health&lt;/code&gt; readiness probe would cause in this specific app under heavy load. Why is &quot;no readiness probe&quot; temporarily safer than a bad one here?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,10,0&quot;&gt;What was the container port vs Service target port mismatch situation? Why was alignment low-risk, and why didn&#39;t it affect routing?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;18,11,0&quot;&gt;An orphaned HPA manifest file was deleted. Why was it safe to delete, and how did we confirm it was no longer active?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;19&quot;&gt;I. Where the config lives &amp;amp; deploy mechanics&lt;/h2&gt;&lt;ol data-path-to-node=&quot;20&quot; start=&quot;59&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;20,0,0&quot;&gt;How did we determine the workload was not managed by GitOps tool deployments (e.g., Argo CD), despite the tool being installed? What metadata annotation was the fingerprint?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;20,1,0&quot;&gt;Which repository and file holds the deployment/HPA, and which separate repository/file holds the ingress? Why do they deploy through different mechanisms?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;20,2,0&quot;&gt;Describe the deployment pipeline flow end to end. What event triggers it, and what are the key build/test/deploy jobs?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;20,3,0&quot;&gt;The deploy step does a string substitution (&lt;code data-index-in-node=&quot;44&quot; data-path-to-node=&quot;20,3,0&quot;&gt;sed &#39;s#$TAG#...#&#39;&lt;/code&gt;) then &lt;code data-index-in-node=&quot;68&quot; data-path-to-node=&quot;20,3,0&quot;&gt;kubectl apply&lt;/code&gt;. What&#39;s the role of the &lt;code data-index-in-node=&quot;106&quot; data-path-to-node=&quot;20,3,0&quot;&gt;$TAG&lt;/code&gt; placeholder, and where does the tag value come from?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;20,4,0&quot;&gt;Why does merging a PR to the main branch not deploy anything, while pushing a specific environment release tag does?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;20,5,0&quot;&gt;The pipeline runs on private self-hosted runners. Why does that matter for a private-endpoint cluster?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;21&quot;&gt;J. Capacity analysis&lt;/h2&gt;&lt;ol data-path-to-node=&quot;22&quot; start=&quot;65&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,0,0&quot;&gt;What instance types/sizes back the general-purpose compute tier, and how much allocatable CPU/memory does each have?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,1,0&quot;&gt;What is Cluster Autoscaler, and how does it differ from node lifecycle managers like Karpenter? Which one is active in this cluster?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,2,0&quot;&gt;There are two node groups feeding the tier (spot instances and on-demand instances). What are their min/max sizes, and what&#39;s the combined node ceiling?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,3,0&quot;&gt;Do the packing math: given ~3920m allocatable CPU and ~400m daemonset overhead, how many pods at 1500m request fit per node? Why is CPU, not memory, the binding constraint?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,4,0&quot;&gt;At HPA max (10 pods), how many nodes are needed, and is that within the ceiling? What&#39;s left for other tenants?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,5,0&quot;&gt;Why is horizontal scale-out slow relative to instant traffic bursts? List every contributor to a cold pod&#39;s total time-to-serve.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,6,0&quot;&gt;Given that scale-out lag, which part of our fix delivers immediate relief, and which acts as the slower &quot;second line of defense&quot;?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;22,7,0&quot;&gt;Why did we bump the memory request to 5Gi even though it doesn&#39;t change node packing density?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;23&quot;&gt;K. Staging-first deploy &amp;amp; verification&lt;/h2&gt;&lt;ol data-path-to-node=&quot;24&quot; start=&quot;73&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;24,0,0&quot;&gt;Why deploy to the staging environment first when the staging manifest file wasn&#39;t even changed by the PR?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;24,1,0&quot;&gt;Precisely what does the staging deploy validate, and what does it &lt;i data-index-in-node=&quot;66&quot; data-path-to-node=&quot;24,1,0&quot;&gt;not&lt;/i&gt; validate?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;24,2,0&quot;&gt;Staging runs on the same cluster as production. How is it isolated, and what was the risk we flagged about a tiny 0.5-core staging pod suddenly running &lt;code data-index-in-node=&quot;152&quot; data-path-to-node=&quot;24,2,0&quot;&gt;workers=2&lt;/code&gt;?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;24,3,0&quot;&gt;List the verification steps we ran on staging, and the pass criteria for each.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;24,4,0&quot;&gt;Why did we test both &lt;code data-index-in-node=&quot;21&quot; data-path-to-node=&quot;24,4,0&quot;&gt;GET /health&lt;/code&gt; and a heavy &lt;code data-index-in-node=&quot;45&quot; data-path-to-node=&quot;24,4,0&quot;&gt;POST&lt;/code&gt; data processing route, rather than just the health check?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;24,5,0&quot;&gt;The startup probe warms up by hitting an internal pre-cache endpoint. What does that endpoint do, and why is it the most likely place for a deploy to fail (especially on a CPU-starved staging pod)?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;25&quot;&gt;L. Production rollout &amp;amp; confirmation&lt;/h2&gt;&lt;ol data-path-to-node=&quot;26&quot; start=&quot;79&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,0,0&quot;&gt;What version did we tag, and why a patch bump (&lt;code data-index-in-node=&quot;47&quot; data-path-to-node=&quot;26,0,0&quot;&gt;v1.14.4&lt;/code&gt;) rather than a minor/major version shift?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,1,0&quot;&gt;During the production rollout, two new pods went &lt;code data-index-in-node=&quot;49&quot; data-path-to-node=&quot;26,1,0&quot;&gt;Pending&lt;/code&gt;, one with no available node. What happened next, and which log event confirmed the cluster autoscaler reacting?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,2,0&quot;&gt;Why did the rollout take ~6 minutes and stay safe (no dropped traffic) the whole time? What deployment configuration controls the surge/unavailable behavior?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,3,0&quot;&gt;Post-deploy, the new pods showed only ~2.6Gi memory usage, not ~5Gi. Why — and why is that expected rather than a contradiction of our sizing?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,4,0&quot;&gt;After deploy, individual requests still hit ~3s max, but the alarm stayed &lt;code data-index-in-node=&quot;74&quot; data-path-to-node=&quot;26,4,0&quot;&gt;OK&lt;/code&gt;. Why is that consistent with a successful fix? (Connect it back to question 7.)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,5,0&quot;&gt;We monitored for 45 minutes and saw no flapping, yet we kept the incident ticket open. What&#39;s the honest gap in that evidence, and what would definitively close it?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;26,6,0&quot;&gt;The HPA stayed at 4 pods during the entire monitoring window. Why is that a good sign rather than a sign the HPA fix did nothing?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;27&quot;&gt;M. Operational / process &amp;amp; gotchas&lt;/h2&gt;&lt;ol data-path-to-node=&quot;28&quot; start=&quot;86&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,0,0&quot;&gt;The cluster API is private-only. What&#39;s the practical consequence for local engineers using &lt;code data-index-in-node=&quot;92&quot; data-path-to-node=&quot;28,0,0&quot;&gt;kubectl&lt;/code&gt;, and why does &lt;code data-index-in-node=&quot;114&quot; data-path-to-node=&quot;28,0,0&quot;&gt;aws sts get-caller-identity&lt;/code&gt; succeed while &lt;code data-index-in-node=&quot;156&quot; data-path-to-node=&quot;28,0,0&quot;&gt;kubectl&lt;/code&gt; times out? How do you distinguish an auth problem from a network/VPN problem?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,1,0&quot;&gt;Why did the CI/CD deployment job still work even when our local machine&#39;s &lt;code data-index-in-node=&quot;74&quot; data-path-to-node=&quot;28,1,0&quot;&gt;kubectl&lt;/code&gt; couldn&#39;t reach the cluster?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,2,0&quot;&gt;The standard code security checks failed on automated image-scanning and vulnerability gates. Diagnose how to isolate the root cause. Were they caused by our configuration change? How do we prove that?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,3,0&quot;&gt;The repository management system showed a &lt;code data-index-in-node=&quot;42&quot; data-path-to-node=&quot;28,3,0&quot;&gt;BLOCKED&lt;/code&gt; merge status, but branch protection rules returned a 404. What&#39;s the resolution of that apparent contradiction (e.g., legacy branch protection vs. modern repository rulesets)?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,4,0&quot;&gt;What was the only thing actually gating the code merge, and why were the red security check flags irrelevant to it?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,5,0&quot;&gt;Which actions in this whole deployment flow required explicit manual operator confirmation before execution, and why those specifically?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;28,6,0&quot;&gt;What&#39;s the commit-authorship convention in this engineering environment, and what must never appear in a commit message or PR description?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h2 data-path-to-node=&quot;29&quot;&gt;N. Synthesis &amp;amp; transfer (test of true mastery)&lt;/h2&gt;&lt;ol data-path-to-node=&quot;30&quot; start=&quot;93&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;30,0,0&quot;&gt;If you were paged for this exact alarm tomorrow with zero prior context, list the first five commands/queries you&#39;d run, in order, and what each one would tell you.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;30,1,0&quot;&gt;Name three plausible-but-wrong hypotheses for a flapping &lt;code data-index-in-node=&quot;57&quot; data-path-to-node=&quot;30,1,0&quot;&gt;TargetResponseTime&lt;/code&gt; alarm, and the single piece of evidence that kills each.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;30,2,0&quot;&gt;Suppose the per-pod CPU time series had shown one pod pinned at 100% and three idle (the load imbalance pattern we originally expected). Given the instance-mode topology, what would the real fix have been then — and why is it different from the LOR annotation?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;30,3,0&quot;&gt;The fix here was vertical + HPA scaling. Under what circumstances would the correct long-term fix instead be an application architecture change, and what would that change look like?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;30,4,0&quot;&gt;Generalize the core lesson: what specific property of an application workload makes &quot;adding more replicas behind a standard round-robin/random balancer&quot; fail to resolve response time spikes, and what is the class of fixes that does help?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;30,5,0&quot;&gt;If the morning peak traffic burst still trips the alarm after this infrastructure fix, what are your next two remediation levers (in order), and what data would you collect to choose between them?&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;span&gt;&lt;a name=&#39;more&#39;&gt;&lt;/a&gt;&lt;/span&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p data-path-to-node=&quot;0&quot;&gt;Incident Study: &lt;code data-index-in-node=&quot;16&quot; data-path-to-node=&quot;3&quot;&gt;core-service-api&lt;/code&gt; · &lt;code data-index-in-node=&quot;35&quot; data-path-to-node=&quot;3&quot;&gt;prod-default-k8s&lt;/code&gt;&lt;/p&gt;&lt;h2 data-path-to-node=&quot;4&quot;&gt;The Flapping Latency Alarm, From Page to Fix&lt;/h2&gt;&lt;p data-path-to-node=&quot;5&quot;&gt;A complete answer key to the 98-question self-test — covering triage, the two false trails, the confirmed root cause, the fix, capacity, deployment, and the operational mechanics.&lt;/p&gt;&lt;p data-path-to-node=&quot;6&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;6&quot;&gt;How to use:&lt;/b&gt; Answer from memory first, then check here. If you can reconstruct section N (synthesis) unaided, you can run this incident solo. Answers are deliberately tight; the &lt;i data-index-in-node=&quot;177&quot; data-path-to-node=&quot;6&quot;&gt;why-not-the-other-thing&lt;/i&gt; reasoning matters more than the recall.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;8&quot;&gt;Sections Overview&lt;/h2&gt;&lt;ul data-path-to-node=&quot;9&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,0,0&quot;&gt;A:&lt;/b&gt; The Alarm Itself&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,0&quot;&gt;B:&lt;/b&gt; ALB → One Service&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,2,0&quot;&gt;C:&lt;/b&gt; First Look at the Workload&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,3,0&quot;&gt;D:&lt;/b&gt; Load-Balancing Theory &lt;i data-index-in-node=&quot;25&quot; data-path-to-node=&quot;9,3,0&quot;&gt;(False Trail)&lt;/i&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,4,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,4,0&quot;&gt;E:&lt;/b&gt; Instance vs IP Mode &lt;i data-index-in-node=&quot;23&quot; data-path-to-node=&quot;9,4,0&quot;&gt;(False Trail)&lt;/i&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,5,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,5,0&quot;&gt;F:&lt;/b&gt; Getting the Proof&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,6,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,6,0&quot;&gt;G:&lt;/b&gt; The Real Root Cause &lt;i data-index-in-node=&quot;23&quot; data-path-to-node=&quot;9,6,0&quot;&gt;(Confirmed)&lt;/i&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,7,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,7,0&quot;&gt;H:&lt;/b&gt; Designing the Fix&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,8,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,8,0&quot;&gt;I:&lt;/b&gt; Where Config Lives&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,9,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,9,0&quot;&gt;J:&lt;/b&gt; Capacity Analysis&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,10,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,10,0&quot;&gt;K:&lt;/b&gt; Staging &amp;amp; Verification&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,11,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,11,0&quot;&gt;L:&lt;/b&gt; Prod Rollout&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,12,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,12,0&quot;&gt;M:&lt;/b&gt; Operational Gotchas&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;9,13,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,13,0&quot;&gt;N:&lt;/b&gt; Synthesis &amp;amp; Transfer&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-path-to-node=&quot;11&quot;&gt;A. The Alarm Itself&lt;/h2&gt;&lt;h3 data-path-to-node=&quot;12&quot;&gt;CloudWatch &amp;amp; ALB fundamentals — what the metric is and what makes it fire.&lt;/h3&gt;&lt;h4 data-path-to-node=&quot;13&quot;&gt;A1. What does &lt;code data-index-in-node=&quot;14&quot; data-path-to-node=&quot;13&quot;&gt;TargetResponseTime&lt;/code&gt; measure, and from whose perspective?&lt;/h4&gt;&lt;p data-path-to-node=&quot;14&quot;&gt;The elapsed seconds from when the ALB forwards a request to a target until the target starts returning the response headers — i.e., backend processing time as measured by the ALB. It excludes the ALB&#39;s own request/response processing and the client ↔ ALB network time. The vantage point is the load balancer, not the client or the pod.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;15&quot;&gt;A2. What do the &lt;code data-index-in-node=&quot;16&quot; data-path-to-node=&quot;15&quot;&gt;k8s-&lt;/code&gt; prefix and the hash in the alarm name tell you?&lt;/h4&gt;&lt;p data-path-to-node=&quot;16&quot;&gt;It&#39;s machine-generated, not hand-authored. The ALB &lt;code data-index-in-node=&quot;51&quot; data-path-to-node=&quot;16&quot;&gt;k8s-coreproddefault-7db0ccf2c3&lt;/code&gt; is one that the AWS Load Balancer Controller created for an ingress group; the hash is derived from it, and the alarm is named after that LB by monitoring automation. &lt;b data-index-in-node=&quot;249&quot; data-path-to-node=&quot;16&quot;&gt;Practical takeaway:&lt;/b&gt; it tracks an ALB resource, so you investigate at the ALB, and hand-edits to the alarm may be reverted by whatever automated system manages it.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;17&quot;&gt;A3. Threshold 0.8, GreaterThanThreshold, period 60s, 2 evaluation periods, statistic Average — what fires it?&lt;/h4&gt;&lt;p data-path-to-node=&quot;18&quot;&gt;When the 60-second average &lt;code data-index-in-node=&quot;27&quot; data-path-to-node=&quot;18&quot;&gt;TargetResponseTime&lt;/code&gt; is greater than 0.8s for two consecutive 1-minute periods.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;19&quot;&gt;A4. Why only the &lt;code data-index-in-node=&quot;17&quot; data-path-to-node=&quot;19&quot;&gt;LoadBalancer&lt;/code&gt; dimension, and what did that cost us?&lt;/h4&gt;&lt;p data-path-to-node=&quot;20&quot;&gt;With only the &lt;code data-index-in-node=&quot;14&quot; data-path-to-node=&quot;20&quot;&gt;LoadBalancer&lt;/code&gt; dimension, the metric is aggregated across all six target groups behind the ALB. &lt;b data-index-in-node=&quot;108&quot; data-path-to-node=&quot;20&quot;&gt;Consequence:&lt;/b&gt; the alarm can&#39;t tell you which specific service is slow, so we had to break latency down per-target-group ourselves to localize it.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;21&quot;&gt;A5. What is flapping, and why is it likely here?&lt;/h4&gt;&lt;p data-path-to-node=&quot;22&quot;&gt;Flapping is the alarm rapidly toggling &lt;code data-index-in-node=&quot;39&quot; data-path-to-node=&quot;22&quot;&gt;OK&lt;/code&gt; ↔ &lt;code data-index-in-node=&quot;44&quot; data-path-to-node=&quot;22&quot;&gt;ALARM&lt;/code&gt;. A modest threshold (0.8s) on a short 2 × 60s average, against a workload that spikes for a few minutes then falls to near-zero, means every burst crosses the line and every recovery clears it — resulting in repeated state transitions rather than a sustained &lt;code data-index-in-node=&quot;309&quot; data-path-to-node=&quot;22&quot;&gt;ALARM&lt;/code&gt;.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;23&quot;&gt;A6. How do you pull state-transition history, and what did 9 cycles / 5h say?&lt;/h4&gt;&lt;p data-path-to-node=&quot;24&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;24&quot;&gt;aws cloudwatch describe-alarm-history --history-item-type StateUpdate&lt;/code&gt;. Nine &lt;code data-index-in-node=&quot;76&quot; data-path-to-node=&quot;24&quot;&gt;OK&lt;/code&gt; → &lt;code data-index-in-node=&quot;81&quot; data-path-to-node=&quot;24&quot;&gt;ALARM&lt;/code&gt; cycles in five hours signaled a genuine, recurring degradation — a pattern worth real investigation, not a one-off network blip.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;25&quot;&gt;A7. The alarm fires on &lt;code data-index-in-node=&quot;23&quot; data-path-to-node=&quot;25&quot;&gt;Average&lt;/code&gt;. Why is that central to both diagnosis and fix?&lt;/h4&gt;&lt;p data-path-to-node=&quot;26&quot;&gt;Because the trigger is the &lt;i data-index-in-node=&quot;27&quot; data-path-to-node=&quot;26&quot;&gt;average&lt;/i&gt;, the fix doesn&#39;t need to eliminate slow individual requests (tail max can stay ~3s) — it needs to keep the average under 0.8s. Adding concurrency pulls the average down even while tail latency persists. This framing drives the diagnosis (bursts raise the average once capacity saturates) and the fix (more concurrency → average drops below threshold). Keep this in mind for Q83.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;27&quot;&gt;A8. What is the low-traffic statistical artifact, and how did we rule it out?&lt;/h4&gt;&lt;p data-path-to-node=&quot;28&quot;&gt;On a near-idle target, a handful of slow requests dominate a tiny sample and inflate the average, tripping the alarm with no real systemic problem. We ruled it out because request count rose during the spikes (~100 → 130–320/min) — the latency was load-correlated, meaning real degradation under load, not a sparse-sample artifact.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;30&quot;&gt;B. Narrowing from ALB to One Service&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;31&quot;&gt;B9. How did we find the responsible target group?&lt;/h4&gt;&lt;p data-path-to-node=&quot;32&quot;&gt;A per-target-group breakdown of &lt;code data-index-in-node=&quot;32&quot; data-path-to-node=&quot;32&quot;&gt;TargetResponseTime&lt;/code&gt; using the &lt;code data-index-in-node=&quot;61&quot; data-path-to-node=&quot;32&quot;&gt;TargetGroup&lt;/code&gt; dimension, via &lt;code data-index-in-node=&quot;88&quot; data-path-to-node=&quot;32&quot;&gt;aws cloudwatch get-metric-data&lt;/code&gt; with one query per target group (avg, max, request count) over the burst window.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;33&quot;&gt;B10. Why can one ALB serve six services?&lt;/h4&gt;&lt;p data-path-to-node=&quot;34&quot;&gt;The AWS Load Balancer Controller&#39;s ingress grouping feature: ingresses sharing the &lt;code data-index-in-node=&quot;83&quot; data-path-to-node=&quot;34&quot;&gt;alb.ingress.kubernetes.io/group.name&lt;/code&gt; annotation are merged onto a single ALB, with each backend service getting its own target group.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;35&quot;&gt;B11. From &lt;code data-index-in-node=&quot;10&quot; data-path-to-node=&quot;35&quot;&gt;k8s-default-dataapi-88e28b4b6c&lt;/code&gt;, how do you get back to a Service?&lt;/h4&gt;&lt;p data-path-to-node=&quot;36&quot;&gt;The target-group name encodes namespace + service: namespace &lt;code data-index-in-node=&quot;61&quot; data-path-to-node=&quot;36&quot;&gt;default&lt;/code&gt;, service truncated to &lt;code data-index-in-node=&quot;91&quot; data-path-to-node=&quot;36&quot;&gt;dataapi...&lt;/code&gt; → &lt;code data-index-in-node=&quot;104&quot; data-path-to-node=&quot;36&quot;&gt;core-service-api&lt;/code&gt;. Confirm via the ingress backend mapping (host → service) or by matching the target-group port to the service&#39;s &lt;code data-index-in-node=&quot;233&quot; data-path-to-node=&quot;36&quot;&gt;NodePort&lt;/code&gt;.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;37&quot;&gt;B12. Why did &lt;code data-index-in-node=&quot;13&quot; data-path-to-node=&quot;37&quot;&gt;data-api&lt;/code&gt; being slow exonerate the shared ALB/ingress?&lt;/h4&gt;&lt;p data-path-to-node=&quot;38&quot;&gt;Only &lt;code data-index-in-node=&quot;5&quot; data-path-to-node=&quot;38&quot;&gt;data-api&lt;/code&gt; showed 5.83s avg / 29.7s max; the other five services on the same ALB and ingress were &amp;lt;0.5s. A shared-path fault (ALB, ingress, global TLS) would hurt all of them. Isolation to one backend points squarely at that backend, not the shared infrastructure.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;40&quot;&gt;C. First Look at the Workload&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;41&quot;&gt;C13. What does &lt;code data-index-in-node=&quot;15&quot; data-path-to-node=&quot;41&quot;&gt;kubectl top pods&lt;/code&gt; show, and why is one snapshot dangerous?&lt;/h4&gt;&lt;p data-path-to-node=&quot;42&quot;&gt;Current per-pod CPU/memory from metrics-server — a point-in-time sample. It has no time axis, so it can&#39;t distinguish a persistent hotspot from a transient one or reveal behavior across an entire traffic burst. Conclusions drawn from a single sample are easy to get wrong (we did, briefly).&lt;/p&gt;&lt;h4 data-path-to-node=&quot;43&quot;&gt;C14. One pod at 997m, three idle — what two explanations fit, and why can&#39;t a snapshot decide?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;44&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;44,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,0,0&quot;&gt;(a)&lt;/b&gt; Routing keeps hammering one pod (a persistent imbalance); or&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;44,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;44,1,0&quot;&gt;(b)&lt;/b&gt; That pod merely happens to be mid-way through one heavy request while the others are momentarily between requests (a transient, rotating hotspot).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;45&quot;&gt;With no time dimension, a snapshot can&#39;t tell a fixed hotspot from a moving one.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;46&quot;&gt;C15. CPU request vs limit?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;47&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;47,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47,0,0&quot;&gt;Request:&lt;/b&gt; Reserved amount used for scheduling (and the denominator for HPA CPU %).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;47,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;47,1,0&quot;&gt;Limit:&lt;/b&gt; Hard ceiling; exceed CPU and you&#39;re throttled, exceed memory and you&#39;re &lt;code data-index-in-node=&quot;79&quot; data-path-to-node=&quot;47,1,0&quot;&gt;OOMKilled&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;48&quot;&gt;C16. How did we check CPU-limit throttling, and what did we find?&lt;/h4&gt;&lt;p data-path-to-node=&quot;49&quot;&gt;Read the cgroup stats inside the container: &lt;code data-index-in-node=&quot;44&quot; data-path-to-node=&quot;49&quot;&gt;cat /sys/fs/cgroup/cpu.stat&lt;/code&gt;. &lt;code data-index-in-node=&quot;73&quot; data-path-to-node=&quot;49&quot;&gt;nr_throttled&lt;/code&gt; / &lt;code data-index-in-node=&quot;88&quot; data-path-to-node=&quot;49&quot;&gt;throttled_usec&lt;/code&gt; were near-zero, so the pod was not being throttled at its CPU limit — limit-throttling wasn&#39;t the cause; the ceiling was elsewhere.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;50&quot;&gt;C17. Why was the recent &lt;code data-index-in-node=&quot;24&quot; data-path-to-node=&quot;50&quot;&gt;v1.14.3&lt;/code&gt; deploy a red herring?&lt;/h4&gt;&lt;p data-path-to-node=&quot;51&quot;&gt;The deploy created the young pods (~12:00), but the alarm history showed flapping starting ~04:34 and running ~8h — well before the deploy. A cause can&#39;t post-date its effect, so the deploy was purely coincidental.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;52&quot;&gt;C18. Distinguish liveness, readiness, startup probes.&lt;/h4&gt;&lt;ul data-path-to-node=&quot;53&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;53,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53,0,0&quot;&gt;Liveness:&lt;/b&gt; Is the container alive — fail → restart container.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;53,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53,1,0&quot;&gt;Readiness:&lt;/b&gt; Is the pod fit to receive traffic — fail → removed from Service/LB endpoints (no restart).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;53,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;53,2,0&quot;&gt;Startup:&lt;/b&gt; Has the app finished initializing — gates the other two until it passes; fail → restart container.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;54&quot;&gt;C19. What can&#39;t the system do without a readiness probe?&lt;/h4&gt;&lt;p data-path-to-node=&quot;55&quot;&gt;It can&#39;t shed load: a pod has no way to signal &lt;i data-index-in-node=&quot;47&quot; data-path-to-node=&quot;55&quot;&gt;&quot;I&#39;m overloaded, stop sending me traffic&quot;&lt;/i&gt; and drop out of the ALB target pool. As long as the container runs, it stays in the routing rotation.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;57&quot;&gt;D. The Load-Balancing Theory&lt;/h2&gt;&lt;h3 data-path-to-node=&quot;58&quot;&gt;False Trail&lt;/h3&gt;&lt;blockquote data-path-to-node=&quot;59&quot;&gt;&lt;p data-path-to-node=&quot;59,0&quot;&gt;A well-reasoned hypothesis that the evidence later refuted — kept here because the reasoning is correct in general, just not the cause here.&lt;/p&gt;&lt;/blockquote&gt;&lt;h4 data-path-to-node=&quot;60&quot;&gt;D20. Why does round-robin degrade with variable request durations?&lt;/h4&gt;&lt;p data-path-to-node=&quot;61&quot;&gt;Round-Robin (RR) equalizes request count, but actual load is &lt;code data-index-in-node=&quot;61&quot; data-path-to-node=&quot;61&quot;&gt;count × duration&lt;/code&gt;. With uniform durations, equal count ≈ equal load. With high variance, a target that draws a long request still receives its turn-by-turn share of new requests (count-fair) while it&#39;s busy, so its backlog grows while targets handling short requests finish quickly and idle. RR is blind to how busy each target actually is.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;62&quot;&gt;D21. The feedback-loop difference vs least-outstanding-requests?&lt;/h4&gt;&lt;p data-path-to-node=&quot;63&quot;&gt;RR has no feedback — it rotates regardless of backend state. Least Outstanding Requests (LOR) keeps a per-target in-flight counter and sends each new request to the least-busy target, so a stuck target stops receiving work until it drains. LOR closes the feedback loop that RR lacks.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;64&quot;&gt;D22. What is LOR equivalent to, and what does AWS advise?&lt;/h4&gt;&lt;p data-path-to-node=&quot;65&quot;&gt;It&#39;s AWS&#39;s name for classic least-connections (&lt;code data-index-in-node=&quot;47&quot; data-path-to-node=&quot;65&quot;&gt;nginx&lt;/code&gt; &lt;code data-index-in-node=&quot;53&quot; data-path-to-node=&quot;65&quot;&gt;least_conn&lt;/code&gt;, &lt;code data-index-in-node=&quot;65&quot; data-path-to-node=&quot;65&quot;&gt;HAProxy&lt;/code&gt; &lt;code data-index-in-node=&quot;73&quot; data-path-to-node=&quot;65&quot;&gt;leastconn&lt;/code&gt;). AWS guidance: round-robin when requests and targets are similar; least-outstanding-requests when request complexity varies or targets differ in capacity — exactly the 0.01–30s spread we saw.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;66&quot;&gt;D23. &quot;Head-of-line blocking&quot; appeared at two layers — name both.&lt;/h4&gt;&lt;ol data-path-to-node=&quot;67&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;67,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;67,0,0&quot;&gt;ALB/target layer:&lt;/b&gt; Under RR, requests queue behind a slow request on a busy target instance.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;67,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;67,1,0&quot;&gt;Inside the pod:&lt;/b&gt; The single async worker&#39;s event loop is blocked by a CPU-bound request, freezing other in-flight requests on that same worker.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p data-path-to-node=&quot;68&quot;&gt;Same phenomenon, two different layers — and the second turned out to be the real root cause.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;69&quot;&gt;D24. Why can&#39;t a snapshot prove RR is the cause, and what would?&lt;/h4&gt;&lt;p data-path-to-node=&quot;70&quot;&gt;A snapshot lacks a time axis, so it can&#39;t show whether the hot pod persists or rotates, nor whether requests spread evenly by count while load concentrates. Proof needs a time series: per-pod CPU through a burst, or per-target request-count and latency over time.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;71&quot;&gt;D25. What made the round-robin conclusion wrong?&lt;/h4&gt;&lt;p data-path-to-node=&quot;72&quot;&gt;Two things: the per-pod CPU time series showed all pods evenly loaded (no pinned pod), and the target group is in &lt;code data-index-in-node=&quot;114&quot; data-path-to-node=&quot;72&quot;&gt;instance&lt;/code&gt; mode, where the ALB balances across nodes — not pods — so its algorithm can&#39;t even produce a per-pod imbalance. Both facts independently killed the theory.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;74&quot;&gt;E. Target Mode: Instance vs IP&lt;/h2&gt;&lt;h3 data-path-to-node=&quot;75&quot;&gt;False Trail&lt;/h3&gt;&lt;blockquote data-path-to-node=&quot;76&quot;&gt;&lt;p data-path-to-node=&quot;76,0&quot;&gt;The topology detail that demolished the LB-algorithm fix and reframed the problem.&lt;/p&gt;&lt;/blockquote&gt;&lt;h4 data-path-to-node=&quot;77&quot;&gt;E26. &lt;code data-index-in-node=&quot;5&quot; data-path-to-node=&quot;77&quot;&gt;instance&lt;/code&gt; vs &lt;code data-index-in-node=&quot;17&quot; data-path-to-node=&quot;77&quot;&gt;ip&lt;/code&gt; target mode — how do you tell which you have?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;78&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;78,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;78,0,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;78,0,0&quot;&gt;instance&lt;/code&gt;:&lt;/b&gt; Targets are EC2 nodes (&lt;code data-index-in-node=&quot;33&quot; data-path-to-node=&quot;78,0,0&quot;&gt;ALB → node:NodePort → kube-proxy → pod&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;78,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;78,1,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;78,1,0&quot;&gt;ip&lt;/code&gt;:&lt;/b&gt; Targets are pod IPs directly.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;79&quot;&gt;Tell from the registered targets (instance IDs &lt;code data-index-in-node=&quot;47&quot; data-path-to-node=&quot;79&quot;&gt;i-...&lt;/code&gt; vs pod IPs) and the target-group port (a NodePort like &lt;code data-index-in-node=&quot;108&quot; data-path-to-node=&quot;79&quot;&gt;31892&lt;/code&gt; ⇒ instance; a container port like &lt;code data-index-in-node=&quot;148&quot; data-path-to-node=&quot;79&quot;&gt;8080&lt;/code&gt; ⇒ ip).&lt;/p&gt;&lt;h4 data-path-to-node=&quot;80&quot;&gt;E27. 10 instances registered on 31892 — what does that imply about the path?&lt;/h4&gt;&lt;p data-path-to-node=&quot;81&quot;&gt;The ALB sends to nodes on the NodePort, then each node&#39;s &lt;code data-index-in-node=&quot;57&quot; data-path-to-node=&quot;81&quot;&gt;kube-proxy&lt;/code&gt; forwards to a pod. So the ALB&#39;s balancing decision is across nodes, and there&#39;s a second hop (&lt;code data-index-in-node=&quot;162&quot; data-path-to-node=&quot;81&quot;&gt;kube-proxy&lt;/code&gt;) that actually picks the destination pod.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;82&quot;&gt;E28. NodePort, and &lt;code data-index-in-node=&quot;19&quot; data-path-to-node=&quot;82&quot;&gt;externalTrafficPolicy: Cluster&lt;/code&gt; vs &lt;code data-index-in-node=&quot;53&quot; data-path-to-node=&quot;82&quot;&gt;Local&lt;/code&gt;?&lt;/h4&gt;&lt;p data-path-to-node=&quot;83&quot;&gt;NodePort exposes the service on a static port on every node.&lt;/p&gt;&lt;ul data-path-to-node=&quot;84&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;84,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;84,0,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;84,0,0&quot;&gt;Cluster&lt;/code&gt;:&lt;/b&gt; Any node may forward to any pod cluster-wide (extra network hop, source IP is SNAT&#39;d).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;84,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;84,1,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;84,1,0&quot;&gt;Local&lt;/code&gt;:&lt;/b&gt; A node only forwards to pods running on itself (preserves client IP, no second hop, but a node with no local pod fails LB health checks).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;85&quot;&gt;E29. Trace the full request path in this topology.&lt;/h4&gt;&lt;p data-path-to-node=&quot;86&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;86&quot;&gt;client → ALB (round-robins across 10 nodes) → node:31892 → kube-proxy (iptables/IPVS, effectively random across all 4 pod endpoints cluster-wide, possibly a second network hop to a pod on a different node) → application master → a worker process&lt;/code&gt;.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;87&quot;&gt;E30. Why wouldn&#39;t LOR fix per-pod imbalance here?&lt;/h4&gt;&lt;p data-path-to-node=&quot;88&quot;&gt;LOR would balance outstanding requests across EC2 nodes, but &lt;code data-index-in-node=&quot;61&quot; data-path-to-node=&quot;88&quot;&gt;kube-proxy&lt;/code&gt; then picks the destination pod randomly afterward. The ALB neither sees nor controls which pod serves a request, so per-pod imbalance is completely untouched by altering the ALB algorithm.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;89&quot;&gt;E31. What two changes together would enable load-aware per-pod routing?&lt;/h4&gt;&lt;p data-path-to-node=&quot;90&quot;&gt;Switch target-type to &lt;code data-index-in-node=&quot;22&quot; data-path-to-node=&quot;90&quot;&gt;ip&lt;/code&gt; (ALB targets pods directly, removing the &lt;code data-index-in-node=&quot;66&quot; data-path-to-node=&quot;90&quot;&gt;kube-proxy&lt;/code&gt; hop) and set &lt;code data-index-in-node=&quot;90&quot; data-path-to-node=&quot;90&quot;&gt;least_outstanding_requests&lt;/code&gt;. It&#39;s a much bigger change: &lt;code data-index-in-node=&quot;145&quot; data-path-to-node=&quot;90&quot;&gt;ip&lt;/code&gt; mode alters the data path, requires specific VPC CNI / security-group wiring for ALB-to-pod traffic, and the algorithm annotation applies ingress-wide.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;91&quot;&gt;E32. Why were ALB access logs useless for the per-pod question?&lt;/h4&gt;&lt;p data-path-to-node=&quot;92&quot;&gt;In &lt;code data-index-in-node=&quot;3&quot; data-path-to-node=&quot;92&quot;&gt;instance&lt;/code&gt; mode, the access log records the EC2 node as the target (&lt;code data-index-in-node=&quot;69&quot; data-path-to-node=&quot;92&quot;&gt;node IP:31892&lt;/code&gt;), not the individual pod — so it cannot reveal per-pod traffic distribution.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;94&quot;&gt;F. Getting the Proof&lt;/h2&gt;&lt;h3 data-path-to-node=&quot;95&quot;&gt;Replacing inference with a time series — the turning point.&lt;/h3&gt;&lt;h4 data-path-to-node=&quot;96&quot;&gt;F33. What is &lt;code data-index-in-node=&quot;13&quot; data-path-to-node=&quot;96&quot;&gt;kube-prometheus-stack&lt;/code&gt; and where did it live?&lt;/h4&gt;&lt;p data-path-to-node=&quot;97&quot;&gt;The &lt;b&gt;Prometheus Operator&lt;/b&gt; bundle — Prometheus, Alertmanager, Grafana, &lt;code data-index-in-node=&quot;68&quot; data-path-to-node=&quot;97&quot;&gt;kube-state-metrics&lt;/code&gt;, and &lt;code data-index-in-node=&quot;92&quot; data-path-to-node=&quot;97&quot;&gt;node-exporter&lt;/code&gt;. It ran in the dedicated &lt;code data-index-in-node=&quot;131&quot; data-path-to-node=&quot;97&quot;&gt;monitoring&lt;/code&gt; namespace.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;98&quot;&gt;F34. Why is &lt;code data-index-in-node=&quot;12&quot; data-path-to-node=&quot;98&quot;&gt;kubectl port-forward&lt;/code&gt; an acceptable read-only way to query it?&lt;/h4&gt;&lt;p data-path-to-node=&quot;99&quot;&gt;It opens a local secure tunnel (&lt;code data-index-in-node=&quot;32&quot; data-path-to-node=&quot;99&quot;&gt;localhost&lt;/code&gt; → pod/service port) through the Kubernetes API server. It proxies a connection without mutating cluster state — so it&#39;s completely safe for querying metrics.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;100&quot;&gt;F35. What PromQL gives per-pod CPU over time, and why &lt;code data-index-in-node=&quot;54&quot; data-path-to-node=&quot;100&quot;&gt;rate(...)&lt;/code&gt;?&lt;/h4&gt;&lt;p data-path-to-node=&quot;101&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;101&quot;&gt;rate(container_cpu_usage_seconds_total{pod=~&quot;core-service-api.*&quot;}[1m])&lt;/code&gt;. The metric is a monotonic CPU-seconds counter; &lt;code data-index-in-node=&quot;119&quot; data-path-to-node=&quot;101&quot;&gt;rate(...[1m])&lt;/code&gt; turns it into cores-used-per-second (i.e., CPU cores used) over the window. The raw counter alone is meaningless for &quot;how busy is it right now&quot;.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;102&quot;&gt;F36. All four pods even at 0.5–0.95 cores — why did that refute imbalance?&lt;/h4&gt;&lt;p data-path-to-node=&quot;103&quot;&gt;The imbalance theory predicted one pinned pod at 100% and the rest idle. The time series showed the exact opposite — every pod working evenly and hard, none starved — so load was already naturally balanced. There was nothing to rebalance; the LB algorithm was irrelevant.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;104&quot;&gt;F37. Why does a ~0.9-core plateau under a 1.5-core limit point at the process model?&lt;/h4&gt;&lt;p data-path-to-node=&quot;105&quot;&gt;A CPU-bound workload that flatlines near exactly one core despite a 1.5-core allowance is the classic fingerprint of a single-process / single-threaded execution ceiling — the pod cannot push past ~1 core, which points straight at having a single execution worker.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;107&quot;&gt;G. The Real Root Cause&lt;/h2&gt;&lt;h3 data-path-to-node=&quot;108&quot;&gt;Confirmed&lt;/h3&gt;&lt;blockquote data-path-to-node=&quot;109&quot;&gt;&lt;p data-path-to-node=&quot;109,0&quot;&gt;Application server workers, runtime resource locks, and an async worker doing synchronous CPU work.&lt;/p&gt;&lt;/blockquote&gt;&lt;h4 data-path-to-node=&quot;110&quot;&gt;G38. What is an application server worker vs the master?&lt;/h4&gt;&lt;p data-path-to-node=&quot;111&quot;&gt;A worker is an OS process running a full copy of the application code and handling incoming HTTP requests. The master process binds the network socket, spawns/monitors/restarts workers, and handles reloads — but serves no requests itself.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;112&quot;&gt;G39. What is a Global Interpreter Lock (GIL) or single-threaded runtime constraint and its consequence?&lt;/h4&gt;&lt;p data-path-to-node=&quot;113&quot;&gt;It prevents multiple threads from executing native bytecode at the same time per process. A single process yields about one core of CPU-bound throughput regardless of how many threads are created or how many cores are allocated to the container.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;114&quot;&gt;G40. What do &lt;code data-index-in-node=&quot;13&quot; data-path-to-node=&quot;114&quot;&gt;workers = 1&lt;/code&gt; and &lt;code data-index-in-node=&quot;29&quot; data-path-to-node=&quot;114&quot;&gt;worker_class = &quot;async_class_name&quot;&lt;/code&gt; do?&lt;/h4&gt;&lt;p data-path-to-node=&quot;115&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;115&quot;&gt;workers = 1&lt;/code&gt; creates exactly one worker process. The worker class sets each worker to run an asynchronous event loop, designed to juggle many concurrent connections via async non-blocking I/O multiplexing.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;116&quot;&gt;G41. Sync vs async worker — when does each shine?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;117&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;117,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;117,0,0&quot;&gt;Sync:&lt;/b&gt; Handles one request at a time to completion; concurrency comes from adding more worker processes — ideal for CPU-bound tasks.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;117,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;117,1,0&quot;&gt;Async:&lt;/b&gt; Handles many concurrent requests on a single loop — but is only an advantage when requests spend most of their time waiting on I/O.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;118&quot;&gt;G42. Why is async wrong for CPU-bound data processing work?&lt;/h4&gt;&lt;p data-path-to-node=&quot;119&quot;&gt;An event loop can only switch tasks at &lt;code data-index-in-node=&quot;39&quot; data-path-to-node=&quot;119&quot;&gt;await&lt;/code&gt; points (non-blocking I/O waits). A heavy synchronous CPU computation holds the thread and the runtime lock tightly and never yields, meaning the entire event loop is blocked for the duration of the calculation — freezing every other concurrent request on that worker. Async buys nothing for CPU-heavy tasks and introduces massive head-of-line blocking.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;120&quot;&gt;G43. So what&#39;s the per-pod and fleet concurrency for heavy requests?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;121&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;121,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;121,0,0&quot;&gt;Per pod:&lt;/b&gt; Effectively one heavy request in flight at a time (one worker, loop blocked during CPU work).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;121,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;121,1,0&quot;&gt;Fleet:&lt;/b&gt; 4 pods × 1 worker ⇒ a strict fleet ceiling of about 4 concurrent heavy data extractions.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;122&quot;&gt;G44. How did we confirm worker count and CPU quota from inside the pod?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;123&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;123,0,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;123,0,0&quot;&gt;cat /sys/fs/cgroup/cpu.max&lt;/code&gt; → &lt;code data-index-in-node=&quot;29&quot; data-path-to-node=&quot;123,0,0&quot;&gt;150000 100000&lt;/code&gt; = &lt;code data-index-in-node=&quot;45&quot; data-path-to-node=&quot;123,0,0&quot;&gt;quota/period&lt;/code&gt; = 1.5 cores.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;123,1,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;123,1,0&quot;&gt;ps | grep -c app_server&lt;/code&gt; → confirmed 1 master + 1 worker, meaning &lt;code data-index-in-node=&quot;65&quot; data-path-to-node=&quot;123,1,0&quot;&gt;workers=1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;124&quot;&gt;The Prometheus plateau and the running configuration now perfectly agreed.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;125&quot;&gt;G45. Why was the 1.5-core limit largely unusable?&lt;/h4&gt;&lt;p data-path-to-node=&quot;126&quot;&gt;Because one worker ≈ one core max due to the process lock, the extra ~0.5 core of the 1.5 limit could never be consumed by CPU-bound work. The resource allocation was there on paper, but the process model couldn&#39;t physically use it.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;127&quot;&gt;G46. State the full causal chain, burst → alarm.&lt;/h4&gt;&lt;p data-path-to-node=&quot;128&quot;&gt;A traffic burst arrives → with only ~4 concurrent-request fleet capacity, excess requests queue up immediately. On each pod, the single event loop is completely blocked by the in-progress CPU-bound processing task → per-request latency climbs dramatically into seconds (max ~30s) → the 60s average crosses 0.8s for two consecutive periods → the CloudWatch alarm fires. Once the burst passes, the queue drains, the average falls, the alarm clears, resulting in a flapping pattern.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;130&quot;&gt;H. Designing the Fix&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;131&quot;&gt;H47. Vertical, horizontal, or both — and why?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;132&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;132,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;132,0,0&quot;&gt;Vertical&lt;/b&gt; (more workers/CPU per pod) adds concurrency instantly to existing running pods.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;132,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;132,1,0&quot;&gt;Horizontal&lt;/b&gt; (more pods via HPA) adds capacity but provisions slowly due to node provisioning.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;133&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;133&quot;&gt;Both won:&lt;/b&gt; vertical gives immediate relief on the real bottleneck; HPA adds elastic burst headroom. Horizontal alone is too slow for sudden short bursts; vertical alone has no safety ceiling.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;134&quot;&gt;H48. Why not just switch to sync workers / offload to a pool?&lt;/h4&gt;&lt;p data-path-to-node=&quot;135&quot;&gt;The application code is fundamentally built on top of an async framework with coroutine request handlers. Switching to sync workers or offloading heavy work to a background process pool requires rewriting structural parts of the app — an application-code change, which is out of scope for an emergency infrastructure hotfix.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;136&quot;&gt;H49. Why raise the CPU limit to 2000m alongside &lt;code data-index-in-node=&quot;48&quot; data-path-to-node=&quot;136&quot;&gt;workers=2&lt;/code&gt;?&lt;/h4&gt;&lt;p data-path-to-node=&quot;137&quot;&gt;Two workers need up to ~2 full cores. Leaving the limit at 1500m would CPU-throttle them (two processes fighting over 1.5 cores), meaning neither could run a full core and defeating the change. The limit rose to 2000m to back two full workers.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;138&quot;&gt;H50. Why expect ~5Gi at two workers, and why won&#39;t application preloading help?&lt;/h4&gt;&lt;p data-path-to-node=&quot;139&quot;&gt;Each worker builds its own heavy data models/caches in memory at runtime (via a warmup endpoint or on initial requests), so memory scales near-linearly with workers (~2.5Gi → ~5Gi). Preloading only shares state loaded at import time &lt;i data-index-in-node=&quot;233&quot; data-path-to-node=&quot;139&quot;&gt;before&lt;/i&gt; the process forks (copy-on-write); caches built post-fork are private to each worker process.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;140&quot;&gt;H51. Why is the import-time model load shareable but the runtime caches aren&#39;t?&lt;/h4&gt;&lt;p data-path-to-node=&quot;141&quot;&gt;The base library imports run at module load time — with preloading, this data lives in the master process before the fork and is shared copy-on-write. The actual execution caches are built after the fork, per worker, making them private anonymous memory.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;142&quot;&gt;H52. Why did the HPA never scale originally?&lt;/h4&gt;&lt;p data-path-to-node=&quot;143&quot;&gt;The &lt;code data-index-in-node=&quot;4&quot; data-path-to-node=&quot;143&quot;&gt;targetCPUUtilizationPercentage&lt;/code&gt; is a percentage of the CPU &lt;i data-index-in-node=&quot;62&quot; data-path-to-node=&quot;143&quot;&gt;request&lt;/i&gt; (1200m). Traffic bursts pushed pods to ~0.9 cores ≈ 75% — just under the 80% trigger — and the bursts were too short for the time-averaged utilization metric to sustain above 80%. So the cluster sat flat at 4 replicas.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;144&quot;&gt;H53. Percent of &lt;i data-index-in-node=&quot;16&quot; data-path-to-node=&quot;144&quot;&gt;what&lt;/i&gt;? Recompute at the new request.&lt;/h4&gt;&lt;p data-path-to-node=&quot;145&quot;&gt;It is a percentage of the CPU &lt;i data-index-in-node=&quot;30&quot; data-path-to-node=&quot;145&quot;&gt;request&lt;/i&gt;. At the new 1500m request, 0.9 cores ≈ 60%. Under real heavy load, two workers can use up to ~2 cores ≈ 133% of the request — far over the new 50% target, which easily triggers scaling.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;146&quot;&gt;H54. Why lower the target to 50% &lt;i data-index-in-node=&quot;33&quot; data-path-to-node=&quot;146&quot;&gt;and&lt;/i&gt; raise max to 10?&lt;/h4&gt;&lt;p data-path-to-node=&quot;147&quot;&gt;Lowering the target makes it scale much earlier during short bursts; raising max gives the cluster room to scale out. A lower target without a higher max caps out too early; a higher max without a lower target would still fail to trigger.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;148&quot;&gt;H55. What does the topology spread constraint do, and why soft?&lt;/h4&gt;&lt;p data-path-to-node=&quot;149&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;149&quot;&gt;maxSkew: 1&lt;/code&gt; / &lt;code data-index-in-node=&quot;13&quot; data-path-to-node=&quot;149&quot;&gt;topologyKey: kubernetes.io/hostname&lt;/code&gt; ensures the per-node count of these pods stays within 1 of each other, spreading them evenly across nodes. Soft (&lt;code data-index-in-node=&quot;162&quot; data-path-to-node=&quot;149&quot;&gt;ScheduleAnyway&lt;/code&gt;) makes it a preference: if node capacity is scarce, it will still schedule pods (just less evenly) rather than leaving pods stuck in &lt;code data-index-in-node=&quot;310&quot; data-path-to-node=&quot;149&quot;&gt;Pending&lt;/code&gt;. Hard (&lt;code data-index-in-node=&quot;325&quot; data-path-to-node=&quot;149&quot;&gt;DoNotSchedule&lt;/code&gt;) could block scheduling entirely and cause a self-inflicted outage.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;150&quot;&gt;H56. Why deliberately omit the readiness probe?&lt;/h4&gt;&lt;p data-path-to-node=&quot;151&quot;&gt;A readiness probe shares the exact same event loop that CPU-bound requests block. Under sustained load, the probe would time out on every busy pod → all pods would flip to &lt;code data-index-in-node=&quot;172&quot; data-path-to-node=&quot;151&quot;&gt;NotReady&lt;/code&gt; at once → all would be removed from the ALB → causing wide-scale 503 errors, which is worse than slow responses. Without a readiness probe, pods stay in rotation (slow but serving). Revisit this once loop-blocking is fixed in code.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;152&quot;&gt;E57. The container port vs Service target port situation?&lt;/h4&gt;&lt;p data-path-to-node=&quot;153&quot;&gt;The application server binds to port 8080 and the Kubernetes Service &lt;code data-index-in-node=&quot;69&quot; data-path-to-node=&quot;153&quot;&gt;targetPort&lt;/code&gt; was set to 8080 (numeric), so traffic already routed correctly; &lt;code data-index-in-node=&quot;144&quot; data-path-to-node=&quot;153&quot;&gt;containerPort: 80&lt;/code&gt; in the manifest was just an incorrect documentation declaration. Changing it to 8080 aligns the documentation with no risk to production routing.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;154&quot;&gt;H58. Why was deleting the separate HPA file safe?&lt;/h4&gt;&lt;p data-path-to-node=&quot;155&quot;&gt;The deployment pipeline only applies &lt;code data-index-in-node=&quot;37&quot; data-path-to-node=&quot;155&quot;&gt;k8s.prod.yml&lt;/code&gt; (which has its own inline HPA definition); the old &lt;code data-index-in-node=&quot;101&quot; data-path-to-node=&quot;155&quot;&gt;hpa-prod.yml&lt;/code&gt; was never referenced anywhere — confirmed by searching the repo and workflows. It was a confusing duplicate file, safe to remove.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;157&quot;&gt;I. Where the Config Lives &amp;amp; Deploy Mechanics&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;158&quot;&gt;I59. How did we know it wasn&#39;t GitOps-managed despite the tool being installed?&lt;/h4&gt;&lt;p data-path-to-node=&quot;159&quot;&gt;The live Deployment carried a &lt;code data-index-in-node=&quot;30&quot; data-path-to-node=&quot;159&quot;&gt;kubectl.kubernetes.io/last-applied-configuration&lt;/code&gt; annotation — the clear fingerprint of a standard &lt;code data-index-in-node=&quot;128&quot; data-path-to-node=&quot;159&quot;&gt;kubectl apply&lt;/code&gt; execution. GitOps-managed objects (like Argo CD) carry distinct tracking annotations instead, and querying the tool showed it only managed specific preview apps.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;160&quot;&gt;I60. Which repo/file holds the Deployment vs the ingress, and why different mechanisms?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;161&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;161,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;161,0,0&quot;&gt;Deployment/HPA:&lt;/b&gt; &lt;code data-index-in-node=&quot;16&quot; data-path-to-node=&quot;161,0,0&quot;&gt;org/core-service&lt;/code&gt; → &lt;code data-index-in-node=&quot;35&quot; data-path-to-node=&quot;161,0,0&quot;&gt;deploy/k8s.prod.yml&lt;/code&gt;, shipped by the repo&#39;s tag-triggered GitHub Actions (&lt;code data-index-in-node=&quot;108&quot; data-path-to-node=&quot;161,0,0&quot;&gt;kubectl apply&lt;/code&gt;).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;161,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;161,1,0&quot;&gt;Ingress:&lt;/b&gt; &lt;code data-index-in-node=&quot;9&quot; data-path-to-node=&quot;161,1,0&quot;&gt;org/infra-k8s&lt;/code&gt; → &lt;code data-index-in-node=&quot;25&quot; data-path-to-node=&quot;161,1,0&quot;&gt;.../platform/router/main.tf&lt;/code&gt;, shipped via &lt;code data-index-in-node=&quot;66&quot; data-path-to-node=&quot;161,1,0&quot;&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;162&quot;&gt;They have different lifecycles — application config moves with the app&#39;s release tags, while cluster routing is foundational infrastructure managed in Terraform.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;163&quot;&gt;I61. Describe the production pipeline end to end.&lt;/h4&gt;&lt;ul data-path-to-node=&quot;164&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;164,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;164,0,0&quot;&gt;Trigger:&lt;/b&gt; Pushing a git tag matching &lt;code data-index-in-node=&quot;36&quot; data-path-to-node=&quot;164,0,0&quot;&gt;v*-prod&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;164,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;164,1,0&quot;&gt;Jobs:&lt;/b&gt; &lt;code data-index-in-node=&quot;6&quot; data-path-to-node=&quot;164,1,0&quot;&gt;test&lt;/code&gt; (unit tests) → &lt;code data-index-in-node=&quot;26&quot; data-path-to-node=&quot;164,1,0&quot;&gt;build&lt;/code&gt; (docker build + push to AWS ECR) → &lt;code data-index-in-node=&quot;67&quot; data-path-to-node=&quot;164,1,0&quot;&gt;deploy-prod&lt;/code&gt; (&lt;code data-index-in-node=&quot;80&quot; data-path-to-node=&quot;164,1,0&quot;&gt;sed&lt;/code&gt; string replacement on the tag, then &lt;code data-index-in-node=&quot;120&quot; data-path-to-node=&quot;164,1,0&quot;&gt;kubectl apply&lt;/code&gt; executed on an in-VPC self-hosted runner) → &lt;code data-index-in-node=&quot;178&quot; data-path-to-node=&quot;164,1,0&quot;&gt;release-prod&lt;/code&gt; (creates GitHub release).&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;165&quot;&gt;I62. Role of the &lt;code data-index-in-node=&quot;17&quot; data-path-to-node=&quot;165&quot;&gt;$TAG&lt;/code&gt; placeholder?&lt;/h4&gt;&lt;p data-path-to-node=&quot;166&quot;&gt;It acts as a placeholder string in the manifest&#39;s image declaration line; the deploy step uses &lt;code data-index-in-node=&quot;95&quot; data-path-to-node=&quot;166&quot;&gt;sed&lt;/code&gt; to substitute it with the exact pushed tag (e.g., &lt;code data-index-in-node=&quot;149&quot; data-path-to-node=&quot;166&quot;&gt;v1.14.4-prod&lt;/code&gt;) so the applied manifest pulls the freshly built image. The value is sourced from the pipeline context (&lt;code data-index-in-node=&quot;266&quot; data-path-to-node=&quot;166&quot;&gt;GITHUB_REF&lt;/code&gt;).&lt;/p&gt;&lt;h4 data-path-to-node=&quot;167&quot;&gt;I63. Why does merging to main branch not deploy, but a tag does?&lt;/h4&gt;&lt;p data-path-to-node=&quot;168&quot;&gt;The automation workflows do not trigger deployments on main branch pushes; the CD workflow is configured to trigger only on a &lt;code data-index-in-node=&quot;126&quot; data-path-to-node=&quot;168&quot;&gt;v*-prod&lt;/code&gt; tag. Deployment is therefore a deliberate, separate engineering act — explicitly cutting and pushing the tag.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;169&quot;&gt;I64. Why does the self-hosted runner matter for a private cluster?&lt;/h4&gt;&lt;p data-path-to-node=&quot;170&quot;&gt;The runner instance is situated inside the VPC, meaning it can natively reach the Kubernetes cluster&#39;s private API endpoint to run &lt;code data-index-in-node=&quot;131&quot; data-path-to-node=&quot;170&quot;&gt;kubectl&lt;/code&gt; commands — even when an engineer&#39;s local machine is off the VPN.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;172&quot;&gt;J. Capacity Analysis&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;173&quot;&gt;J65. What instances back the general-purpose tier, and how big?&lt;/h4&gt;&lt;p data-path-to-node=&quot;174&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;174&quot;&gt;t3.xlarge&lt;/code&gt; / &lt;code data-index-in-node=&quot;12&quot; data-path-to-node=&quot;174&quot;&gt;t3a.xlarge&lt;/code&gt; (and similar compute variants in the node group) — 4 vCPU, ~3920m allocatable CPU, ~14.4Gi allocatable memory each.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;175&quot;&gt;J66. Cluster Autoscaler vs Karpenter — which is here?&lt;/h4&gt;&lt;p data-path-to-node=&quot;176&quot;&gt;Cluster Autoscaler scales fixed-shape Auto Scaling Groups (ASGs) based on pods being marked as unschedulable (&lt;code data-index-in-node=&quot;110&quot; data-path-to-node=&quot;176&quot;&gt;Pending&lt;/code&gt;); Karpenter provisions right-sized nodes directly on demand from pending pod resource definitions without predefined groups. This cluster uses Cluster Autoscaler (the alternative tool was removed during a node group migration).&lt;/p&gt;&lt;h4 data-path-to-node=&quot;177&quot;&gt;J67. The two node groups and the combined ceiling?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;178&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;178,0,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;178,0,0&quot;&gt;gen-purpose&lt;/code&gt; (Spot instances): min 2 / max 5.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;178,1,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;178,1,0&quot;&gt;gp-od&lt;/code&gt; (On-Demand fallback): min 0 / max 5.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;178,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;178,2,0&quot;&gt;Combined ceiling:&lt;/b&gt; 10 total nodes.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;179&quot;&gt;J68. Do the packing math — why is CPU the binding constraint?&lt;/h4&gt;&lt;p data-path-to-node=&quot;180&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;180&quot;&gt;(3920m allocatable − ~400m daemonset overhead) ÷ 1500m request ≈ 2 pods per node&lt;/code&gt;. Memory utilization at 2 pods would be &lt;code data-index-in-node=&quot;120&quot; data-path-to-node=&quot;180&quot;&gt;2 × 5Gi + ~1Gi system overhead ≈ 11Gi&lt;/code&gt;, well under the 14.4Gi allocatable. A 3rd pod would require more CPU than the node possesses (&lt;code data-index-in-node=&quot;252&quot; data-path-to-node=&quot;180&quot;&gt;4500m &amp;gt; 3920m&lt;/code&gt;). Therefore, CPU exhaustion prevents further packing before memory runs out.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;181&quot;&gt;J69. At HPA max (10 pods), how many nodes, and does it fit?&lt;/h4&gt;&lt;p data-path-to-node=&quot;182&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;182&quot;&gt;10 pods ÷ 2 pods per node = 5 nodes&lt;/code&gt;. This is completely within the 10-node group ceiling, leaving 5 nodes available for other workloads in the tier. It fits safely.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;183&quot;&gt;J70. Why is scale-out slow relative to the bursts?&lt;/h4&gt;&lt;p data-path-to-node=&quot;184&quot;&gt;Spot instance capacity acquisition wait + node OS boot and cluster join + 726MB docker image download pull time (~48s) + startup probe warmup execution (up to 300s) ≈ 3–6 minutes total before a brand-new pod can actively serve traffic. This latency struggles against brief 5–8 minute traffic spikes.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;185&quot;&gt;J71. Which part of the fix gives immediate relief?&lt;/h4&gt;&lt;p data-path-to-node=&quot;186&quot;&gt;The vertical &lt;code data-index-in-node=&quot;13&quot; data-path-to-node=&quot;186&quot;&gt;workers=2&lt;/code&gt; change — every single existing pod immediately doubles its concurrent capacity for handling heavy requests. HPA horizontal scale-out serves as a slower second line of defense and may not even complete processing within a short burst window.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;187&quot;&gt;J72. Why bump the memory request to 5Gi if density is unchanged?&lt;/h4&gt;&lt;p data-path-to-node=&quot;188&quot;&gt;5Gi matches the actual steady-state memory utilization of the dual-worker process model, making the scheduler&#39;s reservation honest and avoiding over-packing followed by sudden runtime evictions. Density remains unchanged because the CPU request (1500m) already limits placement to 2 pods per node.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;190&quot;&gt;K. Staging-First Deploy &amp;amp; Verification&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;191&quot;&gt;K73. Why deploy to staging first when the staging manifest file wasn&#39;t changed?&lt;/h4&gt;&lt;p data-path-to-node=&quot;192&quot;&gt;The &lt;code data-index-in-node=&quot;4&quot; data-path-to-node=&quot;192&quot;&gt;workers=2&lt;/code&gt; code and environment configuration is baked directly into the Docker image artifact, which staging runs. Thus, deploying to staging smoke-tests the internal behavior of the new container version before hitting production — even though the staging infra manifest itself remains unchanged.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;193&quot;&gt;K74. What does staging validate, and what does it &lt;i data-index-in-node=&quot;50&quot; data-path-to-node=&quot;193&quot;&gt;not&lt;/i&gt; validate?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;194&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;194,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;194,0,0&quot;&gt;Validates:&lt;/b&gt; Image build/push mechanics, container runtime startup with multiple workers, startup probe caching execution, and basic API routing correctness (&lt;code data-index-in-node=&quot;156&quot; data-path-to-node=&quot;194,0,0&quot;&gt;/health&lt;/code&gt; and live requests).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;194,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;194,1,0&quot;&gt;Does NOT validate:&lt;/b&gt; Production resource sizing under load, HPA auto-scaling dynamics, or topology spread rules (as those are production-only manifest keys). Those configurations were verified via client-side dry-runs and theoretical capacity analysis.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;195&quot;&gt;K75. How is staging isolated, and what risk did we flag?&lt;/h4&gt;&lt;p data-path-to-node=&quot;196&quot;&gt;Staging runs as a distinct Deployment and Service (&lt;code data-index-in-node=&quot;51&quot; data-path-to-node=&quot;196&quot;&gt;core-service-api-staging&lt;/code&gt;) inside the &lt;code data-index-in-node=&quot;88&quot; data-path-to-node=&quot;196&quot;&gt;staging&lt;/code&gt; namespace on the same shared cluster. &lt;b data-index-in-node=&quot;134&quot; data-path-to-node=&quot;196&quot;&gt;Risk:&lt;/b&gt; Running &lt;code data-index-in-node=&quot;148&quot; data-path-to-node=&quot;196&quot;&gt;workers=2&lt;/code&gt; inside a tiny 0.5-core staging allocation means both workers are severely CPU-throttled. The startup warmup steps could slow down and exceed the 300s threshold, triggering an artifact crashloop that is a byproduct of staging constraints rather than an application defect.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;197&quot;&gt;K76. List the staging verification steps and pass criteria.&lt;/h4&gt;&lt;p data-path-to-node=&quot;198&quot;&gt;Pipeline checks pass green → Rollout completes successfully with zero container restarts → Verify process table via container exec (&lt;code data-index-in-node=&quot;132&quot; data-path-to-node=&quot;198&quot;&gt;1 master + 2 workers&lt;/code&gt;) → HTTP &lt;code data-index-in-node=&quot;161&quot; data-path-to-node=&quot;198&quot;&gt;GET /health&lt;/code&gt; returns 200 → Sample heavy request returns 200 with correct output payloads → Logs are clean. All must be green to pass.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;199&quot;&gt;K77. Why test both &lt;code data-index-in-node=&quot;19&quot; data-path-to-node=&quot;199&quot;&gt;/health&lt;/code&gt; and a real request route?&lt;/h4&gt;&lt;p data-path-to-node=&quot;200&quot;&gt;The &lt;code data-index-in-node=&quot;4&quot; data-path-to-node=&quot;200&quot;&gt;/health&lt;/code&gt; endpoint only verifies that the web server socket is open and listening (it is trivial and lightweight). Sending a real processing request confirms that the heavy data models have loaded correctly into active memory and that the application can execute computations under the new worker configuration.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;201&quot;&gt;K78. What does the warmup endpoint do, and why is it the likely failure point?&lt;/h4&gt;&lt;p data-path-to-node=&quot;202&quot;&gt;It builds and primes the heavy in-memory entity models for data extraction. It is the most intensive step during application boot and is gated by the startup probe; on a resource-starved container, this is the most likely location for a timeout failure to occur.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;204&quot;&gt;L. Production Rollout &amp;amp; Confirmation&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;205&quot;&gt;L79. Why a patch bump (&lt;code data-index-in-node=&quot;23&quot; data-path-to-node=&quot;205&quot;&gt;v1.14.4&lt;/code&gt;)?&lt;/h4&gt;&lt;p data-path-to-node=&quot;206&quot;&gt;It represents an operational configuration and infrastructure hotfix with absolutely no changes to the external API surface or core application behavior, following &lt;code data-index-in-node=&quot;164&quot; data-path-to-node=&quot;206&quot;&gt;v1.14.3&lt;/code&gt; — making it a patch version under semver guidelines.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;207&quot;&gt;L80. Two new pods went Pending — what happened next?&lt;/h4&gt;&lt;p data-path-to-node=&quot;208&quot;&gt;The larger 1500m CPU / 5Gi memory pods could not fit on the existing allocated nodes. A &lt;code data-index-in-node=&quot;88&quot; data-path-to-node=&quot;208&quot;&gt;TriggeredScaleUp&lt;/code&gt; event was successfully logged by the Cluster Autoscaler, a new node was provisioned and joined the cluster, and the pending pods scheduled onto it — behaving exactly as predicted in the capacity analysis.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;209&quot;&gt;L81. Why did the ~6-minute rollout drop no traffic?&lt;/h4&gt;&lt;p data-path-to-node=&quot;210&quot;&gt;A &lt;code data-index-in-node=&quot;2&quot; data-path-to-node=&quot;210&quot;&gt;RollingUpdate&lt;/code&gt; deployment replaces pods incrementally based on &lt;code data-index-in-node=&quot;64&quot; data-path-to-node=&quot;210&quot;&gt;maxSurge&lt;/code&gt; and &lt;code data-index-in-node=&quot;77&quot; data-path-to-node=&quot;210&quot;&gt;maxUnavailable&lt;/code&gt; definitions. Each new pod is required to completely pass its lengthy startup probe before an older pod is decommissioned out of the active routing pool, ensuring continuous handling capacity.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;211&quot;&gt;L82. Why only ~2.6Gi post-deploy, not ~5Gi?&lt;/h4&gt;&lt;p data-path-to-node=&quot;212&quot;&gt;Only the initial worker that processed the startup warmup request had fully populated its internal cache; the second worker process was still cold (having received no user traffic yet). Memory usage scales up toward ~5Gi as the second worker processes requests under load, which our sizing footprint anticipates.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;213&quot;&gt;L83. ~3s max requests persisted, yet the alarm stayed OK — why is that consistent with success?&lt;/h4&gt;&lt;p data-path-to-node=&quot;214&quot;&gt;The CloudWatch alarm tracks the mathematical &lt;i data-index-in-node=&quot;45&quot; data-path-to-node=&quot;214&quot;&gt;average&lt;/i&gt;. Individual 3-second maximum latencies are expected due to the variable nature of heavy data workloads, but because 2 workers × 4 pods are now absorbing the volume, the aggregate average stays low (0.1s - 0.3s). Tail latency maxes can persist while keeping the alarm green (confirming the point in Q7).&lt;/p&gt;&lt;h4 data-path-to-node=&quot;215&quot;&gt;L84. What&#39;s the honest gap in the 45-minute monitoring evidence?&lt;/h4&gt;&lt;p data-path-to-node=&quot;216&quot;&gt;The monitoring window captured only moderate off-peak traffic patterns, not a worst-case peak morning burst. The fix is not yet fully proven against maximum production volume; observing a morning traffic peak stay entirely under the latency threshold is required to definitively close out the incident ticket.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;217&quot;&gt;L85. HPA stayed at 4 pods — why is that good, not a non-event?&lt;/h4&gt;&lt;p data-path-to-node=&quot;218&quot;&gt;The vertical optimization change alone provided enough per-pod concurrency to keep individual utilization comfortably below the scaling threshold under the observed load. The horizontal scaling capacity is a safety cushion held in reserve, not a failure of the fix.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;220&quot;&gt;M. Operational &amp;amp; Process Gotchas&lt;/h2&gt;&lt;h4 data-path-to-node=&quot;221&quot;&gt;M86. Why does &lt;code data-index-in-node=&quot;14&quot; data-path-to-node=&quot;221&quot;&gt;sts get-caller-identity&lt;/code&gt; succeed while &lt;code data-index-in-node=&quot;52&quot; data-path-to-node=&quot;221&quot;&gt;kubectl&lt;/code&gt; times out?&lt;/h4&gt;&lt;p data-path-to-node=&quot;222&quot;&gt;The Kubernetes cluster control plane API endpoint is configured as strictly private. &lt;code data-index-in-node=&quot;85&quot; data-path-to-node=&quot;222&quot;&gt;kubectl&lt;/code&gt; requires a path through the internal VPN network to resolve and route traffic. The AWS STS command calls public AWS API endpoints, succeeding regardless of local network routing. &lt;b data-index-in-node=&quot;272&quot; data-path-to-node=&quot;222&quot;&gt;Takeaway:&lt;/b&gt; AWS SSO success combined with a &lt;code data-index-in-node=&quot;314&quot; data-path-to-node=&quot;222&quot;&gt;kubectl&lt;/code&gt; I/O timeout or context deadline error indicates a local network/VPN connectivity issue, not an authentication problem.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;223&quot;&gt;M87. Why did the deploy still work when local &lt;code data-index-in-node=&quot;46&quot; data-path-to-node=&quot;223&quot;&gt;kubectl&lt;/code&gt; couldn&#39;t?&lt;/h4&gt;&lt;p data-path-to-node=&quot;224&quot;&gt;The CI/CD pipeline&#39;s deployment step executes directly on a self-hosted runner instance stationed natively inside the project VPC, giving it a direct internal network path to the private cluster endpoint independent of local engineer connectivity.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;225&quot;&gt;M88. Diagnose the two CI failures — were they ours?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;226&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;226,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;226,0,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;226,0,0&quot;&gt;security-check&lt;/code&gt;:&lt;/b&gt; The internal package scanner attempted to run an interactive prompt setup in an environment with no standard input (&lt;code data-index-in-node=&quot;132&quot; data-path-to-node=&quot;226,0,0&quot;&gt;stdin&lt;/code&gt;), resulting in an &lt;code data-index-in-node=&quot;156&quot; data-path-to-node=&quot;226,0,0&quot;&gt;EOFError&lt;/code&gt; crash (a regression in the testing utility version).&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;226,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;226,1,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;226,1,0&quot;&gt;image-scan&lt;/code&gt;:&lt;/b&gt; The automated vulnerability scan flagged pre-existing CVE thresholds within upstream base dependencies.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-path-to-node=&quot;227&quot;&gt;Neither issue was introduced by our configuration change (the Dockerfile and application dependencies were completely untouched), and historical runs across unrelated branches confirmed identical global failures.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;228&quot;&gt;M89. &lt;code data-index-in-node=&quot;5&quot; data-path-to-node=&quot;228&quot;&gt;BLOCKED&lt;/code&gt; but branch protection returned a 404 — resolve the contradiction.&lt;/h4&gt;&lt;p data-path-to-node=&quot;229&quot;&gt;The repository does not use legacy branch protection rules (hence the API returning 404), but instead implements modern repository compliance rulesets. Rulesets represent a distinct platform feature and must be queried via specific ruleset endpoints. The merge block was enforced by an active ruleset requirement.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;230&quot;&gt;M90. What actually gated the merge?&lt;/h4&gt;&lt;p data-path-to-node=&quot;231&quot;&gt;A ruleset requirement mandating exactly 1 peer-approved review. The automated status checks were not configured as blocking gates within the ruleset, which allowed the branch to be merged despite the red CI scanner flags once a manual review was submitted.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;232&quot;&gt;M91. Which actions needed explicit confirmation, and why?&lt;/h4&gt;&lt;p data-path-to-node=&quot;233&quot;&gt;Any operation writing state changes to production infrastructure or mutating master source tracking (e.g., executing infrastructure applications, pushing release tags, merging to master, modifying tracking tickets). These actions are highly visible and difficult to cleanly reverse, necessitating a strict &quot;confirm-before-acting&quot; protocol.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;234&quot;&gt;M92. Commit-authorship convention here?&lt;/h4&gt;&lt;p data-path-to-node=&quot;235&quot;&gt;Commits must enforce verified corporate identity configurations using strict compliance controls, explicitly avoiding automated local environment placeholders or any explicit AI author attribution footers in either commit histories or pull request descriptions.&lt;/p&gt;&lt;h2 data-path-to-node=&quot;237&quot;&gt;N. Synthesis &amp;amp; Transfer&lt;/h2&gt;&lt;h3 data-path-to-node=&quot;238&quot;&gt;The Real Mastery Test&lt;/h3&gt;&lt;h4 data-path-to-node=&quot;239&quot;&gt;N93. Paged cold — first five moves, in order?&lt;/h4&gt;&lt;ol data-path-to-node=&quot;240&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;240,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,0,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,0,0&quot;&gt;describe-alarms&lt;/code&gt;&lt;/b&gt; → Check current metric state, threshold definitions, and exact configurations.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;240,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,1,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,1,0&quot;&gt;describe-alarm-history&lt;/code&gt;&lt;/b&gt; → Determine if the issue is a flapping pattern and correlate the start time against the cluster deployment log.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;240,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,2,0&quot;&gt;Break down &lt;code data-index-in-node=&quot;11&quot; data-path-to-node=&quot;240,2,0&quot;&gt;TargetResponseTime&lt;/code&gt; and &lt;code data-index-in-node=&quot;34&quot; data-path-to-node=&quot;240,2,0&quot;&gt;RequestCount&lt;/code&gt; by Target Group&lt;/b&gt; → Identify the specific failing service and check if the degradation is load-correlated.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;240,3,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,3,0&quot;&gt;&lt;code data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,3,0&quot;&gt;kubectl get/describe pods&lt;/code&gt; and &lt;code data-index-in-node=&quot;30&quot; data-path-to-node=&quot;240,3,0&quot;&gt;top&lt;/code&gt;&lt;/b&gt; → Check pod health, restart loops, resource tracking, and probe status.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;240,4,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;240,4,0&quot;&gt;Analyze Prometheus per-pod CPU over time series&lt;/b&gt; → Evaluate if workload distribution is balanced and look for single-core saturation plateaus.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p data-path-to-node=&quot;241&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;241&quot;&gt;Each progressive step narrows the operational scope: Alarm → Service → Pod → Process.&lt;/i&gt;&lt;/p&gt;&lt;h4 data-path-to-node=&quot;242&quot;&gt;N94. Name three plausible-but-wrong hypotheses and the one fact that kills each.&lt;/h4&gt;&lt;ol data-path-to-node=&quot;243&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;243,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;243,0,0&quot;&gt;Low-traffic statistical artifact:&lt;/b&gt; Contradicted by clear increases in total request volume coinciding exactly with the latency spikes.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;243,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;243,1,0&quot;&gt;Load balancer round-robin routing imbalance:&lt;/b&gt; Disproven by historical Prometheus time series showing even CPU utilization across all pods, alongside an &lt;code data-index-in-node=&quot;151&quot; data-path-to-node=&quot;243,1,0&quot;&gt;instance&lt;/code&gt;-mode infrastructure topology that prevents pod-level load steering.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;243,2,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;243,2,0&quot;&gt;The recent application deployment:&lt;/b&gt; Refuted by historical alarm logs demonstrating that the latency flapping pattern was active hours prior to the deployment timestamp.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;h4 data-path-to-node=&quot;244&quot;&gt;N95. If the CPU series &lt;i data-index-in-node=&quot;23&quot; data-path-to-node=&quot;244&quot;&gt;had&lt;/i&gt; shown one pinned pod, what&#39;s the real fix in instance mode?&lt;/h4&gt;&lt;p data-path-to-node=&quot;245&quot;&gt;Applying a load balancer annotation wouldn&#39;t work because an ALB operating in &lt;code data-index-in-node=&quot;78&quot; data-path-to-node=&quot;245&quot;&gt;instance&lt;/code&gt; mode cannot steer individual pod traffic behind a NodePort hop. The structural resolution would require changing the target mode configuration to &lt;code data-index-in-node=&quot;233&quot; data-path-to-node=&quot;245&quot;&gt;ip&lt;/code&gt; (paired with LOR) to give the ALB direct visibility into individual pods, or rectifying the balancing mechanics at the internal proxy layer. Most importantly, per-pod concurrency limits would need to be raised to prevent single long-running requests from starving concurrent traffic.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;246&quot;&gt;N96. When is the correct long-term fix an application change?&lt;/h4&gt;&lt;p data-path-to-node=&quot;247&quot;&gt;When the core performance bottleneck stems directly from the internal concurrency model of the runtime — such as running compute-intensive, synchronous operations inside a single asynchronous event loop thread. The robust architectural solution requires modifying the application code: transitioning to synchronous worker processes mapped to available cores, or offloading compute tasks onto background worker queues so the main request loop remains fully responsive. Infrastructure configuration tweaks merely act as mitigation.&lt;/p&gt;&lt;h4 data-path-to-node=&quot;248&quot;&gt;N97. Generalize: what makes &quot;more replicas behind round-robin&quot; fail, and what helps?&lt;/h4&gt;&lt;ul data-path-to-node=&quot;249&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;249,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;249,0,0&quot;&gt;The Failure Property:&lt;/b&gt; Significant variance in per-request computation times, coupled with a routing layer that distributes traffic based purely on request count without visibility into backend state, running on replicas with restricted internal concurrency. Adding replicas fails because the actual &lt;i data-index-in-node=&quot;299&quot; data-path-to-node=&quot;249,0,0&quot;&gt;load&lt;/i&gt; (not request count) is severely unbalanced, leaving individual instances susceptible to head-of-line blocking.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;249,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;249,1,0&quot;&gt;What Helps:&lt;/b&gt; Implementing load-aware routing metrics (e.g., Least Connections / Least Outstanding Requests), increasing true parallel processing capability within each discrete replica, or refactoring the code to handle heavy calculations asynchronously outside the primary request path. You must provide capacity where the work actually serializes.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h4 data-path-to-node=&quot;250&quot;&gt;N98. If the morning peak still trips it — next two levers, and how to choose?&lt;/h4&gt;&lt;ol data-path-to-node=&quot;251&quot; start=&quot;1&quot;&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;251,0,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;251,0,0&quot;&gt;Optimize Horizontal Scaling Responsiveness:&lt;/b&gt; Increase minimum running replicas ahead of time or adjust the auto-scaler to be more aggressive. Evaluate per-pod CPU limits, HPA state machine transitions, and raw request velocity during spikes to verify if additional pre-warmed instances can absorb the peak.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p data-path-to-node=&quot;251,1,0&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;251,1,0&quot;&gt;Refactor Application Concurrency:&lt;/b&gt; Rewrite the request handling layer to run synchronous workers or execute compute tasks via isolated worker pools. Check detailed profiling data to confirm if event loop starvation continues to limit per-pod throughput.&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p data-path-to-node=&quot;252&quot;&gt;&lt;i data-index-in-node=&quot;0&quot; data-path-to-node=&quot;252&quot;&gt;Choose based on whether the exhaustion is caused by total cluster capacity constraints (→ scale out) or localized thread blocking (→ refactor application).&lt;/i&gt;&lt;/p&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/8705070272795564561/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/8705070272795564561' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8705070272795564561'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8705070272795564561'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/06/devops-interview-questions-k8s-alb-alarm.html' title='DevOps Interview Questions - k8s ALB alarm '/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEjw19vD-ImCQt1_4ftfF_ZFp5uHbV_56GO0zN4QVM_NXNHwkjhqGELto39_kVb2vdmvmA8E9OFn3bRe99dCi6O3aBvdUqgZgC1yi6XZcnhfupz9Tg22AYwd9nLSZyqL2XYQVy8k30IlswdnCxGb5KJhv8fNe-O1Vuk1NfhGvFVPE2uix29_aiJTLBRGka0=s72-w640-h360-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-3529066834690095782</id><published>2026-06-25T13:36:56.911+01:00</published><updated>2026-06-25T13:40:55.976+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Elasticsearch"/><category scheme="http://www.blogger.com/atom/ns#" term="ELK"/><title type='text'>Elastic Fleet</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7qLkLic0epYIvdZbBJnuqSenEUW6phHFhy7NwIMPStBFpK32N1pDKR1uC3yadR5rHC2lM9Oa1FZ6mwWqa-7_nn_FOywTR8AWHWja8zLzC3zTZPwjW1-mVveRDYf6GkeFmeIcu3WUx7ShhHANRlw5QljhlF4996mm5p8tCFtiIkiMhquW59ixEcZfTUIw/s2400/elasticsearch-logo-png-transparent.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;2400&quot; data-original-width=&quot;2400&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7qLkLic0epYIvdZbBJnuqSenEUW6phHFhy7NwIMPStBFpK32N1pDKR1uC3yadR5rHC2lM9Oa1FZ6mwWqa-7_nn_FOywTR8AWHWja8zLzC3zTZPwjW1-mVveRDYf6GkeFmeIcu3WUx7ShhHANRlw5QljhlF4996mm5p8tCFtiIkiMhquW59ixEcZfTUIw/w640-h640/elasticsearch-logo-png-transparent.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Elastic Agents do not strictly require Fleet and Fleet Server. You can deploy them in standalone mode, which allows you to manually configure and manage them using local configuration files.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;However, running Elastic Agents in Fleet-managed mode with a Fleet Server is the recommended best practice for most enterprise environments.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Here is how the two approaches compare:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Fleet-Managed Mode (Recommended)&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;In this setup, you use the Fleet UI in Kibana to centrally manage agent policies, roll out upgrades, and apply integrations.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;How it works: Agents connect to a Fleet Server (which is just a specialized Elastic Agent process), which then receives policies from Elasticsearch and pushes them to your endpoints.&lt;/li&gt;&lt;li&gt;Best for: Large-scale deployments, continuous monitoring, and Elastic Security/Endpoint integrations.&lt;/li&gt;&lt;li&gt;Action: To set this up, refer to the official Elastic Agent Installation Guide.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Standalone Mode&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;In this setup, you manually install the agent and define its inputs, outputs, and integrations directly in a local YAML configuration file.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;How it works: The agent connects directly to outputs like Elasticsearch or Logstash without a Fleet Server intermediary.&lt;/li&gt;&lt;li&gt;Limitations: Central management, automated upgrades, and certain advanced Endpoint Security features are disabled.&lt;/li&gt;&lt;li&gt;Best for: Edge cases, highly air-gapped networks, or evaluating specific integrations.&lt;/li&gt;&lt;li&gt;Action: To configure this, use the steps outlined in the Standalone Elastic Agent Tutorial.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Example of how an Elastic Fleet can actually be wired&lt;/h1&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Components &amp;amp; where they live&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Everything is in cluster company-prod-elastic-eks (EKS, us-east-2, now on v1.36.2), namespace elastic-system, across 5 nodes in 3 AZs:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Node&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AZ&amp;nbsp; Node Group&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Runs&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;====&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ==&amp;nbsp; ==========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;====&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ip-10-99-44-1&amp;nbsp; &amp;nbsp;2a&amp;nbsp; es MNG (m7g.2xlarge)&amp;nbsp; &amp;nbsp;agent&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ip-10-99-55-34&amp;nbsp; 2b&amp;nbsp; default MNG (m5.large) agent + Fleet Server pod&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ip-10-99-55-89&amp;nbsp; 2b&amp;nbsp; es MNG (m7g.2xlarge)&amp;nbsp; &amp;nbsp;agent&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ip-10-99-66-51&amp;nbsp; 2c&amp;nbsp; es MNG (m7g.2xlarge)&amp;nbsp; &amp;nbsp;agent&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ip-10-99-66-78&amp;nbsp; 2c&amp;nbsp; default MNG (m5.large) agent&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Fleet Server — Deployment fleet-server-prod, replicas: 1 (single pod, currently on the 2b node). Listens on :8220 HTTPS. Exposed by a NodePort service fleet-server-prod-agent-http (8220 --&amp;gt; 32202 on every node).&lt;/li&gt;&lt;li&gt;Elastic Agents — DaemonSet agent-prod-eck-agent, one pod per node (5 total), spread across all 3 AZs. hostNetwork: true, mode: fleet, FLEET_INSECURE=true. They collect node/pod logs + metrics (hostPath mounts of /var/log/...).&lt;/li&gt;&lt;li&gt;ALB k8s-elastics-eckfleet-a1b2c3d4e5 — internal (not internet-facing), spanning subnets in 2a / 2b / 2c. Created by the AWS LB controller from Ingress eck-fleet-server-prod-ingress. Targets = all 5 nodes&#39; NodePort 32202 (instance mode), backend-protocol: HTTPS, idle_timeout: 300s.&lt;/li&gt;&lt;li&gt;DNS — eck-fleet-server.internal-domain.local (Route53 via external-dns) --&amp;gt;&amp;nbsp; the internal ALB. Elasticsearch is a separate endpoint, elasticsearch.internal-domain.local:443.&lt;/li&gt;&lt;/ul&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Who listens, who initiates&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The key thing: every connection is initiated by the agent (outbound). Nothing is ever pushed to the agents — that&#39;s why this works with agents behind hostNetwork and no inbound rules of their own.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: xx-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;┌───────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ cluster company-prod-elastic-eks / elastic-sys│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;┌──────────┐&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│ Agent&amp;nbsp; &amp;nbsp; │ check-in │&amp;nbsp; &amp;nbsp;internal ALB&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Fleet Server (1 pod)&amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│ DaemonSet│─────────►│&amp;nbsp; eckfleet-a1b2...&amp;nbsp; &amp;nbsp;┌──────► :8220 (HTTPS)&amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│ (5 pods, │&amp;nbsp; :443&amp;nbsp; &amp;nbsp; │&amp;nbsp; :443 TLS&amp;nbsp; ─────────┘&amp;nbsp; NodePort 32202→8220&amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; 3 AZs)&amp;nbsp; │&amp;nbsp; HTTPS&amp;nbsp; &amp;nbsp;│&amp;nbsp; (internal cert)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;kube-proxy → the 1 pod │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;└────┬─────┘&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;▲&amp;nbsp; re-encrypt HTTPS to a node&#39;s :32202&amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ ship data&amp;nbsp; &amp;nbsp; &amp;nbsp; └───────┼─────────────────────┼─────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │ (logs/metrics)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ DNS&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ writes .fleet-* / agent&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ▼&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;eck-fleet-server.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ▼ metadata, reads policy&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp;elasticsearch.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;internal-domain.local&amp;nbsp; Elasticsearch + Kibana (Fleet app)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp;internal-domain.local:443 ◄──────────────────────&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp;(NOT via the fleet ALB)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Traffic Flows: Breakdown&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Agent --&amp;gt; Fleet Server (Control Plane: Enrollment &amp;amp; Policy Check-in)&lt;/h4&gt;&lt;div&gt;The agent acts as the client, and Fleet Server listens on :8220. The agent dials https://eck-fleet-server.internal-domain.local:443:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Traffic Path:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;agent --&amp;gt; DNS --&amp;gt; internal ALB :443 (TLS termination, internal cert) --&amp;gt; re-encrypt --&amp;gt; a node&#39;s NodePort :32202 --&amp;gt;&amp;nbsp; kube-proxy --&amp;gt; the single Fleet Server pod :8220&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Cross-AZ Routing:&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Because the agent always targets the ALB DNS name (not the pod directly), an agent can land on any node&#39;s NodePort. It is then forwarded by kube-proxy to whichever node is hosting the single Fleet Server pod—making cross-AZ hops standard behavior.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Long Poll Mechanism: The check-in is a long poll. The agent opens a connection, and Fleet Server holds it open for up to ~5 minutes until a policy change occurs or the poll times out.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The Latency &quot;False Positive&quot;: This held-open duration is exactly what the ALB records as a TargetResponseTime of ~300s, which triggered the false-positive alert. The ALB&#39;s idle_timeout is intentionally set to 300s to support these long polls. Conversely, the health check (/health, expects 404, 300s interval) is a separate lightweight probe, explaining why targets report as perfectly healthy while &quot;latency&quot; metrics look highly skewed.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Agent --&amp;gt; Elasticsearch (Data Plane: Telemetry Shipping)&lt;/h4&gt;&lt;div&gt;This data path does not traverse the Fleet ALB. The Fleet output configuration points agents directly at https://elasticsearch.internal-domain.local:443.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Consequently, the eckfleet ALB only handles control-plane check-in traffic. This explains why its request volume is incredibly low (~3–5 requests/min) while individual request durations hover around 300s. They are two entirely separate paths utilizing two different load balancers.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Fleet Server --&amp;gt; Elasticsearch + Kibana&lt;/h4&gt;&lt;div&gt;Fleet Server (via elasticsearchRefs / kibanaRef) acts as the client here. It writes agent and policy metadata directly into the .fleet-* indices and coordinates with the Kibana Fleet application, where the eck-fleet-server and eck-agent policies are managed.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;One-Line Summary&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Agents (DaemonSet, 5 pods across 2a/2b/2c) long-poll the single Fleet Server pod for policy updates via the internal ALB on :443 --&amp;gt; NodePort 32202 --&amp;gt; :8220, but ship their actual telemetry data directly to Elasticsearch, completely bypassing that ALB. The ALB only processes the slow, held-open control traffic—which is the root cause of the&amp;nbsp; alarm.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Why does fleet server holds connection open for so long? Why it does not reply immediately?&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/h3&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;It&#39;s a deliberate design choice called long polling, and it&#39;s the opposite of a bug. Here&#39;s the reasoning.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The problem Fleet is solving&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fleet Server&#39;s job is to push policy changes out to agents promptly — a new integration, a changed log path, an output credential rotation. Agents need to find out &quot;has my policy changed?&quot; with low latency.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There are three ways an agent could learn about changes:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 1. Short polling — agent asks &quot;anything new?&quot; every N seconds, server replies instantly &quot;no&quot;, agent sleeps, repeats.&lt;/div&gt;&lt;div&gt;&amp;nbsp; 2. Server push — server opens a connection to each agent when something changes.&lt;/div&gt;&lt;div&gt;&amp;nbsp; 3. Long polling — agent asks &quot;anything new?&quot;, and the server holds the request open until either something actually changes or a timeout fires.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Fleet uses #3, and the connection sitting open for ~5 minutes is that hold.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Why not reply immediately (short polling)?&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If Fleet answered every check-in instantly with &quot;nothing changed&quot;, then to get fast reaction to a policy change, agents would have to poll very frequently — say every few seconds. With your 5 agents that&#39;s tolerable, but Fleet is built to manage thousands to tens of thousands of agents from one server. At that scale:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Frequent short polls = a constant storm of requests, almost all of which return &quot;no change&quot;. Huge wasted CPU/network on both ends.&lt;/div&gt;&lt;div&gt;&amp;nbsp; - To keep reaction time low you&#39;d poll more often, which makes the storm worse. To cut the storm you&#39;d poll less often, which makes policy changes take longer to land. There&#39;s no good setting.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Long polling breaks that trade-off: the agent gets a near-instant reaction to a real change (the server responds the moment policy updates) and there&#39;s almost no idle chatter (one held-open connection per agent instead of hundreds of empty round-trips).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Why not server push (#2)?&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Pushing would mean the server initiating connections inward to every agent. Agents are all over the place — behind NAT, firewalls, in private subnets, on laptops, on hostNetwork pods like yours. The server usually can&#39;t reach them, and you&#39;d need inbound rules everywhere. Long polling flips it: the agent always dials out to the server, the connection is already established and held open, and the server pushes the change down that existing agent-initiated connection the instant it happens. You get push-like latency with poll-like (outbound-only) connectivity. That&#39;s exactly why your agents work behind hostNetwork with no inbound exposure.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;&amp;nbsp;So what&#39;s actually happening in those ~300s&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A check-in is essentially: &quot;Here&#39;s my current state; tell me the moment my policy differs.&quot; The server parks that request. Two things can end it:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - A policy change occurs → server responds immediately with the new policy (could be 2 seconds in).&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Nothing changes → the server lets the request time out at its poll ceiling (~5 min), responds &quot;no change&quot;, and the agent immediately opens a fresh one.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Since your policies rarely change, almost every check-in runs the full clock and returns at ~300s. That&#39;s the held connection the ALB measures as TargetResponseTime.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Why this specifically fools the ALB&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;From the ALB&#39;s point of view, &quot;request received → response sent&quot; took 300 seconds, so it reports TargetResponseTime ≈ 300s. The ALB can&#39;t tell the difference between &quot;the backend was slow for 300s&quot; (bad) and &quot;the backend intentionally held an idle long-poll for 300s&quot; (normal). That ambiguity is the whole reason your generic 0.8s threshold misfires — and why the right fix (DOP-833) is to exempt this endpoint rather than treat it as latency. It&#39;s also why the Ingress sets idle_timeout.timeout_seconds=300: the ALB has to be told to tolerate the held connection, otherwise it would sever the long poll before the agent&#39;s poll cycle completes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In short: Fleet holds the connection open so it can deliver policy changes near-instantly without either hammering the server with empty polls or needing to reach inward to firewalled agents. The ~300s is just an idle long-poll waiting for a change that usually never comes during that window — efficient by design, and only a problem for a latency metric that doesn&#39;t know to expect it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;----&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/3529066834690095782/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/3529066834690095782' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3529066834690095782'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3529066834690095782'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/06/elastic-fleet.html' title='Elastic Fleet'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7qLkLic0epYIvdZbBJnuqSenEUW6phHFhy7NwIMPStBFpK32N1pDKR1uC3yadR5rHC2lM9Oa1FZ6mwWqa-7_nn_FOywTR8AWHWja8zLzC3zTZPwjW1-mVveRDYf6GkeFmeIcu3WUx7ShhHANRlw5QljhlF4996mm5p8tCFtiIkiMhquW59ixEcZfTUIw/s72-w640-h640-c/elasticsearch-logo-png-transparent.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-3983505050708614579</id><published>2026-06-25T13:11:38.955+01:00</published><updated>2026-06-25T13:11:38.955+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Load Balancing"/><title type='text'>AWS EC2: Application Load Balancer</title><content type='html'>&lt;p style=&quot;text-align: center;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSJSM-iOPqVmPdDzFbrSufE6r0RkCQc9H_Ikp-NWSKPyua4Yfnj--YE6bWACpd05qpEn4HeACR8OZZwQfuis529r4oKLZk-WSCTleHcEexFaskQedIAbeW-vOuWb50lGGx97E_vRvltlbN1FnN918TC86Ir5GY7dNGoAqAH_xEpbbvPBZtgR_FILSS/s310/alb_routing_1.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;262&quot; data-original-width=&quot;310&quot; height=&quot;338&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSJSM-iOPqVmPdDzFbrSufE6r0RkCQc9H_Ikp-NWSKPyua4Yfnj--YE6bWACpd05qpEn4HeACR8OZZwQfuis529r4oKLZk-WSCTleHcEexFaskQedIAbeW-vOuWb50lGGx97E_vRvltlbN1FnN918TC86Ir5GY7dNGoAqAH_xEpbbvPBZtgR_FILSS/w400-h338/alb_routing_1.png&quot; width=&quot;400&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;An &lt;b&gt;Application Load Balancer (ALB)&lt;/b&gt; is a fully managed AWS service that automatically distributes incoming HTTP and HTTPS traffic across multiple backend targets.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It operates at the Application Layer (Layer 7) of the Open Systems Interconnection (OSI) model.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Key Features:&lt;/h3&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Content-Based Routing: Routes traffic based on URL paths (/api vs /images) or hostnames (://example.com).&lt;/li&gt;&lt;li&gt;Container Support: Integrates directly with Amazon ECS and EKS using dynamic port mapping.&lt;/li&gt;&lt;li&gt;Advanced Protocols: Native support for modern protocols like HTTP/2, gRPC, and WebSockets.&lt;/li&gt;&lt;li&gt;Security Integration: Features built-in HTTPS/TLS termination and integrates directly with AWS WAF for web security.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How Components Work Together&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Listener: Evaluates connection requests from clients using protocols and ports you configure.&lt;/li&gt;&lt;li&gt;Rules: Determines how the load balancer routes requests to its registered targets.&lt;/li&gt;&lt;li&gt;Target Group: Groups backend resources (like EC2 instances, containers, or IP addresses) that receive the traffic&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How ALB health checks keep applications online?&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;An Application Load Balancer (ALB) keeps your application online by continuously monitoring the health of your backend targets and dynamically redirecting traffic away from failing nodes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Automatic Traffic Redirection&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The ALB sends periodic ping requests (health checks) to every registered target. If a target fails to respond correctly, the ALB marks it as unhealthy and immediately stops sending user traffic to it. Traffic is rerouted to the remaining healthy nodes with zero downtime for the user.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. Auto Scaling Integration&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When paired with an Auto Scaling Group (ASG), ALB health checks can trigger the automatic replacement of broken instances.&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The Problem: An EC2 instance might be running (healthy at the hardware level), but the web server inside it has crashed (unhealthy at the application level).&lt;/li&gt;&lt;li&gt;The Solution: The ALB tells the ASG that the instance is failing application health checks. The ASG terminates that specific broken instance and launches a fresh, working one.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Graceful Recovery&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When an unhealthy instance recovers, or when a new instance is launched, the ALB does not send traffic to it immediately. It enters an initial state and undergoes consecutive successful health checks. Only when it passes the threshold does the ALB safely introduce it back into the traffic rotation.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How to Configure an ALB Health Check&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You configure health checks inside the Target Group settings using these parameters:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Parameter&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; What it does&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Recommended Setting&lt;/b&gt;&lt;/div&gt;&lt;div&gt;========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=================&lt;/div&gt;&lt;div&gt;&lt;b&gt;Health Check Path&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;The URL endpoint the ALB hits&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; (e.g., /health or /index.html).&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; /health&lt;/div&gt;&lt;div&gt;&lt;b&gt;Healthy Threshold&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; Consecutive successes needed to mark a target as healthy.&amp;nbsp; &amp;nbsp; &amp;nbsp; 3&lt;/div&gt;&lt;div&gt;&lt;b&gt;Unhealthy Threshold&lt;/b&gt;&amp;nbsp; Consecutive failures needed to mark a target as unhealthy.&amp;nbsp; &amp;nbsp; &amp;nbsp;2&lt;/div&gt;&lt;div&gt;&lt;b&gt;Timeout&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; How long the ALB waits for a response before failing.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;5 seconds&lt;/div&gt;&lt;div&gt;&lt;b&gt;Interval&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;The time between individual health check pings.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 30 seconds&lt;/div&gt;&lt;div&gt;&lt;b&gt;Success Codes&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; The HTTP status codes that prove the app is working.&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 200 (or 200-399)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When you configure an ALB, you do not select an Availability Zone (AZ) directly; instead, you must select at least two subnets in different Availability Zones to ensure high availability.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;How it works: AWS places a &lt;u&gt;load balancer node&lt;/u&gt; in each of the specified subnets.&lt;/li&gt;&lt;li&gt;The AZ link: Because each subnet belongs to exactly one AZ, this fundamentally binds the ALB&#39;s nodes to those corresponding Availability Zones.&lt;/li&gt;&lt;li&gt;Custom routing: You can modify the subnets via the Update Availability Zones settings in the EC2 Console at any time.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Public ALB&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Binding an ALB to public subnets makes it a public (internet-facing) load balancer.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When you create an internet-facing ALB, AWS requires you to select public subnets so the ALB nodes can receive a public IP address and route traffic from the internet.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Key Characteristics:&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Public DNS: The ALB receives a public DNS name that resolves to public IP addresses.&lt;/li&gt;&lt;li&gt;Internet Gateway: The selected public subnets must have a route to an Internet Gateway (IGW) in their route tables.&lt;/li&gt;&lt;li&gt;Target Routing: Even though the ALB is public, it can still route traffic to EC2 instances living in private subnets&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;An internet-facing ALB routes traffic directly to the individual backend targets (such as EC2 instances or IP addresses), not to the private subnets themselves.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;How Routing Works&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Target Group Config: You configure the ALB to route traffic to a Target Group.&lt;/li&gt;&lt;li&gt;Direct Node Communication: The ALB nodes in the public subnets communicate directly with the private IP addresses of your backend nodes.&lt;/li&gt;&lt;li&gt;Cross-Subnet Traffic: AWS handles this routing internally via the VPC router, allowing the public ALB to securely traverse into private subnets.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Configuration Checklist&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;VPC: Both the public subnets (where the ALB lives) and the private subnets (where the nodes live) must be in the same VPC.&lt;/li&gt;&lt;li&gt;Security Groups: The private instances must have a security group that allows inbound traffic from the ALB&#39;s security group&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Private ALB&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;An internal (private) ALB routes traffic in the exact same way as a public ALB, but it is only accessible within your VPC or connected networks.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It routes traffic directly to individual backend targets, not to subnets.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Key Characteristics&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Private Subnets: You deploy the ALB nodes into private subnets.&lt;/li&gt;&lt;li&gt;Private DNS: The ALB receives a public DNS name, but it resolves exclusively to private IP addresses.&lt;/li&gt;&lt;li&gt;No Internet Access: It cannot receive any traffic from the public internet because it lacks a public IP.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Common Use Cases&lt;/h4&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Internal Microservices: Routing traffic from a public-facing web tier to a private backend API tier.&lt;/li&gt;&lt;li&gt;Hybrid Networks: Routing traffic coming from an on-premises data centre via AWS Direct Connect or a VPN&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Setting Up ALB in AWS Console&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://www.bojankomazec.com/2023/02/aws-elastic-load-balancing.html&quot;&gt;AWS Elastic Load Balancing&lt;/a&gt; shows basic building blocks of AWS Load Balancer which include listeners and target groups.&amp;nbsp;&lt;/p&gt;&lt;p&gt;To create &lt;b&gt;Application Load Balancer&lt;/b&gt; go to EC2 &amp;gt;&amp;gt; Load balancers &amp;gt;&amp;gt; Create Load balancer &amp;gt;&amp;gt; Select load balancer type (click on &lt;i&gt;Create&lt;/i&gt; under &lt;i&gt;Application Load Balancer&lt;/i&gt;)&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlNDq7UJdKgsJNVrw5qECJTe36lkKRM8dOcA6xTQLNO7Ta2iyAd7aKmeC5fQuh88ZM7BbdYrtjsf4unbhnx8k_P8hyLhV9XNnN4MEQCtjD9zk4p4EKCOv0LHIhCFT9sRMWzY8ivKTFRhhyk0GrZMu1bl8dYBzYddNZwR1Igzjs8ufu4zQDweH2_jXx/s1112/Screenshot%20from%202023-02-06%2014-16-55.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;639&quot; data-original-width=&quot;1112&quot; height=&quot;368&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhlNDq7UJdKgsJNVrw5qECJTe36lkKRM8dOcA6xTQLNO7Ta2iyAd7aKmeC5fQuh88ZM7BbdYrtjsf4unbhnx8k_P8hyLhV9XNnN4MEQCtjD9zk4p4EKCOv0LHIhCFT9sRMWzY8ivKTFRhhyk0GrZMu1bl8dYBzYddNZwR1Igzjs8ufu4zQDweH2_jXx/w640-h368/Screenshot%20from%202023-02-06%2014-16-55.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQgCIB2W0J79YmDrenBTv2au-Bmk3G4wOFg9-2zcBWDf4im4PQByajNqUI90uIn7ceMjPeQYMkkw1uLVbxS3AmSLBlXcC5uYoY-Swt-RQvewFvLzLHGuubzRTogRsOkEvf0q6Uy8I_BUgmaOleziF5FEKzxMuJJXB98TtCvbuKsvZgTeelYlXPoB5m/s1102/Screenshot%20from%202023-02-06%2014-16-34.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1013&quot; data-original-width=&quot;1102&quot; height=&quot;588&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQgCIB2W0J79YmDrenBTv2au-Bmk3G4wOFg9-2zcBWDf4im4PQByajNqUI90uIn7ceMjPeQYMkkw1uLVbxS3AmSLBlXcC5uYoY-Swt-RQvewFvLzLHGuubzRTogRsOkEvf0q6Uy8I_BUgmaOleziF5FEKzxMuJJXB98TtCvbuKsvZgTeelYlXPoB5m/w640-h588/Screenshot%20from%202023-02-06%2014-16-34.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;Here we can set:&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Basic configuration&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Name&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Scheme&lt;/b&gt; (cannot be changed after the load balancer is created)&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Internet-facing&lt;/b&gt;. An internet-facing load balancer routes requests from clients over the internet to targets. Requires a public subnet.&amp;nbsp;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Internal&lt;/b&gt;. An internal load balancer routes requests from clients to targets using private IP addresses. &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;IP address type&lt;/b&gt;. Select the type of IP addresses that your subnets use.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;IPv4&lt;/b&gt;. Recommended for internal load balancers.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Dualstack&lt;/b&gt;. Includes IPv4 and IPv6 addresses. &lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Network mapping&lt;/b&gt;. The load balancer routes traffic to targets in the selected subnets, and in accordance with your IP address settings.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;VPC&lt;/b&gt;. Virtual private cloud for your targets. If balancer is internet-facing, only VPCs with an &lt;b&gt;internet gateway&lt;/b&gt; are enabled for selection. The selected VPC cannot be changed after the load balancer is created. As VPC is &lt;u&gt;region-specific&lt;/u&gt; so is Application Load Balancer.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Mappings&lt;/b&gt;. Once VPC is selected, its &lt;u&gt;availability zones&lt;/u&gt; are listed here and are selectable. Select &lt;u&gt;at least two&lt;/u&gt; Availability Zones and one subnet per zone. The load balancer routes traffic to targets in these Availability Zones only. Availability Zones that are not supported by the load balancer or the VPC are not available for selection. We should select all AZs that we listed in the Auto scaling group (if we used it).&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Security groups&lt;/b&gt;. A security group is a set of firewall rules that control the traffic to your load balancer. We can select up to 10 security groups.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;If our application is listening for HTTP requests on port 80 we should select a security group with:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Inbound rule: accept HTTP/TCP traffic on port 80 with source Anywhere-IPv4&lt;/li&gt;&lt;li&gt;Outbound rule: allow all traffic for all protocols and port ranges to custom destination 0.0.0.0/0&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Listeners and routing&lt;/b&gt;. A listener is a process that checks for connection requests using the port and protocol you configure. The rules that you define for a listener determine how the load balancer routes requests to its registered targets.&lt;br /&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Add listener&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Protocol&lt;/b&gt; e.g. HTTP&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Port&lt;/b&gt; e.g. 80. This is a public facing port and it does not need to be the same as the port from the attached target group. E.g. LB can listen on port 80 and forward traffic to target group port 8080. &lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Default action&lt;/b&gt;: Forward to (select a &lt;b&gt;target group&lt;/b&gt;)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Add &lt;b&gt;listener tags&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Add-on services&lt;/b&gt; - optional&lt;/li&gt;&lt;ul&gt;&lt;li&gt;AWS Global Accelerator&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Tags&lt;/b&gt; - optional&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;More info on Scheme, from AWS documentation:&lt;/p&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;i&gt;When you create a load balancer, you must choose whether to make it an &lt;b&gt;internal&lt;/b&gt; load balancer or an &lt;b&gt;internet-facing&lt;/b&gt; load balancer.&lt;/i&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;i&gt;&lt;br /&gt;The &lt;b&gt;nodes&lt;/b&gt; of an &lt;u&gt;internet&lt;/u&gt;-facing load balancer have &lt;u&gt;public&lt;/u&gt; IP addresses.&lt;/i&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;i&gt;&lt;br /&gt;The &lt;b&gt;nodes&lt;/b&gt; of an &lt;u&gt;internal&lt;/u&gt; load balancer have only &lt;u&gt;private&lt;/u&gt; IP addresses.&lt;/i&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;i&gt;&lt;br /&gt;Both internet-facing and internal load balancers route requests to your targets using private IP addresses. Therefore, your targets do not need public IP addresses to receive requests from an internal or an internet-facing load balancer.&lt;/i&gt;&lt;/div&gt;&lt;/blockquote&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;/div&gt;&lt;p&gt;More info on how ALB routes traffic to multiple Availability Zones (and about what Load Balancer Nodes are):&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;i&gt;When you enable an Availability Zone for your load balancer, Elastic Load Balancing creates a &lt;b&gt;load balancer node&lt;/b&gt; in the Availability Zone.&amp;nbsp;&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;The nodes for your load balancer distribute requests from clients to registered targets. When cross-zone load balancing is enabled, each load balancer node distributes traffic across the registered targets in all enabled Availability Zones. When cross-zone load balancing is disabled, each load balancer node distributes traffic only across the registered targets in its Availability Zone.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;Before a client sends a request to your load balancer, it resolves the load balancer&#39;s domain name using a Domain Name System (DNS) server. The DNS entry is controlled by Amazon, because your load balancers are in the amazonaws.com domain. The Amazon DNS servers return one or more IP addresses to the client. These are the &lt;u&gt;IP addresses of the load balancer nodes for your load balancer&lt;/u&gt;.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;As traffic to your application changes over time, &lt;u&gt;Elastic Load Balancing scales your load balancer and updates the DNS entry&lt;/u&gt;. The DNS entry also specifies the time-to-live (TTL) of 60 seconds. This helps ensure that the &lt;u&gt;IP addresses can be remapped&lt;/u&gt; quickly in response to changing traffic.&lt;br /&gt;&lt;br /&gt;The client determines which IP address to use to send requests to the load balancer. &lt;u&gt;The load balancer node that receives the request&lt;/u&gt; selects a healthy registered target and sends the request to the target using its private IP address.&lt;/i&gt; &lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote&gt;&lt;p&gt;&lt;i&gt;With Application Load Balancers, the &lt;u&gt;load balancer node that receives the request&lt;/u&gt; uses the following process: &lt;br /&gt;&lt;/i&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;margin-left: 80px; text-align: left;&quot;&gt;&lt;p&gt;&lt;i&gt;1) Evaluates the &lt;b&gt;listener rules&lt;/b&gt; in priority order to determine which rule to apply. &lt;br /&gt;&lt;/i&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;blockquote style=&quot;margin-left: 80px; text-align: left;&quot;&gt;&lt;p&gt;&lt;i&gt;2) Selects a &lt;b&gt;target&lt;/b&gt; from the&lt;b&gt; target group&lt;/b&gt; for the rule action, using the routing algorithm configured for the target group. The default routing algorithm is round robin. Routing is performed independently for each target group, even when a target is registered with multiple target groups. &lt;/i&gt;&lt;br /&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;For further info: &lt;a href=&quot;https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html&quot;&gt;How Elastic Load Balancing works - Elastic Load Balancing&lt;/a&gt;&lt;/p&gt;&lt;p&gt;ALB nodes use &lt;b&gt;Elastic Network Interface&lt;/b&gt; (&lt;a href=&quot;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-eni.html&quot;&gt;Elastic network interfaces - Amazon Elastic Compute Cloud&lt;/a&gt;) which has public IP address:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;i&gt;At least one ENI is created and attached to the 
balancer in each availability zone where the balancer is deployed 
(except NLB, which should only have one per AZ). Over the life of the 
balancer, new ENIs will appear and old ones will disappear, as the 
balancer scales horizontally (number of nodes) and/or vertically 
(capacity of underlying hardware), all of which is handled transparently
 by the infrastructure. Even though you can tag them, the tagging will 
become stale over time.&lt;/i&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Source: &lt;a href=&quot;https://stackoverflow.com/questions/49365691/aws-affect-load-balancers-tags-to-its-network-interfaces-eni&quot;&gt;amazon web services - AWS - Affect Load Balancer&#39;s tags to its Network Interfaces (ENI) - Stack Overflow&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;&lt;i&gt;You can determine the IP addresses associated with an internal load balancer or an internet-facing load balancer by resolving the DNS name of the load balancer. These are the IP addresses where the clients should send the requests that are destined for the load balancer. However, Classic Load Balancers and &lt;u&gt;Application Load Balancers use the private IP addresses associated with their elastic network interfaces as the source IP address for requests forwarded to your web servers&lt;/u&gt;.&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Source: &lt;a href=&quot;https://aws.amazon.com/premiumsupport/knowledge-center/elb-find-load-balancer-IP/&quot;&gt;Find the IP address used by a load balancer to forward traffic to web servers&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Load balancer routes requests to the targets in a target group and performs health checks on the targets. Target group is accepting requests from the load balancer and forwards them to targets. These targets can be e.g. EC2 instances created either manually or through auto scaling group.&lt;br /&gt;&lt;/p&gt;&lt;p&gt;How to create a &lt;b&gt;Target Group &lt;/b&gt;used by Load Balancer listeners? (This applies for any type of Load Balancer)&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;EC2&lt;/i&gt; &amp;gt;&amp;gt; &lt;i&gt;Target groups&lt;/i&gt; &amp;gt;&amp;gt; &lt;i&gt;Create target group&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;Step 1: Specify group details &lt;br /&gt;&lt;/i&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMrpgIpclkp1DrT1T3-LVBLgZlB7yU1r8td-BsWF70W567usJSZMbeZnuLp6xHLF1nB-GCxJLZv-fqzW1sU-4SmvasIysatT4Kmp7GLL1-AhktdvCcpLlyGsqLE3hoIMryUVqeJZA0xjy8_QrW_a9EfgofsA6EvgbCd_CllcCCi22D7BJWpUYkesNJ/s1097/Screenshot%20from%202023-02-06%2015-52-20.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;997&quot; data-original-width=&quot;1097&quot; height=&quot;582&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMrpgIpclkp1DrT1T3-LVBLgZlB7yU1r8td-BsWF70W567usJSZMbeZnuLp6xHLF1nB-GCxJLZv-fqzW1sU-4SmvasIysatT4Kmp7GLL1-AhktdvCcpLlyGsqLE3hoIMryUVqeJZA0xjy8_QrW_a9EfgofsA6EvgbCd_CllcCCi22D7BJWpUYkesNJ/w640-h582/Screenshot%20from%202023-02-06%2015-52-20.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;Here we can set:&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;b&gt;Basic configuration&lt;/b&gt;. Settings in this section cannot be changed after the target group is created.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Target type&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Instances&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Supports load balancing to instances within a specific VPC.&lt;/li&gt;&lt;li&gt;Facilitates the use of Amazon EC2 Auto Scaling&amp;nbsp; to manage and scale your EC2 capacity.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;IP addresses&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Supports load balancing to VPC and on-premises resources.&lt;/li&gt;&lt;li&gt;Facilitates routing to multiple IP addresses and network interfaces on the same instance.&lt;/li&gt;&lt;li&gt;Offers flexibility with microservice based architectures, simplifying inter-application communication.&lt;/li&gt;&lt;li&gt;&amp;nbsp;Supports IPv6 targets, enabling end-to-end IPv6 communication, and IPv4-to-IPv6 NAT.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Lambda function&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Facilitates routing to a single Lambda function.&lt;/li&gt;&lt;li&gt;&amp;nbsp;Accessible to Application Load Balancers only.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Application Load Balancer&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Offers the flexibility for a Network Load Balancer to accept and route TCP requests within a specific VPC&lt;/li&gt;&lt;li&gt;Facilitates using static IP addresses and PrivateLink with an Application Load Balancer.&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Target group name&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Protocol:Port&lt;/b&gt; e.g. If our application is accepting HTTP requests on port 8080 this would be HTTP:8080&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;VPC&lt;/b&gt; - VPC with the instances that you want to include in the target group.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Protocol version&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;HTTP1&lt;/b&gt;. Send requests to targets using HTTP/1.1. Supported when the request protocol is HTTP/1.1 or HTTP/2.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;HTTP2&lt;/b&gt;. Send requests to targets using HTTP/2. Supported when the request protocol is HTTP/2 or gRPC, but gRPC-specific features are not available.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;gRPC&lt;/b&gt;. Send requests to targets using gRPC. Supported when the request protocol is gRPC.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Health checks&lt;/b&gt;. The associated load balancer periodically sends requests, per the settings below, to the registered targets to test their status.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Health check protocol&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;HTTP&lt;/li&gt;&lt;li&gt;HTTPS&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Health check path&lt;/b&gt;. Use the default path of “/“ to ping the root, or specify a custom path if preferred.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Advanced health check settings&lt;/b&gt;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Port&lt;/b&gt;. The port the load balancer uses when performing health checks on targets. The default is the port on which each target receives traffic from the load balancer, but you can specify a different port.&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Traffic port&lt;/li&gt;&lt;li&gt;Override&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Healthy threshold&lt;/b&gt;. The number of consecutive health checks successes required before considering an unhealthy target healthy.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Unhealthy threshold&lt;/b&gt;. The number of consecutive health check failures required before considering a target unhealthy.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Timeout&lt;/b&gt;. The amount of time, in seconds, during which no response means a failed health check.&lt;/li&gt;&lt;li&gt;&lt;b&gt;Interval&lt;/b&gt;. The approximate amount of time between health checks of an individual target&lt;/li&gt;&lt;li&gt;&lt;b&gt;Success codes&lt;/b&gt;. The HTTP codes to use when checking for a successful response from a target. You can specify multiple values (for example, &quot;200,202&quot;) or a range of values (for example, &quot;200-299&quot;).&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;li&gt;&lt;b&gt;Attributes&lt;/b&gt;&lt;/li&gt;&lt;li&gt;&lt;b&gt;Tags&lt;/b&gt; - optional &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;i&gt;Step 2: Register targets&lt;/i&gt;&lt;/p&gt;&lt;p&gt;This is an optional step to create a target group. However, to ensure that your load balancer routes traffic to this target group you must register your targets. &lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrSi-7Wrilmvw0PzggsgM1PKdgmc814YTMgfcMnpCxWUtb56nDCflPAz4LNq8Hlc6LnEH742vQyFO5Z0raTO8eJJPFJe3W0qLpWALR_Y7FMIGlFjad1T5kjENfaBFkiN3FtkrF-7FRMsiYSXDLoJXEX6ceu0h7r4tMVNUsX9keYxGIyQu4CttL-eKK/s1789/Screenshot%20from%202023-02-06%2016-58-17.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;947&quot; data-original-width=&quot;1789&quot; height=&quot;338&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjrSi-7Wrilmvw0PzggsgM1PKdgmc814YTMgfcMnpCxWUtb56nDCflPAz4LNq8Hlc6LnEH742vQyFO5Z0raTO8eJJPFJe3W0qLpWALR_Y7FMIGlFjad1T5kjENfaBFkiN3FtkrF-7FRMsiYSXDLoJXEX6ceu0h7r4tMVNUsX9keYxGIyQu4CttL-eKK/w640-h338/Screenshot%20from%202023-02-06%2016-58-17.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;After load balancer is created it takes several minutes while it&#39;s in &lt;i&gt;provisioning&lt;/i&gt; state and get into &lt;i&gt;active&lt;/i&gt; state. After this, we can use its DNS name in order to see what it&#39;s doing. &lt;br /&gt;&lt;/p&gt;&lt;p&gt;If we copy its DNS name and paste it to our browser, if we haven&#39;t registered any targets in the target group associated with the load balancer, we&#39;ll get error &lt;b&gt;503 - Service Temporary Unavailable&lt;/b&gt;.&lt;/p&gt;&lt;p&gt;If we&#39;ve registered targets and are getting error 504 Gateway time-out, we should check first if security groups (firewalls) for our EC2 instances (inbound rule - source IP range) are set up correctly as this error usually indicates that inbound traffic is not allowed. &lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;AWS Terraform provider offers provisioning all these resources:&lt;br /&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Application Load Balancer: &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb&quot;&gt;aws_lb | Resources | hashicorp/aws | Terraform Registry&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Listener: &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_listener&quot;&gt;aws_lb_listener | Resources | hashicorp/aws | Terraform Registry&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;li&gt;Target Group: &lt;a href=&quot;https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/lb_target_group&quot;&gt;aws_lb_target_group | Resources | hashicorp/aws | Terraform Registry&lt;/a&gt;&amp;nbsp;&lt;/li&gt;&lt;/ul&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;How is AWS Application Load Balancing usually implemented?&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Let&#39;s say we have our application running on 3 EC2 instances where 2 are in the same region e.g. us-west-2 but in separate availability zones e.g. us-west-2a and us-west-2b. Third EC2 instance is in eu-central-1, in availability zone eu-central-1a.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;VPC is region-specific but can span multiple availability zones (AZ).&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Subnet is an IP address range within VPC.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;VPC can have public and private subnets. &lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;VPC can be divided into multiple subnets but each subnet is AZ-specific.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;AZ can have multiple subnets.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;So, all EC2 instances belong to the same VPC but, as they are in different AZs, each of them belongs to different subnet.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Load balancer must be in the public subnet of VPC as clients communicate with load balancer via internet (public network). &lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Load balancer does not get associated directly with EC2 instances but subnets:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;aws_lb&quot; &quot;test&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;subnets = [&quot;subnet-0001&quot;, &quot;subnet-0002&quot;]&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; ...&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Target group is associated with VPC:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;resource &quot;aws_alb_target_group&quot; &quot;test&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;vpc_id&amp;nbsp;&amp;nbsp; = var.vpc_id&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Difference between ALB and NLB (Network Load Balancer)&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;An Application Load Balancer (ALB) and a Network Load Balancer (NLB) serve different purposes based on the layer of the network they operate on and the type of traffic they handle.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The core difference is that an ALB understands application-level traffic (Layer 7) like HTTP/HTTPS headers, while an NLB handles low-level network traffic (Layer 4) like TCP/UDP packets at extreme speeds.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;Direct Comparison Matrix&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Feature&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Application Load Balancer (ALB)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Network Load Balancer (NLB)&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;======&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=========================&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; =======================&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;OSI Layer&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Layer 7 (Application)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Layer 4 (Transport)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Protocols&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;HTTP, HTTPS, HTTP/2, gRPC, WebSockets&amp;nbsp; &amp;nbsp; &amp;nbsp; TCP, UDP, TLS&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;IP Addresses&lt;/b&gt;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &lt;/span&gt;Dynamic IPs (Changes automatically; requires a DNS name)&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Static IPs (Can assign an Elastic IP per AZ)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Routing Features&lt;/b&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Advanced (Path, Host, Query parameters, Headers)&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Basic (Port and IP protocol routing only)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Performance&lt;/b&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;Optimized for complex web apps (Millions of requests/sec)&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;nbsp;Optimized for ultra-low latency (Billions of requests/sec)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Key Technical Differences&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;1. Smart Routing vs. Raw Speed&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;ALB (Smart): Can read the contents of your HTTP requests. It can route traffic bound for ://example.com to an API server cluster, and traffic for ://example.com to a storage cluster.&lt;/li&gt;&lt;li&gt;NLB (Fast): Does not look inside the data packet. It simply looks at the target port and forwards the packet instantly. This results in ultra-low latency (measured in milliseconds).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;2. IP Addresses and DNS&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;ALB: Scale out dynamically by adding or removing nodes. This causes its underlying IP addresses to change frequently. You must always point your domain name to the ALB&#39;s DNS Name, never to a static IP.&lt;/li&gt;&lt;li&gt;NLB: Gives you a Static IP address per Availability Zone. You can also assign your own Elastic IP addresses. This is critical if your corporate clients need to whitelist specific, unchanging IPs in their firewalls.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4 style=&quot;text-align: left;&quot;&gt;3. Client IP Preservation&lt;/h4&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;ALB: Terminates the connection and makes a new one to your backend instances. The backend see the ALB&#39;s private IP. To find the real user&#39;s IP, your code must read the X-Forwarded-For HTTP header.&lt;/li&gt;&lt;li&gt;NLB: Passes the original TCP packet straight through to your backend server. Your backend instances see the original source IP address of the client natively, without needing extra headers.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;When to Choose Which?&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Choose an ALB if you are building:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Standard web applications and microservices.&lt;/li&gt;&lt;li&gt;Containerized apps (ECS/EKS) requiring path-based or host-based routing.&lt;/li&gt;&lt;li&gt;Applications requiring tight integration with AWS Web Application Firewall (WAF).&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Choose an NLB if you are building:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Non-HTTP applications (e.g., gaming servers, SFTP, MQTT, database clusters).&lt;/li&gt;&lt;li&gt;Architectures requiring fixed, static IP addresses or Elastic IPs.&lt;/li&gt;&lt;li&gt;High-frequency financial applications where sub-millisecond network latency is a hard requirement&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Which alerts should typically be set for AWS ALB?&lt;/h2&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;To keep your applications highly available, you should set up Amazon CloudWatch alarms for a mix of availability, performance, and target health metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;The most critical metrics to monitor for an AWS ALB are grouped by priority below:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;1. High Priority (Critical Infrastructure Impact)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;UnHealthyHostCount&lt;/span&gt; (Per Target Group)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What it means: The number of backend instances failing health checks Target Group Metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Alert Threshold: &amp;gt; 0 (or &amp;gt; 1 for larger clusters).&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why it matters: Signals that your servers are crashing or cannot handle traffic.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HTTPCode_Target_5XX_Count&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What it means: The number of 5xx server error codes generated by your backend application ALB Metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Alert Threshold: Depends on baseline traffic, typically &amp;gt; 5 failures within a 1-minute to 5-minute window.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why it matters: Indicates server crashes, database connection timeouts, or unhandled exceptions in your application code.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HTTPCode_ELB_5XX_Count&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What it means: The number of 5xx errors generated directly by the ALB itself (not your servers) ALB Metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Alert Threshold: &amp;gt; 0.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why it matters: Usually means the ALB cannot find any healthy hosts, or it is experiencing a configuration mismatch (e.g., bad TLS handshake with the target).&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;2. Medium Priority (Performance &amp;amp; User Experience)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;TargetResponseTime&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What it means: The time elapsed (in seconds) from when the ALB sent the request to the target until the target started responding ALB Metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Alert Threshold: Use the p95 or p99 statistic. Alert if it exceeds your application’s maximum acceptable latency (e.g., &amp;gt; 2.0 seconds).&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why it matters: Users are experiencing severe application slowdowns, likely due to high CPU/memory usage on your instances.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;RejectedConnectionCount&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What it means: The load balancer is rejecting connections because it has reached its maximum capacity ALB Metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Alert Threshold: &amp;gt; 0.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why it matters: Your application is getting sudden traffic spikes and the ALB cannot scale fast enough, or backend targets are failing to keep up.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;3. Low Priority (Anomalies &amp;amp; Security)&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;HTTPCode_Target_4XX_Count&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What it means: The number of 4xx client errors (like 404 Not Found or 401 Unauthorized) returned by backend targets ALB Metrics.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Alert Threshold: A significant spike above your standard baseline.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Why it matters: A sudden surge might indicate a broken frontend deployment, a bad API update, or a malicious entity scanning your network for vulnerabilities.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Summary Checklist for CloudWatch Alarms&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;Metric Name&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Statistic&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Recommended&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Suggested&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Action&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Period&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Threshold&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;==========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ======&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; ===========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;========&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;=====&lt;/b&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;UnHealthyHostCount&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Maximum&amp;nbsp; &amp;nbsp;1 Minute&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;gt; 0&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Page/On-Call&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;HTTPCode_ELB_5XX_Count&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Sum&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 Minute&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;gt; 0&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Page/On-Call&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;HTTPCode_Target_5XX_Count&lt;/b&gt;&amp;nbsp; Sum&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 5 Minutes&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;gt; 10 (or &amp;gt; 1% of traffic) Ticket/Slack&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;b&gt;TargetResponseTime&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;p95&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 5 Minutes&amp;gt; [Your Limit]&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Ticket/Slack&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Resources:&lt;/h1&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://docs.aws.amazon.com/elasticloadbalancing/latest/userguide/how-elastic-load-balancing-works.html&quot;&gt;How Elastic Load Balancing works - Elastic Load Balancing&lt;/a&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/3983505050708614579/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/3983505050708614579' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3983505050708614579'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3983505050708614579'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/06/aws-ec2-application-load-balancer.html' title='AWS EC2: Application Load Balancer'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiSJSM-iOPqVmPdDzFbrSufE6r0RkCQc9H_Ikp-NWSKPyua4Yfnj--YE6bWACpd05qpEn4HeACR8OZZwQfuis529r4oKLZk-WSCTleHcEexFaskQedIAbeW-vOuWb50lGGx97E_vRvltlbN1FnN918TC86Ir5GY7dNGoAqAH_xEpbbvPBZtgR_FILSS/s72-w400-h338-c/alb_routing_1.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-1233425363097768997</id><published>2026-06-03T11:20:16.303+01:00</published><updated>2026-06-03T11:25:38.957+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Agentic AI"/><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="LLM"/><title type='text'>Some notes on LLMs</title><content type='html'>&lt;p&gt;&amp;nbsp;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8A60re2nD4AwHHSi7LJHj4v0mh7ZeoLzo28cm_iEfN3RTIDA4OyuCTEQL5vs7MR3f4xqW4zxWdYkl-iVA3JoMPWwP4HMsqPseSrtxThyphenhyphenZv7s_4lToQLXv14sFA_kX8HPBNxRP5b0-oJnAMJFx1recQJBziBHM_q78eZUTHLf2rwgn_SzS4gF18TSAevE/s1190/Screenshot%202026-04-08%20at%2012.30.13.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;872&quot; data-original-width=&quot;1190&quot; height=&quot;468&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8A60re2nD4AwHHSi7LJHj4v0mh7ZeoLzo28cm_iEfN3RTIDA4OyuCTEQL5vs7MR3f4xqW4zxWdYkl-iVA3JoMPWwP4HMsqPseSrtxThyphenhyphenZv7s_4lToQLXv14sFA_kX8HPBNxRP5b0-oJnAMJFx1recQJBziBHM_q78eZUTHLf2rwgn_SzS4gF18TSAevE/w640-h468/Screenshot%202026-04-08%20at%2012.30.13.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/p&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;h3&gt;What does e.g.&amp;nbsp;&lt;i&gt;1M context&lt;/i&gt;&amp;nbsp;mean in a model description?&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&quot;1M context&quot; means the model can handle up to 1 million tokens in its context window — the combined input (your messages, files, tool results, system prompt) plus output it can consider at once. For reference, that&#39;s roughly 750,000 words or a few thousand pages of text, letting it work with very large codebases or long conversations without losing earlier content.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;What is a LLM&#39;s tool-use loop?&amp;nbsp;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;An LLM tool-use loop is the pattern where you let a language model drive an investigation by repeatedly choosing tools to call, rather than answering in one shot.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4&gt;The shape&lt;/h4&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 1. Send: system prompt + user request + list of available tools (with JSON schemas)&lt;/div&gt;&lt;div&gt;&amp;nbsp; 2. Model responds with either:&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(a) a final text answer&amp;nbsp; -&amp;gt; exit loop&lt;/div&gt;&lt;div&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;(b) a&amp;nbsp;&lt;b&gt;&quot;tool_use&quot; block&lt;/b&gt;: { name: &quot;run_aws_cli&quot;, input: { args: [...] } }&lt;/div&gt;&lt;div&gt;&amp;nbsp; 3. Your code executes that tool, captures the result&lt;/div&gt;&lt;div&gt;&amp;nbsp; 4. Append the tool result to the conversation as a &quot;tool_result&quot; message&lt;/div&gt;&lt;div&gt;&amp;nbsp; 5. Send the whole conversation back to the model&lt;/div&gt;&lt;div&gt;&amp;nbsp; 6. Goto 2&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The model never executes anything itself — it just emits requests to call tools. Your code is the runtime that actually runs them and feeds the output back.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4&gt;Why it&#39;s a loop&lt;/h4&gt;&lt;div&gt;Each turn the model sees everything it has learned so far (prior tool calls + their outputs) and decides the next step based on that. So a real run looks like:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Turn 1: model calls&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;cloudwatch describe-alarms --state-value ALARM&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Turn 2: sees 3 alarms, picks the noisiest, calls logs filter-log-events for that log group around the alarm time&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Turn 3: sees an error pattern, calls&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;kubectl describe pod&lt;/span&gt;&amp;nbsp;on the affected workload&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Turn 4: emits final Markdown report, no tool call → loop exits&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; The model is doing the planning; your code is the dispatcher.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4&gt;Why you need a budget&lt;/h4&gt;&lt;div&gt;Without limits the loop can spin forever — the model keeps finding &quot;one more thing to check.&quot; Hence in agent.run():&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - max_iterations=30 — hard cap on turns&lt;/div&gt;&lt;div&gt;&amp;nbsp; - max_tokens_per_turn=12288 — cap on a single response&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Per-tool wall-clock timeouts (60 s for CLI, 30 s for HTTP)&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Output truncation (50 000 char stdout) so a giant tool result doesn&#39;t blow the context window&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4&gt;How it ends&lt;/h4&gt;&lt;div&gt;The loop terminates when the model returns a response with no tool_use block — that&#39;s the &quot;I&#39;m done, here&#39;s the answer&quot; signal (stop_reason: end_turn). Or when you hit a budget limit and force-stop it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h4&gt;Where the safety lives&lt;/h4&gt;&lt;div&gt;Because the model can ask for arbitrary tool calls, the loop is only as safe as the tool implementations. That&#39;s why when implementing agents we should have the allowlists (services, verbs, paths) - the model can request&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;aws s3 rm&lt;/span&gt;, but the validator rejects it before subprocess.run ever sees it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The &quot;two-pass&quot; design in agent is a refinement: pass 1 is a tool-use loop (gather), pass 2 is a single non-loop call (synthesize). Splitting them lets each prompt focus on one job.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;What are those .md files used by AI Agents?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;There isn&#39;t a universally agreed official name, but people commonly refer to files like CLAUDE.md, GEMINI.md, AGENTS.md, COPILOT_INSTRUCTIONS.md, and .cursorrules as:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;AI agent instruction files (most generic)&lt;/li&gt;&lt;li&gt;Agent configuration files&lt;/li&gt;&lt;li&gt;Agent context files&lt;/li&gt;&lt;li&gt;LLM instruction files&lt;/li&gt;&lt;li&gt;Repository AI instructions&lt;/li&gt;&lt;li&gt;Project AI guidelines&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the developer tooling community, &lt;b&gt;&quot;agent instructions&quot;&lt;/b&gt; or &lt;b&gt;&quot;agent context files&quot;&lt;/b&gt; are probably the most widely understood umbrella terms.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Tool&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; File&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;----&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;-----&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Claude Code&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; CLAUDE.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Gemini CLI&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; GEMINI.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;GitHub Copilot&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; .github/copilot-instructions.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;OpenAI Codex CLI&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; AGENTS.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Cursor&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; .cursorrules / project rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;Windsurf&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;Rules files&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Collectively, you could describe them as:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&quot;Repository-level AI agent instruction files that provide &lt;u&gt;persistent context&lt;/u&gt; and &lt;u&gt;operating rules&lt;/u&gt; for coding assistants.&quot;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you&#39;re building tooling around them (e.g., in your DevOps work), I&#39;d recommend using &lt;b&gt;&quot;agent instructions&quot;&lt;/b&gt; as the generic term because it&#39;s vendor-neutral and easily understood across Claude, Gemini, Copilot, Cursor, Codex, and similar tools.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/1233425363097768997/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/1233425363097768997' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/1233425363097768997'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/1233425363097768997'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/06/some-notes-on-llms.html' title='Some notes on LLMs'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi8A60re2nD4AwHHSi7LJHj4v0mh7ZeoLzo28cm_iEfN3RTIDA4OyuCTEQL5vs7MR3f4xqW4zxWdYkl-iVA3JoMPWwP4HMsqPseSrtxThyphenhyphenZv7s_4lToQLXv14sFA_kX8HPBNxRP5b0-oJnAMJFx1recQJBziBHM_q78eZUTHLf2rwgn_SzS4gF18TSAevE/s72-w640-h468-c/Screenshot%202026-04-08%20at%2012.30.13.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-7392176680319035455</id><published>2026-06-03T11:19:38.085+01:00</published><updated>2026-06-03T12:17:21.065+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Agentic AI"/><category scheme="http://www.blogger.com/atom/ns#" term="Claude"/><category scheme="http://www.blogger.com/atom/ns#" term="Claude Code"/><title type='text'>Introduction to Claude by Anthropic</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC9dW0-zQI2mqq1qBJRsMkBhGn0O8xcS1fg4osrumfurtxCRS2e1zJwZvGQEe4oWpsIKMUuuaB94vqp1AhyphenhyphenqZIGRRItnTV1ZBaHfDTWV_t_vx-esOMA3McQez0Ij7hCVBKCBSnOHBSuVoQukHxyQyBf5yzU6zpqtZc8D79IU2hqX796qFPiKKZVIN1tHE/s1024/claude-color.png&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1024&quot; data-original-width=&quot;1024&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC9dW0-zQI2mqq1qBJRsMkBhGn0O8xcS1fg4osrumfurtxCRS2e1zJwZvGQEe4oWpsIKMUuuaB94vqp1AhyphenhyphenqZIGRRItnTV1ZBaHfDTWV_t_vx-esOMA3McQez0Ij7hCVBKCBSnOHBSuVoQukHxyQyBf5yzU6zpqtZc8D79IU2hqX796qFPiKKZVIN1tHE/w640-h640/claude-color.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/h3&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How to give Claude an instruction to apply label &quot;DevOps&quot; whenever it creates a new Linear ticket?&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;It depends on whether you&#39;re creating Linear tickets via:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Linear MCP,&lt;/li&gt;&lt;li&gt;a custom /create-ticket command,&lt;/li&gt;&lt;li&gt;or just asking Claude in chat to create tickets&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;The best location depends on which of those you&#39;re using.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you&#39;re just asking Claude in chat to create tickets: if you want this behavior for all projects, put it in:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;~/.claude/CLAUDE.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you want it only for a specific repository/project, put it in:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;lt;repo-root&amp;gt;/CLAUDE.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you want it only for yourself in a specific project (without committing it to git), put it in:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;lt;repo-root&amp;gt;/CLAUDE.local.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;CLAUDE.local.md&lt;/span&gt; is usually the best place for personal workflow preferences like Linear labels. Claude loads it after the project-level file, so your preference is read later and tends to have more influence.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;## Linear Ticket Creation&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;When creating new Linear issues:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Always apply the &quot;DevOps&quot; label.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- If multiple labels are appropriate, include &quot;DevOps&quot; in addition to the others.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Verify the label exists before creating the issue.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you&#39;re creating tickets through a custom slash command (e.g.&lt;span style=&quot;font-family: courier;&quot;&gt; /create-ticket&lt;/span&gt;) or a Linear MCP workflow, an even more reliable approach is to put the instruction directly in the command definition under:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;~/.claude/commands/create-ticket.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;or in a dedicated skill, because the instruction is then attached specifically to the ticket-creation workflow rather than relying on general memory. Many Claude Code users find &lt;u&gt;workflow-specific commands more reliable than large global CLAUDE.md files for things like Linear automation&lt;/u&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How to instruct Claude to use feature branch name from a Linear ticket when implementing it?&lt;/h2&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Add a specific workflow rule to CLAUDE.md rather than a vague instruction. Claude follows procedural instructions much more reliably.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;## Linear Ticket Workflow&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;When implementing work from a Linear ticket:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;1. Retrieve the ticket details from Linear.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;2. Check whether the ticket has a suggested branch name.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;3. Before making any code changes, switch to or create a git branch using the Linear branch name.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;4. Do not create an alternative branch name unless the Linear branch name is unavailable.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;5. If the branch already exists remotely, check it out instead of creating a new branch.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;6. Confirm the branch name being used before starting implementation.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Example:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Linear ticket: DEV-123 Add deployment health checks&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Branch: bojan/dev-123-add-deployment-health-checks&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Commands:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;git fetch origin&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;git checkout bojan/dev-123-add-deployment-health-checks || \&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;git checkout -b bojan/dev-123-add-deployment-health-checks&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you&#39;re using the &lt;b&gt;Linear MCP server&lt;/b&gt;, I&#39;d make it even more explicit:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;## Branch Naming&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;For any task originating from Linear:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Always obtain the branch name from the Linear ticket metadata.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Never invent a branch name if Linear provides one.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Use the Linear-provided branch name exactly as written.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Checkout the branch before planning or implementing changes.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;An even better approach is to create a dedicated command such as &lt;span style=&quot;font-family: courier;&quot;&gt;.claude/commands/implement-ticket.md&lt;/span&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;Given a Linear ticket ID:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;1. Load the ticket.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;2. Read the ticket&#39;s branch name.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;3. Checkout the branch.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;4. Implement the work.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;5. Commit using the ticket ID in the commit message.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Workflow-specific commands tend to be more reliable than relying on global instructions in CLAUDE.md, especially for things like branch creation, ticket labeling, and PR naming.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;If I update ~/.claude/CLAUDE.md, do I need to start the new Claude session?&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Usually no restart is strictly required, but it depends on how you’re using Claude:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;If you’re using Claude Code / CLI (most common case)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;~/.claude/CLAUDE.md&lt;/span&gt; is typically read when a new session starts&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So:&lt;/div&gt;&lt;div&gt;✅ New terminal session / new Claude run → picks up changes&lt;/div&gt;&lt;div&gt;⚠️ Already-running interactive session → may not reliably reload it&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Best practice: restart the Claude session (or re-open the CLI/chat) after changing it if you want to be sure the new rules apply.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;If you’re using a long-running agent session&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;(e.g. an ongoing “Claude is working in the repo” process)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It often caches the instructions in memory at session start&lt;/div&gt;&lt;div&gt;So updates won’t consistently apply mid-task&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;If you’re using repo-level CLAUDE.md&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Same rule: loaded at session start or project initialization&lt;/div&gt;&lt;div&gt;Changes apply reliably on next session or re-attach to repo&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Practical rule of thumb&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Changed CLAUDE.md → start a new Claude session to guarantee it applies&lt;/div&gt;&lt;div&gt;Minor edits while actively working → assume they won’t fully apply until restart&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How to add auto-generated instructions to global context?&lt;/h2&gt;&lt;div&gt;During Claude Code session, it created two memories but they were bound to the current project only:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;Write(/Users/bojan/.claude/projects/-Users-bojan-repos-Company-Project/memory/gh-token-unset.md)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; ⎿&amp;nbsp; Wrote 10 lines to /Users/bojan/.claude/projects/-Users-bojan-repos-&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;Company-Project&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;/memory/gh-token-unset.md&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 ---&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2 name: gh-token-unset&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3 description: gh CLI fails with HTTP 401 Bad credentials unless GH_TOKEN is unset first&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;4 metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;5&amp;nbsp; &amp;nbsp;node_type: memory&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;6&amp;nbsp; &amp;nbsp;type: reference&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;7&amp;nbsp; &amp;nbsp;originSessionId: 9xxx6&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;8 ---&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;9&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 10 In this environment `gh` fails with `HTTP 401: Bad credentials` because a stale `GH_TOKEN` env var overrides the working keyring auth. Run `unset GH_TOKEN` in the same Bash command before any `gh`&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; call — the keyring account (`BojanKomazec`, scopes gist/read:org/repo) then works. Env doesn&#39;t persist between Bash calls, so include the `unset` each time.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;⏺ Write(/Users/bojan/.claude/projects/-Users-bojan-repos-&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;Company-Project&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;/memory/precommit-hook-needs-no-verify.md)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; ⎿&amp;nbsp; Wrote 10 lines to /Users/bojan/.claude/projects/-Users-bojan-repos-&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;Company-Project&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;/memory/precommit-hook-needs-no-verify.md&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;1 ---&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;2 name: precommit-hook-needs-no-verify&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;3 description: git commit aborts in non-interactive shell; the pre-commit hook prompts on /dev/tty&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;4 metadata:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;5&amp;nbsp; &amp;nbsp;node_type: memory&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;6&amp;nbsp; &amp;nbsp;type: reference&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;7&amp;nbsp; &amp;nbsp;originSessionId: 9xxx6&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;8 ---&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;9&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 10 The global pre-commit hook (`~/.git-templates/hooks/pre-commit`) prompts interactively via `/dev/tty` (&quot;view diff? proceed with commit?&quot;). In a non-interactive shell it errors `/dev/tty: Device no&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;t configured` and aborts the commit. Use `git commit --no-verify` to bypass it when committing programmatically.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;⏺ Write(/Users/bojan/.claude/projects/-Users-bojan-repos-&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;Company-Project&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;/memory/MEMORY.md)&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d;&quot;&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; ⎿&amp;nbsp; Wrote 2 lines to /Users/bojan/.claude/projects/-Users-bojan-repos-&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;Company-Project&lt;/span&gt;&lt;span style=&quot;font-family: courier; font-size: xx-small;&quot;&gt;/memory/MEMORY.md&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 1 - [gh token unset](gh-token-unset.md) — `unset GH_TOKEN` before any `gh` command or it 401s&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: xx-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; 2 - [pre-commit hook needs --no-verify](precommit-hook-needs-no-verify.md) — commit hook prompts on /dev/tty, aborts non-interactively&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;What you’re looking at in:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;~/.claude/projects/.../memory/MEMORY.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;is project-scoped learned memory (auto-generated runtime notes), not the best place to define global rules.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;To make something apply to all projects, you should put it in:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;~/.claude/CLAUDE.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;How to structure it properly&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Think of&lt;span style=&quot;font-family: courier;&quot;&gt; ~/.claude/CLAUDE.md&lt;/span&gt; as your global “agent operating system”.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You can directly migrate those &lt;span style=&quot;font-family: courier;&quot;&gt;MEMORY.md&lt;/span&gt; rules into it like this:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;## Global DevOps / CLI Behavior Rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;These rules apply to all projects unless explicitly overridden.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;### GitHub CLI&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Always run `unset GH_TOKEN` before executing any `gh` command.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Reason: GH_TOKEN causes 401 errors in local CLI authentication.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;### Git commits&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- When committing changes, always use `--no-verify`.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Reason: pre-commit hooks may attempt interactive prompts via /dev/tty and break non-interactive runs.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;Important distinction (this is the key part)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;File&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Scope&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Who writes it&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;Purpose&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;----&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -----&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -------------&amp;nbsp; &amp;nbsp; --------&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;~/.claude/CLAUDE.md&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp;Global (all projects)&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;You&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Your permanent rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;&amp;lt;repo&amp;gt;/CLAUDE.md&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;Per project&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; You/team&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; Repo-specific rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;~/.claude/projects/.../MEMORY.md&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;Auto-generated&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;Claude&lt;span style=&quot;white-space: pre;&quot;&gt;	&lt;/span&gt;&amp;nbsp; &amp;nbsp; Learned behavior, not authoritative&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;👉 Never manually “promote” &lt;span style=&quot;font-family: courier;&quot;&gt;MEMORY.md&lt;/span&gt; as your source of truth&lt;/div&gt;&lt;div&gt;Instead, copy stable patterns into&lt;span style=&quot;font-family: courier;&quot;&gt; ~/.claude/CLAUDE.md&lt;/span&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;For your Linear branch-name requirement&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You’d add it here too:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;## Linear Workflow (Global)&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;When working from Linear tickets:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Always use the branch name provided by Linear metadata.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Never generate custom branch names.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;- Always checkout the Linear branch before implementation.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;One extra improvement (worth doing)&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you want this to be really robust, structure your global file like:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;~/.claude/CLAUDE.md&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;├── Git / GitHub rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;├── Linear workflow rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;├── Shell safety rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;├── CI/CD rules&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Claude responds better to clear domains than long flat lists.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/7392176680319035455/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/7392176680319035455' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7392176680319035455'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/7392176680319035455'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/06/introduction-to-claude-by-anthropic.html' title='Introduction to Claude by Anthropic'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiC9dW0-zQI2mqq1qBJRsMkBhGn0O8xcS1fg4osrumfurtxCRS2e1zJwZvGQEe4oWpsIKMUuuaB94vqp1AhyphenhyphenqZIGRRItnTV1ZBaHfDTWV_t_vx-esOMA3McQez0Ij7hCVBKCBSnOHBSuVoQukHxyQyBf5yzU6zpqtZc8D79IU2hqX796qFPiKKZVIN1tHE/s72-w640-h640-c/claude-color.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-3669883960912657603</id><published>2026-05-14T15:36:44.566+01:00</published><updated>2026-05-14T15:36:44.566+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Checkly"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Monitoring"/><title type='text'>Introduction to Checkly</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;How Checkly works&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Checkly is a SaaS synthetic monitoring platform — you define &quot;checks&quot; (HTTP requests or browser scripts), Checkly runs them on a schedule from probe locations around the world (or on-demand from CI), records latency/assertions/screenshots, and alerts you when they fail or get slow.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; Two main check types:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;API checks&lt;/b&gt; — a single HTTP request with assertions on status, headers, body, response time.&lt;/div&gt;&lt;div&gt;&amp;nbsp; - &lt;b&gt;Browser checks&lt;/b&gt; — a Playwright script run in a real &lt;a href=&quot;https://www.bojankomazec.com/2026/05/intro-to-qa-with-headless-browsers.html&quot; rel=&quot;nofollow&quot; target=&quot;_blank&quot;&gt;headless&lt;/a&gt; Chromium against your deployed app.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;There&#39;s also &lt;b&gt;multi-step API checks&lt;/b&gt; (chain requests, e.g. login → use token → logout) and &lt;b&gt;heartbeat checks&lt;/b&gt; (your job pings Checkly; alert if it stops).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;i&gt;Hearbeat vs Ping&amp;nbsp;&lt;/i&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;i&gt;&lt;b&gt;Heartbeats&lt;/b&gt; and &lt;b&gt;pings&lt;/b&gt; are both vital network failure-detection mechanisms, but they differ in purpose: &lt;u&gt;Heartbeats are proactive, periodic &quot;I am alive&quot; messages sent by an application to signal it is healthy&lt;/u&gt;, while &lt;u&gt;Pings are reactive requests to check if a server is reachable&lt;/u&gt;. &lt;u&gt;Heartbeats detect &lt;b&gt;application crashes&lt;/b&gt;&lt;/u&gt;&lt;b&gt;,&lt;/b&gt; &lt;u&gt;while pings detect &lt;b&gt;network downtime&lt;/b&gt;&lt;/u&gt;.&lt;/i&gt;&lt;/blockquote&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Checks are typically authored as code (Checkly CLI, TypeScript) and checkly deploy&#39;d to the cloud. You can tag them (tags: [&quot;auth&quot;]), parametrise them with env vars like ENVIRONMENT_URL, and trigger them on-demand from CI — which is exactly what this PR does with npx checkly trigger --tags=auth.&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; Runtime model:&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Scheduled: every N minutes from chosen regions (e.g. us-east-2, eu-west-1) — catches regressions/outages between deploys.&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Triggered from CI: post-deploy smoke test, results gate (or just annotate) the deploy.&lt;/div&gt;&lt;div&gt;&amp;nbsp; - Alerts: Slack/PagerDuty/email on failure, with retry/degraded thresholds to avoid flap.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; ---&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; What it would check for this auth API&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; Given the auth API&#39;s surface (login, OAuth, JWT issuance, admin endpoints), realistic auth-tagged checks:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 1. Health endpoint — basic liveness&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-health&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – health&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; frequency: 1, // minute&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; locations: [&quot;us-east-2&quot;, &quot;eu-west-1&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/health`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;GET&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.statusCode().equals(200),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.responseTime().lessThan(500),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.jsonBody(&quot;$.status&quot;).equals(&quot;ok&quot;),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 2. Login flow — happy path, returns a JWT&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-login&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – login returns JWT&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/auth/login`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;POST&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; headers: [{ key: &quot;Content-Type&quot;, value: &quot;application/json&quot; }],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; body: JSON.stringify({&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; email: process.env.SYNTHETIC_USER_EMAIL,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; password: process.env.SYNTHETIC_USER_PASSWORD,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; }),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.statusCode().equals(200),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.responseTime().lessThan(1500),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.jsonBody(&quot;$.token&quot;).isNotNull(),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // structural check on JWT shape&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.jsonBody(&quot;$.token&quot;).matches(&quot;^eyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$&quot;),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 3. Login — wrong password returns 401 (negative path)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; Catches the &quot;accidentally accepts anything&quot; class of regression.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-login-bad-pw&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – wrong password = 401&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/auth/login`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;POST&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; headers: [{ key: &quot;Content-Type&quot;, value: &quot;application/json&quot; }],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; body: JSON.stringify({ email: process.env.SYNTHETIC_USER_EMAIL, password: &quot;wrong&quot; }),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [AssertionBuilder.statusCode().equals(401)],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 4. Multi-step — login then call protected endpoint&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; This is the most useful kind for an auth API, because it proves the token actually works.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new MultiStepCheck(&quot;auth-token-roundtrip&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – token works against /me&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; code: { entrypoint: path.join(__dirname, &quot;token-roundtrip.spec.ts&quot;) },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; // token-roundtrip.spec.ts&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; import { test, expect } from &quot;@playwright/test&quot;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; test(&quot;login then /me&quot;, async ({ request }) =&amp;gt; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const login = await request.post(`${process.env.ENVIRONMENT_URL}/auth/login`, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; data: { email: process.env.SYNTHETIC_USER_EMAIL, password: process.env.SYNTHETIC_USER_PASSWORD },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; expect(login.ok()).toBeTruthy();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const { token } = await login.json();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const me = await request.get(`${process.env.ENVIRONMENT_URL}/me`, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; headers: { Authorization: `Bearer ${token}` },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; expect(me.status()).toBe(200);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const body = await me.json();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; expect(body.email).toBe(process.env.SYNTHETIC_USER_EMAIL);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 5. TLS &amp;amp; cert expiry&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; A pure config check — useful because cert rotation is a classic outage cause.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-tls&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – TLS cert valid &amp;gt; 14d&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/health`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;GET&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [AssertionBuilder.statusCode().equals(200)],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-size: x-small;&quot;&gt;// Checkly surfaces cert expiry on the run; you set a threshold per check&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 6. Browser check — full login UX&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; expect(login.ok()).toBeTruthy();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const { token } = await login.json();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const { token } = await login.json();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const me = await request.get(`${process.env.ENVIRONMENT_URL}/me`, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; headers: { Authorization: `Bearer ${token}` },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; expect(me.status()).toBe(200);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; const body = await me.json();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; expect(body.email).toBe(process.env.SYNTHETIC_USER_EMAIL);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 5. TLS &amp;amp; cert expiry&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; A pure config check — useful because cert rotation is a classic outage cause.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-tls&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – TLS cert valid &amp;gt; 14d&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/health`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;GET&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [AssertionBuilder.statusCode().equals(200)],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-size: x-small;&quot;&gt;// Checkly surfaces cert expiry on the run; you set a threshold per check&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 5. TLS &amp;amp; cert expiry&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; A pure config check — useful because cert rotation is a classic outage cause.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-tls&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – TLS cert valid &amp;gt; 14d&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/health`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;GET&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [AssertionBuilder.statusCode().equals(200)],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-size: x-small;&quot;&gt;// Checkly surfaces cert expiry on the run; you set a threshold per check&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-tls&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – TLS cert valid &amp;gt; 14d&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/health`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;GET&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [AssertionBuilder.statusCode().equals(200)],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &lt;span style=&quot;font-size: x-small;&quot;&gt;// Checkly surfaces cert expiry on the run; you set a threshold per check&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 6. Browser check — full login UX&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; Runs against the front-end but exercises the auth API end-to-end including redirects, cookies, CSRF.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new BrowserCheck(&quot;auth-ui-login&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Login UI works&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; code: { entrypoint: path.join(__dirname, &quot;login.spec.ts&quot;) },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; import { test, expect } from &quot;@playwright/test&quot;;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; test(&quot;user can sign in&quot;, async ({ page }) =&amp;gt; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; await page.goto(process.env.ENVIRONMENT_URL!);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; await page.getByLabel(&quot;Email&quot;).fill(process.env.SYNTHETIC_USER_EMAIL!);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; await page.getByLabel(&quot;Password&quot;).fill(process.env.SYNTHETIC_USER_PASSWORD!);&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; await page.getByRole(&quot;button&quot;, { name: &quot;Sign in&quot; }).click();&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; await expect(page.getByText(&quot;Dashboard&quot;)).toBeVisible({ timeout: 10_000 });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; 7. OAuth callback reachability&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp; Doesn&#39;t fully exercise the Google/Microsoft flow (those need real consent), but checks the callback&lt;/div&gt;&lt;div&gt;&amp;nbsp; endpoint responds correctly to a missing-code request — confirms route + handler are wired.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; new ApiCheck(&quot;auth-oauth-google-callback-shape&quot;, {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; name: &quot;Auth API – Google OAuth callback exists&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; tags: [&quot;auth&quot;],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; request: {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; url: `${process.env.ENVIRONMENT_URL}/auth/google/callback`,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; method: &quot;GET&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; assertions: [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; // 400 for missing `code`, not 404/500 — proves handler is mounted&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; AssertionBuilder.statusCode().equals(400),&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ],&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; },&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; });&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/3669883960912657603/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/3669883960912657603' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3669883960912657603'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/3669883960912657603'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/05/introduction-to-checkly.html' title='Introduction to Checkly'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-1418997202847760203</id><published>2026-05-14T11:14:54.403+01:00</published><updated>2026-05-14T11:14:54.403+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="Browser"/><category scheme="http://www.blogger.com/atom/ns#" term="Frontend"/><category scheme="http://www.blogger.com/atom/ns#" term="QA"/><title type='text'>Intro to QA with Headless Browsers</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;Headless browsers are used in QA to execute automated browser tests faster and more efficiently by eliminating the graphical user interface (GUI). Because &lt;u&gt;they don&#39;t render visuals&lt;/u&gt;, they &lt;u&gt;consume fewer resources&lt;/u&gt;, enabling rapid, parallel testing in CI/CD pipelines, making them ideal for high-volume functional and regression testing.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Key Reasons for Using Headless Browsers in QA:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Faster Execution: Without the need to render CSS, images, or layout, tests run significantly faster.&lt;/li&gt;&lt;li&gt;CI/CD Integration: They are ideal for server-side environments where a GUI is unavailable, allowing automated tests to run after every code commit.&lt;/li&gt;&lt;li&gt;Lower Resource Usage: They consume significantly less RAM and CPU, allowing for higher parallelization (running many tests simultaneously) without overloading hardware.&lt;/li&gt;&lt;li&gt;Automated Functional Testing: They can accurately simulate user actions such as clicking buttons, submitting forms, and navigating pages.&lt;/li&gt;&lt;li&gt;Regression Testing: Due to speed and efficiency, they are perfect for running large suites of regression tests to ensure new changes haven&#39;t broken existing functionality.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Common tools for headless testing include headless Chrome, Firefox, Puppeteer, and Playwright&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;Headless browsers &lt;u&gt;parse, compile, and execute the exact same underlying code as standard browsers, but they skip the final step of painting pixels to a physical screen&lt;/u&gt;.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;What Headless Browsers Still Do&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Construct the DOM: They parse HTML into a full Document Object Model tree.&lt;/li&gt;&lt;li&gt;Apply Styling: They process CSS and calculate layout, element positions, and visibility.&lt;/li&gt;&lt;li&gt;Execute JavaScript: They run a full JS engine (like V8 in Chrome) to handle AJAX, animations, and frontend logic.&lt;/li&gt;&lt;li&gt;Manage Network Traffic: They make real HTTP requests, download cookies, and handle API responses.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;How QA Verifies Visuals Without a Display&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Layout Queries: Code checks if elements are present, hidden, or overlapping by querying their coordinates.&lt;/li&gt;&lt;li&gt;Computed Styles: Scripts verify specific CSS properties, like checking if a button color is exactly rgb(0, 0, 255).&lt;/li&gt;&lt;li&gt;Virtual Screenshots: The browser renders the page into an in-memory buffer, allowing QA tools to save PNGs or perform pixel-by-pixel visual regression comparisons.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;To help tailor using headless browser to our workflow, we need to know:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Which testing framework we are using (e.g., Playwright, Selenium, Cypress)?&lt;/li&gt;&lt;li&gt;Are we trying to catch functional bugs or visual layout glitches?&lt;/li&gt;&lt;li&gt;Do our tests run on a local machine or a CI/CD server (e.g., GitHub Actions, Jenkins)?&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;---&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/1418997202847760203/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/1418997202847760203' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/1418997202847760203'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/1418997202847760203'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/05/intro-to-qa-with-headless-browsers.html' title='Intro to QA with Headless Browsers'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-8038873605182059656</id><published>2026-04-29T14:24:23.579+01:00</published><updated>2026-04-29T14:24:23.580+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS"/><category scheme="http://www.blogger.com/atom/ns#" term="AWS SNS"/><category scheme="http://www.blogger.com/atom/ns#" term="AWS SQS"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><title type='text'>Introduction to Amazon Simple Notification Service (SNS)</title><content type='html'>&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8SqiQ4Whxspu4ooNEx8jANQ_T4iKjjFWyut0EJ4vbDcAz7oaka7xcDt9hKzdbwWJ8R6ZcRHF6whEWTEaW8Trwnkdr8NJwL4nm3riQdOtM-ngVDmIXkaMZZYVgH0xKUjxg1KSQsGnJSWwLWoIXvLQoUTlDsWfmm6uPvrXxt9IsWwQ2kxLyNmVbX0NLOdM/s1990/Screenshot%202024-08-05%20at%2014.27.37.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;914&quot; data-original-width=&quot;1990&quot; height=&quot;294&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8SqiQ4Whxspu4ooNEx8jANQ_T4iKjjFWyut0EJ4vbDcAz7oaka7xcDt9hKzdbwWJ8R6ZcRHF6whEWTEaW8Trwnkdr8NJwL4nm3riQdOtM-ngVDmIXkaMZZYVgH0xKUjxg1KSQsGnJSWwLWoIXvLQoUTlDsWfmm6uPvrXxt9IsWwQ2kxLyNmVbX0NLOdM/w640-h294/Screenshot%202024-08-05%20at%2014.27.37.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Amazon Simple Notification Service (SNS) is a fully managed messaging service that enables you to decouple microservices, distributed systems, and serverless applications. Here&#39;s how SNS works:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Key Concepts&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Topics&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A topic is a logical access point and communication channel. Publishers send messages to a topic, and subscribers receive these messages by subscribing to the topic.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Publishers&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Publishers are entities that send messages to an SNS topic. They could be applications, services, or even other AWS services like Lambda or CloudWatch.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Subscribers&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Subscribers are endpoints that receive messages from an SNS topic. These can include &lt;u&gt;Amazon SQS queues,&lt;/u&gt; AWS Lambda functions, HTTP/S endpoints, email addresses, and SMS numbers.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Messages&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Messages are the payload sent by publishers to SNS topics. They can include a variety of data formats, typically JSON.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;How It Works&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Creating a Topic:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;First, you create a topic using the AWS Management Console, AWS CLI, or AWS SDKs. This &lt;u&gt;topic acts as a communication channel.&lt;/u&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Subscribing to a Topic:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You then subscribe one or more endpoints to the topic. These endpoints can be other AWS services or external services capable of receiving notifications.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When subscribing, you specify the protocol (such as HTTP, SQS, Lambda, etc.) and the endpoint (like the URL or ARN of the SQS queue).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Publishing a Message:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Publishers send messages to the SNS topic using the Publish API. The message can include a subject, a message body, and optional attributes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SNS stores multiple copies of the message for redundancy and high availability.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Message Delivery:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SNS distributes the message to all subscribed endpoints.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Each endpoint processes the message according to its protocol. For example, an HTTP endpoint receives a POST request with the message content, and an SQS queue receives the message as a new queue entry.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Use Cases&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Fan-out Scenarios:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;When a message published to an SNS topic needs to be sent to multiple endpoints, SNS acts as a fan-out service. For example, updating various microservices or notifying multiple systems about an event.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Push Notifications:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SNS can be used to send push notifications to mobile devices through services like Amazon Device Messaging (ADM), Apple Push Notification Service (APNS), and Google Cloud Messaging (GCM).&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Application Alerts:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sending notifications or alerts to administrators or users via SMS, email, or other endpoints.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Event-Driven Computing:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Integrating with AWS Lambda to trigger functions in response to incoming messages, enabling event-driven processing.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Benefits&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Scalability:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SNS is designed to handle high-throughput and large volumes of messages, making it suitable for large-scale applications.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Flexibility:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;It supports multiple protocols, allowing for versatile messaging architectures.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Reliability:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;With built-in redundancy and fault tolerance, SNS ensures reliable message delivery.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Ease of Use:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The service is fully managed, reducing the operational overhead associated with maintaining messaging infrastructure.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Security&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Access Control:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Using AWS IAM policies, you can control who can publish or subscribe to topics.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Encryption&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SNS supports server-side encryption (SSE) for messages at rest and SSL/TLS for messages in transit.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Compliance&lt;/b&gt;:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;SNS is compliant with various industry standards and regulations, ensuring it meets security and compliance requirements.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Amazon SNS is a powerful and flexible tool for building loosely coupled, scalable, and robust messaging solutions in the cloud.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;How does Amazon SNS differ from Amazon SQS?&lt;/h1&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Amazon SNS (Simple Notification Service) and Amazon SQS (Simple Queue Service) are both messaging services provided by AWS, but they serve different purposes and have distinct features. Here&#39;s a comparison highlighting their differences:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Purpose and Use Cases&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Publish/Subscribe Messaging:&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;SNS is designed for &lt;u&gt;sending notifications or messages to multiple subscribers&lt;/u&gt;.&lt;/li&gt;&lt;li&gt;It&#39;s ideal for &lt;u&gt;broadcasting messages&lt;/u&gt; to multiple endpoints, such as sending alerts, notifications, or updates to different systems or users.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Use Cases:&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Real-time notifications&lt;/li&gt;&lt;li&gt;push notifications&lt;/li&gt;&lt;li&gt;fan-out scenarios (where a message needs to be sent to multiple recipients simultaneously)&lt;/li&gt;&lt;li&gt;application alerts&lt;/li&gt;&lt;li&gt;event-driven architectures&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Message Queuing:&amp;nbsp;&lt;/li&gt;&lt;ul&gt;&lt;li&gt;SQS is designed for decoupling and scaling distributed systems.&amp;nbsp;&lt;/li&gt;&lt;li&gt;It allows you to send, store, and receive messages between software components at any volume, without losing messages or requiring other services to be available.&lt;/li&gt;&lt;/ul&gt;&lt;li&gt;Use Cases:&lt;/li&gt;&lt;ul&gt;&lt;li&gt;Task queues&lt;/li&gt;&lt;li&gt;asynchronous processing&lt;/li&gt;&lt;li&gt;decoupling microservices&lt;/li&gt;&lt;li&gt;job dispatching&lt;/li&gt;&lt;li&gt;buffering messages between producer and consumer systems&lt;/li&gt;&lt;/ul&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Messaging Patterns&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Push-Based: SNS pushes messages to subscribers. Subscribers can be other AWS services (like Lambda, SQS), HTTP/S endpoints, email addresses, SMS numbers, and mobile push notifications.&lt;/li&gt;&lt;li&gt;Fan-Out: One message can be sent to multiple subscribers.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Pull-Based: Consumers pull messages from the queue. A consumer explicitly retrieves messages from the queue.&lt;/li&gt;&lt;li&gt;Point-to-Point: Each message is delivered to and processed by one consumer.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Message Handling&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Real-Time Delivery: Messages are delivered immediately to all subscribers.&lt;/li&gt;&lt;li&gt;No Message Persistence: Messages are not stored after delivery; if a subscriber is unavailable, the message is lost unless it&#39;s sent to an SQS queue or some other durable store.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Message Persistence: Messages are stored in the queue until they are processed and deleted by a consumer, or until they expire.&lt;/li&gt;&lt;li&gt;Delivery Guarantees: Ensures at least once delivery. With FIFO queues, SQS provides exactly-once processing and message ordering.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Scalability and Performance&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Scalable: Designed to handle massive numbers of messages and deliver them to large numbers of subscribers.&lt;/li&gt;&lt;li&gt;Latency: Typically has very low latency for message delivery.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Scalable: Automatically scales to handle large volumes of messages. Suitable for high-throughput applications.&lt;/li&gt;&lt;li&gt;Latency: Slightly higher latency compared to SNS due to the nature of pull-based consumption.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Features and Capabilities&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Multiple Protocols: Supports multiple delivery protocols including HTTP/S, email, SMS, SQS, Lambda, and mobile push notifications.&lt;/li&gt;&lt;li&gt;Filtering: Allows message filtering, enabling subscribers to receive only the messages that match their filter policies.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Visibility Timeout: Temporarily hides a message from other consumers while it is being processed.&lt;/li&gt;&lt;li&gt;Dead-Letter Queues (DLQ): Allows you to handle messages that can&#39;t be processed successfully.&lt;/li&gt;&lt;li&gt;FIFO Queues: Ensures the order of messages and exactly-once processing.&lt;/li&gt;&lt;li&gt;Delay Queues: Postpones the delivery of new messages to consumers for a specified amount of time.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Pricing&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Pricing Model: Based on the number of requests (publishes, deliveries, and notifications) and data transfer.&lt;/li&gt;&lt;li&gt;Cost Efficiency: More cost-effective for scenarios requiring a high number of subscribers and real-time notifications.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Pricing Model: Based on the number of requests (send, receive, delete) and data transfer.&lt;/li&gt;&lt;li&gt;Cost Efficiency: More cost-effective for decoupling microservices and scenarios requiring message persistence and complex message handling.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Integration and Interoperability&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SNS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Integration: Easily integrates with a wide range of AWS services (e.g., Lambda, SQS, HTTP/S endpoints, etc.).&lt;/li&gt;&lt;li&gt;Interoperability: Often used in conjunction with SQS for fan-out scenarios where messages need to be processed asynchronously and stored reliably.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;Amazon SQS:&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Integration: Commonly used to decouple systems and provide reliable message delivery. Often used with other AWS services like Lambda, ECS, and EC2.&lt;/li&gt;&lt;li&gt;Interoperability: Can be subscribed to SNS topics to receive messages that need persistent storage or further processing.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Summary&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In summary, Amazon SNS is a pub/sub messaging service optimized for real-time notifications and broadcasting messages to multiple subscribers, while Amazon SQS is a message queuing service designed for decoupling distributed systems and ensuring reliable message delivery through persistence and processing guarantees. They are often used together to build scalable, resilient, and flexible messaging architectures in AWS.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;a href=&quot;https://aws.amazon.com/sns/&quot;&gt;Push Notification Service - Amazon Simple Notification Service - AWS&lt;/a&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/8038873605182059656/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/8038873605182059656' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8038873605182059656'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8038873605182059656'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/04/introduction-to-amazon-simple.html' title='Introduction to Amazon Simple Notification Service (SNS)'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj8SqiQ4Whxspu4ooNEx8jANQ_T4iKjjFWyut0EJ4vbDcAz7oaka7xcDt9hKzdbwWJ8R6ZcRHF6whEWTEaW8Trwnkdr8NJwL4nm3riQdOtM-ngVDmIXkaMZZYVgH0xKUjxg1KSQsGnJSWwLWoIXvLQoUTlDsWfmm6uPvrXxt9IsWwQ2kxLyNmVbX0NLOdM/s72-w640-h294-c/Screenshot%202024-08-05%20at%2014.27.37.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4912226416400969049.post-8196912125955159361</id><published>2026-04-21T13:07:00.002+01:00</published><updated>2026-04-21T13:07:15.131+01:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AWS EKS"/><category scheme="http://www.blogger.com/atom/ns#" term="DevOps"/><category scheme="http://www.blogger.com/atom/ns#" term="Kubernetes"/><category scheme="http://www.blogger.com/atom/ns#" term="Terraform"/><title type='text'>Provisioning AWS EKS Cluster with terraform-aws-modules/eks/aws</title><content type='html'>&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpL3UGIwOtt3orHP_dEAx8jM5SFevZLk2BnC6GGcWxyhk7xlnNCeo1oLfWDQsVP21h-OlZamMah3YrDbtKPzUdctuUQx5HXUVZaVCV3p9Ou2BqnQshV0K8zeYt1pHjQCCRrL4QjTTiYRr7NGpV-6wJha2TNZGsSB448SNqosT_i8o2hLKd0wTc3B5OyZA/s728/aws-eks_terraform-1.png&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;728&quot; data-original-width=&quot;350&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpL3UGIwOtt3orHP_dEAx8jM5SFevZLk2BnC6GGcWxyhk7xlnNCeo1oLfWDQsVP21h-OlZamMah3YrDbtKPzUdctuUQx5HXUVZaVCV3p9Ou2BqnQshV0K8zeYt1pHjQCCRrL4QjTTiYRr7NGpV-6wJha2TNZGsSB448SNqosT_i8o2hLKd0wTc3B5OyZA/w308-h640/aws-eks_terraform-1.png&quot; width=&quot;308&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://github.com/terraform-aws-modules/terraform-aws-eks&quot;&gt;terraform-aws-modules/terraform-aws-eks: Terraform module to create Amazon Elastic Kubernetes (EKS) resources&lt;/a&gt;&amp;nbsp;is a popular Terraform module for provisioning AWS EKS cluster.&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;In this article we want to explore and breakdown its key components and their purposes.&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;We&#39;d typically use this module like here:&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;module &quot;eks&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; source&amp;nbsp; = &quot;terraform-aws-modules/eks/aws&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; version = &quot;21.15.1&quot;&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Let&#39;s explore this module&#39;s attributes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;1. Cluster Configuration&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;name&lt;/span&gt;,&amp;nbsp;&amp;nbsp;&lt;span style=&quot;font-family: courier;&quot;&gt;version&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;Sets the name and Kubernetes version for the EKS cluster. Use local and variable values for flexibility.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;endpoint_public_access&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;Set public (Internet) access to the Kubernetes API endpoint (via &lt;span style=&quot;font-family: courier;&quot;&gt;kubectl&lt;/span&gt;). Disable it for enhanced security.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;endpoint_private_access&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;Set private access to the API endpoint, whether only resources within the VPC can access it. If enabled, it is only reachable from within the VPC (Virtual Private Cloud) where your EKS cluster is deployed. There are few ways to access it:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;/div&gt;&lt;blockquote&gt;&lt;div&gt;How to Access the Kubernetes API from VPC&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;1. Use a Bastion Host or EC2 Instance in the VPC&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Launch an EC2 instance (bastion host or jump box) in a subnet within the same VPC as your EKS cluster.&lt;/div&gt;&lt;div&gt;SSH into this instance, and from there, use kubectl to access the cluster.&lt;/div&gt;&lt;div&gt;Alternatively, use SSH port forwarding or a VPN to proxy kubectl commands from your local machine through the bastion.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;2. Use AWS Systems Manager (SSM) Session Manager&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If your EC2 instances have the SSM agent and the necessary IAM permissions, you can use AWS SSM Session Manager to start a shell session on an instance in the VPC, then run kubectl from there.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;3. Use a VPN Connection&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Set up a VPN (such as AWS Client VPN or OpenVPN, or Site-to-site VPN for office LAN) that connects your local network to the VPC. Once connected, your local machine will be able to reach the private endpoint.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;4. Use AWS PrivateLink (Interface VPC Endpoints)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For advanced scenarios, you can use AWS PrivateLink to expose the Kubernetes API endpoint privately to other VPCs or on-premises networks.&lt;/div&gt;&lt;/blockquote&gt;&lt;div&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;enable_cluster_creator_admin_permissions&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If enabled, grants admin permissions to the user who creates the cluster.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;2. Logging and Add-ons&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;enabled_log_types&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;Enables logging for various Kubernetes components (API, audit, authenticator, controllerManager, scheduler) for monitoring and troubleshooting.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; enabled_log_types = [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;api&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;audit&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;authenticator&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;controllerManager&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;scheduler&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; ]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;addons&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;A dictionary-type attribute which installs and configures essential Kubernetes add-ons. Dictionary keys are addon names like:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;coredns&lt;/li&gt;&lt;li&gt;kube-proxy&lt;/li&gt;&lt;li&gt;aws-ebs-csi-driver&lt;/li&gt;&lt;li&gt;vpc-cni&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Dictionary values are objects which attributes are:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;most_recent&lt;/span&gt; - to set using the latest version (set it to &lt;span style=&quot;font-family: courier;&quot;&gt;false&lt;/span&gt; for version pinning)&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;version&lt;/span&gt; - addon version (use it for version pinning)&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;before_compute&lt;/span&gt; - set it to true if addon should be installed and set before nodes (compute layer)&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;service_account_role_arn&lt;/span&gt; - to configure addon with IAM roles for service accounts, enabling secure integration with AWS services.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;addons = {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; vpc-cni = {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;most_recent&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = false&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;version&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;v1.21.1-eksbuild.7&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;before_compute&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;= true&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;service_account_role_arn&lt;/b&gt; = module.k8s_default_vpc_cni_irsa.iam_role_arn&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier; font-size: x-small;&quot;&gt;&amp;nbsp; &amp;nbsp; }&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;VPC CNI (Container networking interface) is responsible for allocating IP addresses to the Kubernetes nodes and provides networking to pods. The plugin manage network interfaces (ENIs) on the nodes and uses it to assign IP addresses to pods.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;3. Networking&lt;/h2&gt;&lt;div&gt;We need to integrate the EKS cluster with existing VPC and subnets:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;vpc_id&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;VPC ID&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;subnet_ids&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;Subnets in which nodes (EC2 instances) will be created.&lt;/div&gt;&lt;div&gt;Where your worker nodes (EC2 instances) run.&lt;br /&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;&lt;b&gt;control_plane_subnet_ids&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;Where the EKS control plane ENIs (network interfaces) are placed&lt;/div&gt;&lt;div&gt;Defines where the EKS control plane creates its Elastic Network Interfaces (ENIs)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;What it controls:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;The EKS control plane runs in an AWS-managed VPC (you don&#39;t see it)&lt;/li&gt;&lt;li&gt;To communicate with your worker nodes, it creates ENIs in your VPC&lt;/li&gt;&lt;li&gt;These ENIs are placed in the subnets you specify here&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Typical configuration:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;module &quot;eks&quot; {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; source&amp;nbsp; = &quot;terraform-aws-modules/eks/aws&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; name = &quot;my-cluster&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; # Control plane ENIs go here&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &lt;b&gt;control_plane_subnet_ids&lt;/b&gt; = [&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;subnet-private-1a&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;subnet-private-1b&quot;,&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;subnet-private-1c&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; ]&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Best practices:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Usually private subnets&lt;/li&gt;&lt;li&gt;Should span multiple AZs for high availability (AWS requires at least 2)&lt;/li&gt;&lt;li&gt;Minimum of 2 subnets, maximum of 16&lt;/li&gt;&lt;li&gt;Each subnet needs at least 5 available IP addresses&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;What these ENIs do:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Allow the control plane to communicate with worker nodes&lt;/li&gt;&lt;li&gt;Allow worker nodes to communicate with the API server&lt;/li&gt;&lt;li&gt;Handle API server endpoint traffic&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;security_group_additional_rules&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Adds custom security group rules for the cluster, such as allowing node-to-node communication and VPN access for kubectl.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;node_security_group_additional_rules&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Further customizes node security groups, allowing all node-to-node traffic and all outbound traffic.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;Understanding EKS Architecture&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;An EKS cluster has two main components:&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;┌─────────────────────────────────────────────────────────┐&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; EKS Cluster&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; ┌───────────────────────────────────────┐&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;Control Plane (AWS Managed)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;- API Server&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;- etcd&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;- Scheduler&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;- Controller Manager&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;Runs in AWS-managed account&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; └──────────────┬────────────────────────┘&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ ENIs in your VPC&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│ (&lt;b&gt;control_plane_subnet_ids&lt;/b&gt;)&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; ┌──────────────▼────────────────────────┐&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;Your VPC&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;┌─────────────────────────────┐&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;│&amp;nbsp; Worker Nodes (subnet_ids)&amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;│&amp;nbsp; - EC2 instances&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;│&amp;nbsp; - Your pods run here&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; │&amp;nbsp; &amp;nbsp;└─────────────────────────────┘&amp;nbsp; &amp;nbsp; &amp;nbsp;│&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;│&amp;nbsp; └───────────────────────────────────────┘&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; │&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-family: courier; font-size: x-small;&quot;&gt;└─────────────────────────────────────────────────────────┘&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;ENI: elastic network interface&lt;/b&gt;. It is a logical networking component in a VPC that represents a &lt;b&gt;virtual network card&lt;/b&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;4. Node Group Configuration&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;node_security_group_tags&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Adds a tag for Karpenter (an open-source Kubernetes node autoscaler) discovery.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;eks_managed_node_group_defaults&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Sets default properties for all managed node groups, including:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;Attaching the CNI policy for networking.&lt;/li&gt;&lt;li&gt;Using a specific SSH key.&lt;/li&gt;&lt;li&gt;Associating additional security groups.&lt;/li&gt;&lt;li&gt;Defining block device mappings for EBS volumes.&lt;/li&gt;&lt;li&gt;Attaching the AmazonSSMManagedInstanceCore policy for SSM access.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;eks_managed_node_groups&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Defines a default managed node group with:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;A specific AMI type.&lt;/li&gt;&lt;li&gt;Desired, minimum, and maximum node counts.&lt;/li&gt;&lt;li&gt;Instance types from a variable.&lt;/li&gt;&lt;li&gt;On-demand capacity, EBS optimization, and disk size.&lt;/li&gt;&lt;li&gt;Custom labels for node identification and environment.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;The gold standard for production environments is &lt;b&gt;explicit pinning&lt;/b&gt;. This ensures that our infrastructure only changes when we decide to change the code. In order to pin AMI version used in node groups we need to set two attributes:&lt;/div&gt;&lt;div&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;ami_release_version&lt;/span&gt; needs to be set. This prevents nodes from cycling unexpectedly during a routine deployment.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;use_latest_ami_release_version&lt;/span&gt; needs to be set to false (without this, terraform plan will still show that it wants to upgrade AMI version, even if we&#39;ve set ami_release_version)&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Example:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; eks_managed_node_groups = {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &quot;${local.cluster_name}-v1_33&quot; = {&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;ami_release_version&lt;/b&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; = &quot;1.33.8-20260224&quot;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;use_latest_ami_release_version&lt;/b&gt; = false&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;color: #38761d; font-family: courier;&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; ...&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;5. Tagging&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style=&quot;text-align: left;&quot;&gt;&lt;span style=&quot;font-family: courier;&quot;&gt;tags&lt;/span&gt;&lt;/h3&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Applies custom tags to all AWS resources created by the module, supporting cost allocation and resource management.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style=&quot;text-align: left;&quot;&gt;Summary&lt;/h2&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Our configuration sets up a secure, private, and production-ready EKS cluster with managed node groups, essential add-ons, robust logging, and fine-grained network and IAM controls. It leverages best practices for security (private endpoints, IAM roles for service accounts), scalability (managed node groups, Karpenter tags), and maintainability (modular, versioned, and tagged infrastructure).&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;---&lt;/div&gt;&lt;div&gt;&lt;a href=&quot;https://github.com/terraform-aws-modules/terraform-aws-iam/tree/91c8dbd25d9546d228c2c8030c1463f9b52c9b43/modules/iam-role-for-service-accounts-eks&quot;&gt;&lt;br class=&quot;Apple-interchange-newline&quot; /&gt;terraform-aws-iam/modules/iam-role-for-service-accounts-eks&lt;/a&gt;&amp;nbsp;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://tech-blog.bojankomazec.com/feeds/8196912125955159361/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment/fullpage/post/4912226416400969049/8196912125955159361' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8196912125955159361'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4912226416400969049/posts/default/8196912125955159361'/><link rel='alternate' type='text/html' href='http://tech-blog.bojankomazec.com/2026/04/provisioning-aws-eks-cluster-with.html' title='Provisioning AWS EKS Cluster with terraform-aws-modules/eks/aws'/><author><name>Bojan Komazec</name><uri>http://www.blogger.com/profile/05437236807206825832</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjpL3UGIwOtt3orHP_dEAx8jM5SFevZLk2BnC6GGcWxyhk7xlnNCeo1oLfWDQsVP21h-OlZamMah3YrDbtKPzUdctuUQx5HXUVZaVCV3p9Ou2BqnQshV0K8zeYt1pHjQCCRrL4QjTTiYRr7NGpV-6wJha2TNZGsSB448SNqosT_i8o2hLKd0wTc3B5OyZA/s72-w308-h640-c/aws-eks_terraform-1.png" height="72" width="72"/><thr:total>0</thr:total></entry></feed>