<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Laszlo Fogas</title>
    <description>The latest articles on Forem by Laszlo Fogas (@laszlocph).</description>
    <link>https://forem.com/laszlocph</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F40654%2Fb3b70a23-ce6d-4278-9f26-e7dbe69e69c4.jpg</url>
      <title>Forem: Laszlo Fogas</title>
      <link>https://forem.com/laszlocph</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/laszlocph"/>
    <language>en</language>
    <item>
      <title>The how and why we built our SaaS platform on Hetzner and Kubernetes</title>
      <dc:creator>Laszlo Fogas</dc:creator>
      <pubDate>Mon, 13 Mar 2023 10:45:42 +0000</pubDate>
      <link>https://forem.com/laszlocph/options-for-kubernetes-pod-autoscaling-2mf5</link>
      <guid>https://forem.com/laszlocph/options-for-kubernetes-pod-autoscaling-2mf5</guid>
      <description>&lt;p&gt;Hello everyone, Laszlo here, the founder of Gimlet.io 👋. In this blog post I try to address various aspects of how and why we built our SaaS platform on Hetzner and Kubernetes and not on one of the hyperscaler cloud providers.&lt;/p&gt;

&lt;p&gt;It is an interesting time. While we were building our platform on Hetzner, David Heinemeier Hansson, the founder of 37Signals has also broke ground on their new project to leave AWS in favor of a managed data center. His posts on the topic had a good run on social media. While DHH is a contrarian and sometimes good at starting trends, we are not here to start or join any movement. In this post I simply want to share our choices and experiences with Hetzner with the curious minded. Show what tools we used, what trade-offs we had to consider, what are the pros and cons in early 2023 of not using one of the big cloud providers. So let's get started, shall we?&lt;/p&gt;

&lt;p&gt;First things first, why Hetzner?&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Hetzner?
&lt;/h2&gt;

&lt;p&gt;I don't want to drag it out too long: it is price.&lt;/p&gt;

&lt;p&gt;We built our compute nodes on the EX43 model, the newest staple machine of Hetzner. The hexa-core, Alder Lake CPU with 64GB RAM and 2x512GB NVMe SSD comes in at 52.1 EUR in one of Hetzner's German datacenter. No VAT in our case.&lt;/p&gt;

&lt;p&gt;Comparing this to&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS's &lt;code&gt;m6a.2xlarge&lt;/code&gt; and &lt;code&gt;m6i.2xlarge&lt;/code&gt; instances costing $281 and $312 respectively in &lt;code&gt;eu-west-1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;and Google Cloud's &lt;code&gt;c3-standard-8&lt;/code&gt; and &lt;code&gt;n2-standard-8&lt;/code&gt; comes in $277 and $311 in &lt;code&gt;europe-west4&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;there is a significant price advantage on Hetzner's side.&lt;/p&gt;

&lt;p&gt;And that's only comparing to eight virtual CPU cores. If we factor in the number of disk IOPS we get from a bare metal server, the at least double memory size, and the fact that we are getting not eight virtual CPUs, but six real CPU cores with twelve threads, Hetzner being 5 times cheaper is an understatement.&lt;/p&gt;

&lt;p&gt;But not all things are equal, Hetzner being five times cheaper comes at a price. We are missing out on features that hyperscalers spent a decade building.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are we losing by not hosting on one of the big clouds?
&lt;/h2&gt;

&lt;p&gt;We only need a few things to run our platform, and among those, the things we miss the most on Hetzner are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First and foremost we need Kubernetes, we would use managed Kubernetes if it would be available.&lt;/li&gt;
&lt;li&gt;We need a highly available SQL database, but RDS or CloudSQL is not available.&lt;/li&gt;
&lt;li&gt;Virtual networks and security groups. We had to work even for basic network and host security on Hetzner.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Addressing the missing managed Kubernetes
&lt;/h3&gt;

&lt;p&gt;Managing Kubernetes is not something we prefer doing and usually we rely on the managed Kubernetes offerings, but on Hetzner it is just not possible.&lt;/p&gt;

&lt;p&gt;Even though we manage Kubernetes ourselves, we tried to minimize the moving parts. Simplicity is key for us, therefore we chose to use the k3s project. K3s is a fully compliant Kubernetes distribution with a few notable simplifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;k3s is using an SQL database as storage, so we don't have to deal with etcd at all.&lt;/li&gt;
&lt;li&gt;It is packaged as a single binary and it has a matching configuration experience. All cluster operations - like managing certificates - are reduced to either an argument of the k3s binary, or a CLI command&lt;/li&gt;
&lt;li&gt;It is secure by default with reasonable defaults for lightweight environments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We took one more notable architectural decision that eased our Kubernetes cluster building: we keep our state in SQL databases, so we did not have to install and maintain a clustered filesystem for persistent volumes.&lt;/p&gt;

&lt;p&gt;Even though we cut many of the difficult parts short in our setup, we expect a few days of maintenance, sometimes immediate action, in the coming year that will be releated to our self-managed Kubernetes.&lt;/p&gt;

&lt;p&gt;But nodes die also on managed Kubernetes offerings and disks fill up. Maybe in our case rebuilding a node will be longer (starting a new node, running Ansible scripts, etc) than on hyperscalers, but the number of issues and the severity we don't expect to become unmanageable. Famous last words, right? A follow up blog post is due in 12 months.&lt;/p&gt;

&lt;h3&gt;
  
  
  No RDS, what now?
&lt;/h3&gt;

&lt;p&gt;A managed database is really something that I happily pay a premium for. High-availability, point-in-time restores in a click of a button is not something that is easy to replicate.&lt;/p&gt;

&lt;p&gt;Postgresql is also something that is critical for Gimlet's SaaS platform as we keep all state in Postgresql databases. Not just client data, but the Kubernetes control plane is also stored in an SQL database. So we needed to build a highly available, secure Postgresql cluster, and had to handle proper off-site backups.&lt;/p&gt;

&lt;p&gt;What gave us confidence in the process is that we had experience in running replicated Postgres clusters back in the pre-Postgres-RDS days. Feels like a lifetime away, but Postgres on RDS is not yet ten years old today.&lt;/p&gt;

&lt;h3&gt;
  
  
  Networking and security, the big one
&lt;/h3&gt;

&lt;p&gt;We spent most of the time on the networking setup and security considerations.&lt;/p&gt;

&lt;p&gt;By default, Hetzner gives you root access over SSH, there are no virtual networks, or security groups. If you start a server process on your node, it will be instantly accessible on the internet. Not a friendly default, and the lack of VPCs and Security Groups have been the biggest pain we had to solve.&lt;/p&gt;

&lt;p&gt;This was also the place where we involved external help. A review of our setup by two independent consultants icreased our confidence in the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building infrastructure from the ground up on bare metal in 2023
&lt;/h2&gt;

&lt;p&gt;In this section I list the tooling and steps it took to build our SaaS platform on Hetzner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as code
&lt;/h3&gt;

&lt;p&gt;While the year is 2023, we wrote our infrastructure as code setup in Ansible. Since most of the configuration happens inside a node, there is not much to Terraform. Our team had confidence in Ansible from the virtual machine days.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node security
&lt;/h3&gt;

&lt;p&gt;The first Ansible playbooks we wrote were the node hardening ones. Unattended upgrades, SSH fail2ban, and tens of best practices applied automatically on all our nodes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network security
&lt;/h3&gt;

&lt;p&gt;By default Hetzner assigns a public IP to nodes, and hands out root SSH access for you to start using the nodes. Also, every port you open is open to the internet. No private networks, or security groups by default. So we started by building one.&lt;/p&gt;

&lt;p&gt;How we built our network:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;There is basic VLAN support in Hetzner, we could connect our nodes with an unmetered, internal network connection.&lt;/li&gt;
&lt;li&gt;We also built a Wireguard based VPN mesh on this internal network. Essentially encrypting all traffic between our nodes.&lt;/li&gt;
&lt;li&gt;We installed a firewall on all nodes, and bound only port 80 and 443 to the public network IP addresses.&lt;/li&gt;
&lt;li&gt;We front all web traffic with a Cloudflare Loadbalancer and Web Application Firewall. Traffic is only accepted from Cloudflare's servers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubernetes
&lt;/h3&gt;

&lt;p&gt;We use k3s. Its single binary distribution and config experience made the job easier, furthermore we could back it with an SQL database, which removed etcd from the mix. We don't know etcd, so that was amazing.&lt;/p&gt;

&lt;p&gt;We took one more notable architectural decision that eased our Kubernetes cluster building: we keep our state in SQL databases, so we did not have to install and maintain a clustered filesystem for persistent volumes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Postgresql
&lt;/h3&gt;

&lt;p&gt;We built a streaming replication based active - passive Postgresql cluster.&lt;/p&gt;

&lt;p&gt;To keep things simple we built the cluster outside of Kubernetes and containers. Not that it would have been a big issue otherwise, but we would have pinned the Postgres pods onto specific nodes anyways. We didn't opt to use Kubernetes Postgres operators, like Patroni just yet.&lt;/p&gt;

&lt;p&gt;There is one significant shortcut that we took here. Failover is designed to be manual at this point. This could be a considerable source of downtime, and we may improve this practice in further iterations of our platform.&lt;/p&gt;

&lt;p&gt;It is important to note why we took this risk: to enable automatic failover we would have to write a bulletproof failover script that maximizes availability and minimizes data consistency risks. With a bug in the failover automation, we could risk data consistency issues that are potentially more difficult to handle than downtimes. Basically we optimized for operational simplicity, and a good enough uptime.&lt;/p&gt;

&lt;p&gt;Now judging a good enough uptime comes down to the reader as Gimlet does not provide an SLA at this point, but let me leave you with two thoughts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A 99,5% availability, an industry wide standard for SaaS platforms, means a yearly 1.83 days of downtime. A 99% availability means a 3.65 days downtime a year. This last one practically means that our whole team could be on vacation in the jungles of Brazil, travel back to Europe, open their laptops and do the database failover and we would still be faster than three days. It goes without saying that we are not planning to travel to the jungles of Brazil without anyone being on call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It is good to look up at this point what &lt;a href="https://aws.amazon.com/rds/sla/"&gt;SLA&lt;/a&gt; Amazon's RDS database provides. They are kind of mushy on the topic: &lt;em&gt;"AWS will use commercially reasonable efforts"&lt;/em&gt; and if they fail, they give your money back in credits. Ten percent if they are between 99-99.95% availability, 25% percent if they are between 95 and 99 percent, and all the money otherwise. In practical terms, they can be down for 18 days a year and you only get back one fourth of your money.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gitops: drinking our own champagne
&lt;/h3&gt;

&lt;p&gt;Each user that signs up gets a new Gimlet instance that is identical to the latest release of the open-source version. Each user configuration is stored in gitops and we run Gimlet to manage those Gimlet instances.&lt;/p&gt;

&lt;p&gt;Besides Gimlet instances, we also manage our cluster components with gitops.&lt;/p&gt;

&lt;h3&gt;
  
  
  Backups
&lt;/h3&gt;

&lt;p&gt;We store state in a single location only, in our Postgresql cluster.&lt;/p&gt;

&lt;p&gt;For everything else, the source of truth is our infrastructure as code repositories: Ansible and gitops.&lt;/p&gt;

&lt;p&gt;We backup our Postgresql cluster, encrypt and ship our backups to an off-site location.&lt;/p&gt;

&lt;h3&gt;
  
  
  Disaster recovery
&lt;/h3&gt;

&lt;p&gt;Our disaster recovery strategy builds on two pillars: our backups and infrastructure as code repositories.&lt;/p&gt;

&lt;p&gt;During our platform building efforts we rebuilt the whole stack numerous times from code and before launching the early access program, we rebuilt everything from scratch. We also ran synthetic database restore tests.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encryption
&lt;/h3&gt;

&lt;p&gt;We encrypt data in numerous places.&lt;/p&gt;

&lt;p&gt;In transit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We encrypt all traffic on our internal Wireguard based VPN mesh.&lt;/li&gt;
&lt;li&gt;We use SSL between our applications and Postgresql.&lt;/li&gt;
&lt;li&gt;Between our services.&lt;/li&gt;
&lt;li&gt;Between our applications and our ingress controller.&lt;/li&gt;
&lt;li&gt;Between our ingress controller and Cloudflare.&lt;/li&gt;
&lt;li&gt;Between Cloudflare and endusers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We encrypt our node disks.&lt;/li&gt;
&lt;li&gt;We encrypt our backups.&lt;/li&gt;
&lt;li&gt;Gimlet instances encrypt sensitive database fields in the database.&lt;/li&gt;
&lt;li&gt;K3s encrypts its secrets that are stored in Postgresql.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Monitoring
&lt;/h3&gt;

&lt;p&gt;We use a Prometheus / Grafana stack for monitoring, UptimeRobot for uptime monitoring, and PagerDuty for our on-call.&lt;/p&gt;

&lt;h2&gt;
  
  
  How has Hetzner been so far?
&lt;/h2&gt;

&lt;p&gt;We used Hetzner a couple of years back. It has been stable enough back then, and also today.&lt;/p&gt;

&lt;p&gt;We use stock node types and only expect linear scaling. We provision nodes manually which takes around fifteen minutes until we can log in. We heard from friends that this is not the case for custom machine types, but we will cross this bridge when we get there.&lt;/p&gt;

&lt;p&gt;An improvement since our previous ride with Hetzner is the VLAN feature. It was straightforward to set up based on the documentation and it has been stable so far. One thing we could not achieve though: connecting our dedicated nodes with Hetzner Cloud VMs. We are using dedicated nodes, but we could spin up VMs for small tasks if the VLAN would work between those.&lt;/p&gt;

&lt;p&gt;That's it for now. It is a snapshot of where we are currently, expect a follow up post in 12 months, or earlier if something changes significantly.&lt;/p&gt;

&lt;p&gt;As always, if you want to deploy to Kubernetes using the best of open-source tools out of the box, our self-serve SaaS early access is open on &lt;a href="//https:/gimlet.io/signup"&gt;https:/gimlet.io/signup&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Onwards!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cloud</category>
      <category>aws</category>
    </item>
    <item>
      <title>Options for Kubernetes pod autoscaling</title>
      <dc:creator>Laszlo Fogas</dc:creator>
      <pubDate>Thu, 26 Jan 2023 09:28:25 +0000</pubDate>
      <link>https://forem.com/laszlocph/options-for-kubernetes-pod-autoscaling-7jn</link>
      <guid>https://forem.com/laszlocph/options-for-kubernetes-pod-autoscaling-7jn</guid>
      <description>&lt;p&gt;Kubernetes autoscaling was supposed to be easy. Even though one of the selling points of Kubernetes is scaling, the built-in autoscaling support is basic at best. You can only scale based on CPU or memory consumption, anything more advanced requires additional tooling that is often not trivial.&lt;/p&gt;

&lt;p&gt;The Gimlet.io team put together this blog to show common usecases of autoscaling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;based on CPU&lt;/li&gt;
&lt;li&gt;custom Prometheus metrics&lt;/li&gt;
&lt;li&gt;and RabbitMQ queue length&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Furthermore, we are aiming to clear up the differences between the Horizontal Pod Autoscaler (HPA), the Prometheus Adapter and KEDA. &lt;/p&gt;

&lt;p&gt;Let's get into it shall we?&lt;/p&gt;

&lt;p&gt;First, about the Horizontal Pod Autoscaler (HPA).&lt;/p&gt;

&lt;h2&gt;
  
  
  First, about the Horizontal Pod Autoscaler (HPA)
&lt;/h2&gt;

&lt;p&gt;The Horizontal Pod Autoscaler, or HPA in short, is a Kubernetes resource that allows you to scale your application based on resource utilization such as CPU and memory.&lt;/p&gt;

&lt;p&gt;To be more precise, HPA is a general purpose autoscaler, but by default only CPU and memory metrics are available for it to scale on.&lt;/p&gt;

&lt;p&gt;Its data source is the Kubernetes Metrics API, which by the way also powers the &lt;code&gt;kubectl top&lt;/code&gt; command, and backed by data provided by the &lt;code&gt;metrics-server&lt;/code&gt; component. This component runs on your cluster and it is installed by default on GKE, AKS, CIVO and k3s clusters, but it needs to be manually installed on many others, like on Digital Ocean, EKS and Linode.&lt;/p&gt;

&lt;p&gt;The HPA resource is moderately well documented in the Kubernetes documentation. Some confusion arises from the fact that there are blog posts out there showcasing different Kubernetes API versions: keep in mind that &lt;code&gt;autoscaling/v2&lt;/code&gt; is not backwards compatible with v1!&lt;/p&gt;

&lt;p&gt;More headaches arise when you try to scale on resource metrics other than CPU and memory. In order to scale pods - let's say - based on number of HTTP requests or queue length, you need to make the Kubernetes API aware of these metrics first. Luckily there are open-source metrics backends implemented, and the best known is Prometheus Adapter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prometheus Adapter
&lt;/h2&gt;

&lt;p&gt;Prometheus Adapter is a Kubernetes Custom Metrics API implementation which exposes selected Prometheus metrics through the Kubernetes API for the Horizontal Pod Autoscaler (HPA) to scale on.&lt;/p&gt;

&lt;p&gt;Essentially you configure the Prometheus Adapter to read your desired metric from Prometheus, and it will serve it to HPA to scale on. This can be an HTTP request rate, or a RabbitMQ queue length or any metric from Prometheus.&lt;/p&gt;

&lt;p&gt;Prometheus Adapter does the job, but in our experience its configuration is cryptic. While there are several blog posts out there explaining its configuration syntax, we could not make it work sufficiently reliably with our custom metrics scaling needs.&lt;/p&gt;

&lt;p&gt;That is essentially why we have brought you here today, to share our experience with a Prometheus Adapter alternative, called KEDA.&lt;/p&gt;

&lt;p&gt;So, what exactly is KEDA, and why we prefer it?&lt;/p&gt;

&lt;h2&gt;
  
  
  KEDA
&lt;/h2&gt;

&lt;p&gt;KEDA is a Kubernetes operator that is handling a user friendly custom yaml resource where you can define your scaling needs.&lt;/p&gt;

&lt;p&gt;In KEDA, you create a &lt;code&gt;ScaledObject&lt;/code&gt;custom resource with the necessary information about the deployment you want to scale, then define the trigger event, which can be based on CPU and memory usage or on custom metrics. It has premade triggers for most anything that you may want to scale on, with a yaml structure that we think the Kubernetes API could have been made in the first place.&lt;/p&gt;

&lt;p&gt;KEDA does two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it exposes the selected metrics to the Kubernetes Custom Metrics API - just like Prometheus Adapter&lt;/li&gt;
&lt;li&gt;and it creates the Horizontal Pod Autoscaler resource. Ultimately this HPA does the scaling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now that you have an overview, let's take a step further and show how you can autoscale with KEDA!&lt;/p&gt;

&lt;h2&gt;
  
  
  Autoscaling example based on CPU usage
&lt;/h2&gt;

&lt;p&gt;In order to autoscale your application with KEDA, you need to define a &lt;code&gt;ScaledObject&lt;/code&gt; resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda.sh/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ScaledObject&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cpu-based-scaledobject&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;minReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="s"&gt;1&lt;/span&gt;                                 
  &lt;span class="na"&gt;maxReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-app-deployment&lt;/span&gt;
  &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cpu&lt;/span&gt;
    &lt;span class="na"&gt;metricType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Utilization&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;scaleTargetRef&lt;/code&gt; is where you refer to your deployment, and &lt;code&gt;triggers&lt;/code&gt; is where you define the metrics and threshold that will trigger the scaling.&lt;/p&gt;

&lt;p&gt;In this sample we trigger based on the CPU usage, the &lt;code&gt;ScaledObject&lt;/code&gt; will manage the number of replicas automatically for you and maintain a maximum 50% CPU usage per pod.&lt;/p&gt;

&lt;p&gt;As usual with Kubernetes custom resources, you can &lt;code&gt;kubectl get&lt;/code&gt; and &lt;code&gt;kubectl describe&lt;/code&gt; the resource once you deployed it on the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get scaledobject
NAME                    SCALETARGETKIND      SCALETARGETNAME      MIN   MAX   TRIGGERS  READY   ACTIVE
cpu-based-scaledobject  apps/v1.Deployment   test-app-deployment   2     10    cpu      True    True
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To have an in-depth understanding of what is happening in the background, you can see the logs of the keda operator pod, and you can also &lt;code&gt;kubectl describe&lt;/code&gt; the HPA resource that KEDA created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autoscaling example based on custom metrics
&lt;/h2&gt;

&lt;p&gt;To use custom metrics, you need to make changes to the &lt;code&gt;triggers&lt;/code&gt; section.&lt;/p&gt;

&lt;p&gt;Scaling example based on custom Prometheus metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;serverAddress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://&amp;lt;prometheus-host&amp;gt;:9090&lt;/span&gt;
    &lt;span class="na"&gt;metricName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http_requests_total&lt;/span&gt; &lt;span class="c1"&gt;# Note: name to identify the metric, generated value would be `prometheus-http_requests_total`&lt;/span&gt;
    &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sum(rate(http_requests_total{deployment="my-deployment"}[2m]))&lt;/span&gt; &lt;span class="c1"&gt;# Note: query must return a vector/scalar single element response&lt;/span&gt;
    &lt;span class="na"&gt;threshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;100.50'&lt;/span&gt;
    &lt;span class="na"&gt;activationThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;5.5'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scaling example based on RabbitMQ queue length:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rabbitmq&lt;/span&gt;
  &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amqp://localhost:5672/vhost&lt;/span&gt;
    &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueueLength&lt;/span&gt; &lt;span class="c1"&gt;# QueueLength or MessageRate&lt;/span&gt;
    &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100"&lt;/span&gt; &lt;span class="c1"&gt;# message backlog or publish/sec. target per instance&lt;/span&gt;
    &lt;span class="na"&gt;queueName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;testqueue&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the &lt;a href="https://keda.sh/docs/2.9/scalers/"&gt;KEDA&lt;/a&gt; official website to see all the scalers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing words
&lt;/h2&gt;

&lt;p&gt;When we found KEDA, our pains with the Prometheus Adapter were solved instantly. KEDA's simple install experience and readymade scalers allowed us to cover our autoscaling needs, while its straightforward yaml syntax communicates well the scaling intent.&lt;/p&gt;

&lt;p&gt;We not just use KEDA ourselves, but also recommend it to our clients and friends. So much so that we integrated KEDA into our preferred stack at Gimlet.&lt;/p&gt;

&lt;p&gt;Onwards!&lt;/p&gt;




&lt;p&gt;Originally published by Youcef Guichi and Laszlo Fogas at &lt;a href="https://gimlet.io/blog/options-for-kubernetes-pod-autoscaling"&gt;https://gimlet.io&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cloud</category>
      <category>microservices</category>
    </item>
  </channel>
</rss>
