<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.9.2">Jekyll</generator><link href="https://taulev.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://taulev.github.io/" rel="alternate" type="text/html" /><updated>2022-08-14T14:32:24+00:00</updated><id>https://taulev.github.io/feed.xml</id><title type="html">Adventures of a SysAdmin</title><subtitle>Random snippets from SysAdmin experience</subtitle><author><name>taulev</name></author><entry><title type="html">Boostraping Private Gke Cluster With Terraform</title><link href="https://taulev.github.io/2022/08/14/boostraping-private-gke-cluster-with-terraform.html" rel="alternate" type="text/html" title="Boostraping Private Gke Cluster With Terraform" /><published>2022-08-14T00:00:00+00:00</published><updated>2022-08-14T00:00:00+00:00</updated><id>https://taulev.github.io/2022/08/14/boostraping-private-gke-cluster-with-terraform</id><content type="html" xml:base="https://taulev.github.io/2022/08/14/boostraping-private-gke-cluster-with-terraform.html">&lt;p&gt;Kubernetes is an orchestration tool which automates deployment, scaling, and management of containerized applications. GKE (Google Kubernetes Engine) is a manged Kubernetes platform offering by Google Cloud, it takes away the pain of managing Kubernetes control plane. In this post, I will be sharing the steps required to easily bootstrap a private GKE cluster with Terraform.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;creating-a-service-account&quot;&gt;Creating a service account&lt;/h2&gt;
&lt;p&gt;First, you will need to create a service account within Google Cloud Platform (GCP). Open the GCP console and navigate to &lt;strong&gt;IAM &amp;amp; Admin -&amp;gt; Service Accounts -&amp;gt; Create service account (at the top)&lt;/strong&gt; and fill out the details.&lt;/p&gt;

&lt;p&gt;There are multiple ways for service account to authenticate with GCP, but for convenience we will be using service account keys. In the same service account window, click on 3 dots under actions and select &lt;strong&gt;Manage keys&lt;/strong&gt;. In the keys window, click &lt;strong&gt;Add key -&amp;gt; Create new key -&amp;gt; Select JSON -&amp;gt; Create&lt;/strong&gt;, the key will be automatically downloaded.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;configuring-terraform-providers&quot;&gt;Configuring Terraform providers&lt;/h2&gt;
&lt;p&gt;First create a root folder where you will hold Terraform files, in this post the folder name we will be using is &lt;code&gt;gke&lt;/code&gt;. In the &lt;code&gt;gke&lt;/code&gt; folder, create the following files:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code&gt;config.tf&lt;/code&gt; - will contain provider information&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;main.tf&lt;/code&gt; - will have our resource definitions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should also move the service account key file to this folder, I name my service account key file &lt;code&gt;account.json&lt;/code&gt;. Your directory structure should look like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gke
├── account.json
├── config.tf
├── main.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Open the &lt;code&gt;config.tf&lt;/code&gt; file and fill it with the following text:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;terraform {
  required_providers {
    google = {
      version = &quot;~&amp;gt; 4.31.0&quot;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We specify that we will be using Google provider of version &lt;em&gt;4.31.x&lt;/em&gt;, &lt;code&gt;~&amp;gt;&lt;/code&gt; specifies that the rightmost component of the version can be incremented.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;bootstrapping-the-gke-cluster&quot;&gt;Bootstrapping the GKE cluster&lt;/h2&gt;
&lt;p&gt;Now that we have our provider configured, we are ready to define Terraform resources, that allows us to create a GKE cluster with one &lt;code&gt;terraform apply&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4 id=&quot;enabling-required-gcp-apis&quot;&gt;Enabling required GCP APIs&lt;/h4&gt;
&lt;p&gt;By default, the GKE API is not enabled and without it, Terraform won’t be able to provision a GKE cluster. This could be enabled manually, but we can also enable it via Terraform. Open &lt;code&gt;main.tf&lt;/code&gt; and add the following lines (&lt;strong&gt;you should change “your-project-id” with your actual project id&lt;/strong&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;locals {
  api = [
    &quot;cloudresourcemanager.googleapis.com&quot;,
    &quot;container.googleapis.com&quot;,
  ]
}

resource &quot;google_project_service&quot; &quot;apis&quot; {
  count = length(local.api)

  service                    = local.api[count.index]
  project                    = &quot;your-project-id&quot;
  disable_dependent_services = true
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we define a local variable &lt;code&gt;api&lt;/code&gt; which is a list of APIs that we will be enabling, and we later access it via the &lt;code&gt;local&lt;/code&gt; keyword (as can be seen in &lt;code&gt;service&lt;/code&gt; value). &lt;code&gt;cloudresourcemanager.googleapis.com&lt;/code&gt; enables Cloud Resource Manager API, which allows us to interact with GCP resources, while &lt;code&gt;container.googleapis.com&lt;/code&gt; enables Kubernetes Engine API, which allows us to provision GKE clusters. We are using &lt;code&gt;count&lt;/code&gt; to create multiple &lt;code&gt;google_project_service&lt;/code&gt; resources with a single definition.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4 id=&quot;creating-a-vpc-network&quot;&gt;Creating a VPC network&lt;/h4&gt;
&lt;p&gt;For the GKE cluster, we will also create a private VPC network. We will be using the predefined &lt;a href=&quot;https://registry.terraform.io/modules/terraform-google-modules/network/google/5.2.0&quot;&gt;Google Network&lt;/a&gt; module. A module is basically a collection of resources that are used together. Append your &lt;code&gt;main.tf&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module &quot;gke_network&quot; {
  source  = &quot;terraform-google-modules/network/google&quot;
  version = &quot;~&amp;gt; 5.2.0&quot;

  network_name = &quot;gke-network&quot;
  project_id   = &quot;your-project-id&quot;
  subnets = [
    {
      subnet_name           = &quot;gke-subnetwork&quot;
      subnet_ip             = &quot;10.1.0.0/16&quot;
      subnet_private_access = &quot;true&quot;
      subnet_region         = &quot;europe-west3&quot;
    },
  ]
  secondary_ranges = {
    &quot;gke-subnetwork&quot; = [
      {
        ip_cidr_range = &quot;10.2.0.0/16&quot;
        range_name    = &quot;pod-ip-range&quot;
      },
      {
        ip_cidr_range = &quot;10.3.0.0/16&quot;
        range_name    = &quot;service-ip-range&quot;
      },
    ]
  }

  depends_on = [google_project_service.apis]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With &lt;code&gt;source&lt;/code&gt; we tell Terraform where to look for the module and everything else is pretty self-explanatory - we create a VPC and a subnetwork within that VPC, then we specify secondary IP ranges which will be used for Kubernetes Pods and Services (for real life clusters you should carefully consider subnet ranges as per &lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/concepts/alias-ips#defaults_limits&quot;&gt;docs&lt;/a&gt;). Finally, we use &lt;code&gt;depends_on&lt;/code&gt; to ensure that Terraform won’t attempt to provision these resources until APIs are enabled.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4 id=&quot;provisioning-gke-cluster&quot;&gt;Provisioning GKE cluster&lt;/h4&gt;
&lt;p&gt;Finally, we are ready to provision our GKE cluster. Again, we will be using a &lt;a href=&quot;https://registry.terraform.io/modules/terraform-google-modules/kubernetes-engine/google/22.1.0&quot;&gt;Terraform module&lt;/a&gt; to make our life a lot easier. Add these lines to &lt;code&gt;main.tf&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module &quot;gke&quot; {
  source  = &quot;terraform-google-modules/kubernetes-engine/google//modules/private-cluster&quot;
  version = &quot;~&amp;gt; 22.1.0&quot;

  enable_private_nodes = true
  ip_range_pods        = &quot;pod-ip-range&quot;
  ip_range_services    = &quot;service-ip-range&quot;
  master_authorized_networks = [
    { cidr_block = &quot;1.2.3.4/32&quot;, display_name = &quot;First IP that will have access to Control Plane&quot; },
    { cidr_block = &quot;5.6.7.8/32&quot;, display_name = &quot;Second IP that will have access to Control Plane&quot; },
  ]
  name           = &quot;gke-cluster&quot;
  network        = module.gke_network.network_name
  network_policy = true
  node_pools = [
    {
      name         = &quot;my-node-pool&quot;
      machine_type = &quot;e2-small&quot;
      min_count    = 1
      max_count    = 3
      disk_size_gb = 30
    },
  ]
  project_id               = &quot;your-project-id&quot;
  region                   = &quot;europe-west3&quot;
  regional                 = false
  remove_default_node_pool = true
  subnetwork               = &quot;gke-subnetwork&quot;
  zones                    = [&quot;europe-west3-a&quot;]

  depends_on = [google_project_service.apis, module.gke_network]
}

module &quot;auth&quot; {
  source  = &quot;terraform-google-modules/kubernetes-engine/google//modules/auth&quot;
  version = &quot;~&amp;gt; 22.1.0&quot;

  cluster_name = module.gke.name
  location     = module.gke.location
  project_id   = &quot;your-project-id&quot;
  
  depends_on = [google_project_service.apis, module.gke]
}

resource &quot;local_file&quot; &quot;kubectlconfig&quot; {
  content  = module.auth.kubeconfig_raw
  filename = &quot;gke-cluster-config&quot;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Things worth mentioning:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;code&gt;enable_private_nodes = true&lt;/code&gt; ensures that created nodes do not have external IP address. As we are creating a private cluster, we want to limit public endpoint accessibility, we could disable it, but instead we will filter IPs which can access this public endpoint. We achieve that with &lt;code&gt;master_authorized_networks&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;You can use &lt;code&gt;node_pools&lt;/code&gt; to define your list of node pools that should be created. Here we create only one pool with e2-small instances. We allow a maximum of 3 nodes for autoscaler to scale if we lack resources.&lt;/li&gt;
  &lt;li&gt;As this is a tutorial, we are creating a zonal cluster by setting &lt;code&gt;regional = false&lt;/code&gt; and &lt;code&gt;zones = [&quot;europe-west3-a&quot;]&lt;/code&gt;. Zones specify in which zones the cluster can be hosted. Zonal cluster has only 1 Control plane, so if you want high availability you should change &lt;code&gt;regional&lt;/code&gt; to true, and then you can omit &lt;code&gt;zones&lt;/code&gt; variable as it is not required for regional cluster.&lt;/li&gt;
  &lt;li&gt;&lt;code&gt;module &quot;auth&quot;&lt;/code&gt; and &lt;code&gt;resource &quot;local_file&quot; &quot;kubectlconfig&quot;&lt;/code&gt; are optional, however they are convenient as they will create you a config file that allows you to authenticate with your GKE cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4 id=&quot;terraform-init--terraform-apply&quot;&gt;terraform init &amp;amp;&amp;amp; terraform apply&lt;/h4&gt;
&lt;p&gt;Our &lt;code&gt;main.tf&lt;/code&gt; is ready and all that is left for us to do it initialize terraform providers/modules and apply the configuration. Before you do that, you need to set the &lt;code&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/code&gt; environment variable to point to our &lt;code&gt;account.json&lt;/code&gt; file. You can achieve this with:
&lt;code&gt;export GOOGLE_APPLICATION_CREDENTIALS=/path/to/gke/account.json&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Once the environment variable is set, run &lt;code&gt;terraform init&lt;/code&gt; followed by &lt;code&gt;terraform apply&lt;/code&gt;. After running &lt;code&gt;terraform apply&lt;/code&gt; you will be provided with a list of resources that will be provisioned and will be asked for confirmation - type &lt;code&gt;yes&lt;/code&gt; press &lt;code&gt;Enter&lt;/code&gt; on your keyboard and go grab a drink as the provisioning will take some time, usually it is around 15-20 minutes.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;final-words&quot;&gt;Final Words&lt;/h2&gt;
&lt;p&gt;And here you have it - an easy way to provision a Private Cluster in GKE using Terraform! It is worth mentioning that the configuration here is simplified - usually you would want to define variables in &lt;code&gt;variables.tf&lt;/code&gt; and use those variables instead of direct values as in this post, also you would probably want to separate resources in different files, in this case you could move API enabling to &lt;code&gt;api.tf&lt;/code&gt; and VPC creating to &lt;code&gt;network.tf&lt;/code&gt;.&lt;/p&gt;

&lt;h4 id=&quot;complete-maintf&quot;&gt;Complete main.tf&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;locals {
  api = [
    &quot;cloudresourcemanager.googleapis.com&quot;,
    &quot;container.googleapis.com&quot;,
  ]
}

resource &quot;google_project_service&quot; &quot;apis&quot; {
  count = length(local.api)

  service                    = local.api[count.index]
  project                    = &quot;your-project-id&quot;
  disable_dependent_services = true
}

module &quot;gke_network&quot; {
  source  = &quot;terraform-google-modules/network/google&quot;
  version = &quot;~&amp;gt; 5.2.0&quot;

  network_name = &quot;gke-network&quot;
  project_id   = &quot;your-project-id&quot;
  subnets = [
    {
      subnet_name           = &quot;gke-subnetwork&quot;
      subnet_ip             = &quot;10.1.0.0/16&quot;
      subnet_private_access = &quot;true&quot;
      subnet_region         = &quot;europe-west3&quot;
    },
  ]
  secondary_ranges = {
    &quot;gke-subnetwork&quot; = [
      {
        ip_cidr_range = &quot;10.2.0.0/16&quot;
        range_name    = &quot;pod-ip-range&quot;
      },
      {
        ip_cidr_range = &quot;10.3.0.0/16&quot;
        range_name    = &quot;service-ip-range&quot;
      },
    ]
  }

  depends_on = [google_project_service.apis]
}

module &quot;gke&quot; {
  source  = &quot;terraform-google-modules/kubernetes-engine/google//modules/private-cluster&quot;
  version = &quot;~&amp;gt; 22.1.0&quot;

  enable_private_nodes = true
  ip_range_pods        = &quot;pod-ip-range&quot;
  ip_range_services    = &quot;service-ip-range&quot;
  master_authorized_networks = [
    { cidr_block = &quot;1.2.3.4/32&quot;, display_name = &quot;First IP that will have access to Control Plane&quot; },
    { cidr_block = &quot;5.6.7.8/32&quot;, display_name = &quot;Second IP that will have access to Control Plane&quot; },
  ]
  name           = &quot;gke-cluster&quot;
  network        = module.gke_network.network_name
  network_policy = true
  node_pools = [
    {
      name         = &quot;my-node-pool&quot;
      machine_type = &quot;e2-small&quot;
      min_count    = 1
      max_count    = 3
      disk_size_gb = 30
    },
  ]
  project_id               = &quot;your-project-id&quot;
  region                   = &quot;europe-west3&quot;
  regional                 = false
  remove_default_node_pool = true
  subnetwork               = &quot;gke-subnetwork&quot;
  zones                    = [&quot;europe-west3-a&quot;]

  depends_on = [google_project_service.apis, module.gke_network]
}

module &quot;auth&quot; {
  source  = &quot;terraform-google-modules/kubernetes-engine/google//modules/auth&quot;
  version = &quot;~&amp;gt; 22.1.0&quot;

  cluster_name = module.gke.name
  location     = module.gke.location
  project_id   = &quot;your-project-id&quot;
  
  depends_on = [google_project_service.apis, module.gke]
}

resource &quot;local_file&quot; &quot;kubectlconfig&quot; {
  content  = module.auth.kubeconfig_raw
  filename = &quot;gke-cluster-config&quot;
}
&lt;/code&gt;&lt;/pre&gt;</content><author><name>taulev</name></author><category term="Other" /><summary type="html">Kubernetes is an orchestration tool which automates deployment, scaling, and management of containerized applications. GKE (Google Kubernetes Engine) is a manged Kubernetes platform offering by Google Cloud, it takes away the pain of managing Kubernetes control plane. In this post, I will be sharing the steps required to easily bootstrap a private GKE cluster with Terraform.</summary></entry><entry><title type="html">Preparing Usb That Auto Installs Debian 11</title><link href="https://taulev.github.io/2022/08/07/preparing-usb-that-auto-installs-debian-11.html" rel="alternate" type="text/html" title="Preparing Usb That Auto Installs Debian 11" /><published>2022-08-07T00:00:00+00:00</published><updated>2022-08-07T00:00:00+00:00</updated><id>https://taulev.github.io/2022/08/07/preparing-usb-that-auto-installs-debian-11</id><content type="html" xml:base="https://taulev.github.io/2022/08/07/preparing-usb-that-auto-installs-debian-11.html">&lt;p&gt;I have been setting up my home lab, which consists of 5 servers and instead of spending 30-40 minutes installing Debian interactively I decided to spend 4-5 hours automating the process, so all you would have to do is choose the boot option as USB for the OS to be installed. After trial and error of multiple re-installs, here are the steps I took to succeed:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Create bootable USB flash drive&lt;/li&gt;
  &lt;li&gt;Create preseed file&lt;/li&gt;
  &lt;li&gt;Update grub.cfg, isolinux.cfg and txt.cfg&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;creating-bootable-usb-flash-drive&quot;&gt;Creating bootable USB flash drive&lt;/h2&gt;
&lt;p&gt;I am not going to go into detail how to do this, as this is a pretty straight-forward process and there are different ways to achieve it. I used &lt;a href=&quot;https://cdimage.debian.org/debian-cd/current/amd64/iso-cd/debian-11.4.0-amd64-netinst.iso&quot;&gt;amd64&lt;/a&gt; image which can be downloaded from &lt;a href=&quot;https://www.debian.org/distrib/netinst&quot;&gt;Debian&lt;/a&gt; page. And then used &lt;a href=&quot;https://rufus.ie/en/&quot;&gt;Rufus&lt;/a&gt; to create a bootable USB from that image.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;creating-preseed-file&quot;&gt;Creating preseed file&lt;/h2&gt;
&lt;p&gt;Once your bootable USB flash drive is prepared, you will need to create a preseed file. This file is used during the installation to automatically provide the values to the questions that you are asked normally during interactive installation. There are many options which can be found on &lt;a href=&quot;https://www.debian.org/releases/stable/s390x/apbs04.en.html&quot;&gt;Debian Appendix B&lt;/a&gt;, but my goal was to set up a basic server with SSH installed, as I later use Ansible to finish configuring the server as desired, thus your preseed file might differ based on your requirements. Empty lines or lines starting with # are ignored during installation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;preseed.cfg&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Set language, country, locale, keyboard 
d-i debian-installer/language string en
d-i debian-installer/country string US
d-i debian-installer/locale string en_US.UTF-8
d-i keyboard-configuration/xkb-keymap select us

# If possible, automatically selects a network interface that has a link 
d-i netcfg/choose_interface select auto
# Sets hostname and domain
d-i netcfg/get_hostname string hostname
d-i netcfg/get_domain string domain.com
# Disables WEP key dialog
d-i netcfg/wireless_wep string

# Set up mirror settings that may be used to download additional components for the installed and to setup sources.list
d-i mirror/country string manual
d-i mirror/http/hostname string http.us.debian.org
d-i mirror/http/directory string /debian
d-i mirror/suite string testing
d-i mirror/http/proxy string

# Specifies a disk to partition. If there is only one disk, installer automatically defaults to it
d-i partman-auto/disk string /dev/sda
# Specifies partition method to be LVM
d-i partman-auto/method string lvm
# Specifies to use all available space for LVM partition
d-i partman-auto-lvm/guided_size string max
# Skip the warning if there is an old LVM configuration
d-i partman-lvm/device_remove_lvm boolean true
# Skip the warning if there is an old RAID array
d-i partman-md/device_remove_md boolean true
# Skip the confirmation to write the LVM partition
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
# Specifies to put all files in one partition
d-i partman-auto/choose_recipe select atomic
# Makes partman automatically partition without confirmation
d-i partman-partitioning/confirm_write_new_label boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true

# Installs grub automatically to MBR if not other OS is detected on the machine
d-i grub-installer/only_debian boolean true
# Specifies a disk to which to install grub
# Required as installer might fail to properly identify between USB and the CD and without this option might still ask you to choose
d-i grub-installer/bootdev  string /dev/sda

# Set root password (might want to change this part if your USB will be shared)
passwd passwd/root-password password somepassword
passwd passwd/root-password-again password somepassword
# Skip creating additional users
passwd passwd/make-user boolean false

# Installs standard tools and SSH server
tasksel tasksel/first multiselect standard, ssh-server
# Accept to report back on what software is installed and what software is used
d-i popularity-contest/participate boolean true
# Skip the last message that installation is complete
d-i finish-install/reboot_in_progress note
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Put this file in the root directory of USB, it should look like:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/78643754/183289755-05aab8f2-c8af-4fd6-9532-4598dc03c57c.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;updating-grubcfg-isolinuxcfg-and-txtcfg&quot;&gt;Updating grub.cfg, isolinux.cfg and txt.cfg&lt;/h2&gt;
&lt;p&gt;With the preseed.cfg file in place, you need to tell the installer to use it. For that, you have to update 2 files - &lt;strong&gt;grub.cfg&lt;/strong&gt; and &lt;strong&gt;txt.cfg&lt;/strong&gt;.&lt;/p&gt;

&lt;h4 id=&quot;grubcfg&quot;&gt;grub.cfg&lt;/h4&gt;
&lt;p&gt;Using above image as root directory, you will find &lt;em&gt;grub.cfg&lt;/em&gt; in boot/grub directory - &lt;code&gt;boot/grub/grub.cfg&lt;/code&gt;, open the file and find the following lines:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;menuentry --hotkey=i 'Install' {
    set background_color=black
    linux    /install.amd/vmlinuz vga=788 --- quiet 
    initrd   /install.amd/initrd.gz
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Update the &lt;code&gt;linux&lt;/code&gt; line, so it would be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;menuentry --hotkey=i 'Install' {
    set background_color=black
    linux    /install.amd/vmlinuz preseed/file=/cdrom/preseed.cfg auto-install/enable=true vga=788 --- quiet  
    initrd   /install.amd/initrd.gz
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;preseed/file=/cdrom/preseed.cfg&lt;/code&gt; specifies the preseed file to use.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;auto-install/enable=true&lt;/code&gt; delays the asking of localization questions, without this line you wouldn’t be able to preseed them in preseed.cfg&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4 id=&quot;txtcfg&quot;&gt;txt.cfg&lt;/h4&gt;
&lt;p&gt;&lt;em&gt;txt.cfg&lt;/em&gt; file can be found by navigating from the root directory to isolinux - &lt;code&gt;isolinux/txt.cfg&lt;/code&gt;, open the file and edit the append line from:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;label install
	menu label ^Install
	kernel /install.amd/vmlinuz
	append vga=788 initrd=/install.amd/initrd.gz --- quiet 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;label install
	menu label ^Install
	kernel /install.amd/vmlinuz
        append preseed/file=/cdrom/preseed.cfg auto-install/enable=true vga=788 initrd=/install.amd/initrd.gz --- quiet
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h4 id=&quot;isolinuxcfg&quot;&gt;isolinux.cfg&lt;/h4&gt;
&lt;p&gt;Finally, we want to update &lt;em&gt;isolinux.cfg&lt;/em&gt; file, which can be found in the same directory as &lt;em&gt;txt.cfg&lt;/em&gt; - &lt;code&gt;isolinux/isolinux.cfg&lt;/code&gt;. We need to change &lt;code&gt;default&lt;/code&gt; line from &lt;code&gt;vesamenu.c32&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# D-I config version 2.0
# search path for the c32 support libraries (libcom32, libutil etc.)
path 
include menu.cfg
default vesamenu.c32
prompt 0
timeout 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To &lt;code&gt;install&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# D-I config version 2.0
# search path for the c32 support libraries (libcom32, libutil etc.)
path 
include menu.cfg
default install
prompt 0
timeout 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This allows us to skip the menu which asks you what type of installation you want and directly goes to install option.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;that-is-all&quot;&gt;That is all&lt;/h2&gt;
&lt;p&gt;Now you are ready - plug the USB into the device you want to install Debian to, select that USB as a boot option and watch how the installation goes through without any input required from you.&lt;/p&gt;</content><author><name>taulev</name></author><category term="Other" /><summary type="html">I have been setting up my home lab, which consists of 5 servers and instead of spending 30-40 minutes installing Debian interactively I decided to spend 4-5 hours automating the process, so all you would have to do is choose the boot option as USB for the OS to be installed. After trial and error of multiple re-installs, here are the steps I took to succeed:</summary></entry><entry><title type="html">Gke Access Management With Kubernetes Rbac</title><link href="https://taulev.github.io/2022/07/30/GKE-access-management-with-kubernetes-RBAC.html" rel="alternate" type="text/html" title="Gke Access Management With Kubernetes Rbac" /><published>2022-07-30T00:00:00+00:00</published><updated>2022-07-30T00:00:00+00:00</updated><id>https://taulev.github.io/2022/07/30/GKE-access-management-with-kubernetes-RBAC</id><content type="html" xml:base="https://taulev.github.io/2022/07/30/GKE-access-management-with-kubernetes-RBAC.html">&lt;p&gt;Lately, I have been working on setting up proper accesses for our GKE (Google Kubernetes Engine) cluster. There are 2 options for controlling access within GKE cluster - &lt;em&gt;GCP Identity and Access Management (IAM)&lt;/em&gt; and &lt;em&gt;Kubernetes role-based access control (RBAC)&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GCP IAM&lt;/strong&gt; - allows managing permissions to GCP resources, however when it comes to GKE it becomes a bit limited in its granularity. For example, you could assign a &lt;em&gt;Kubernetes Engine Viewer&lt;/em&gt; role to a user to give view permissions to Kubernetes resources, but you can’t limit it to a specific namespace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes RBAC&lt;/strong&gt; - allows managing permissions to objects within Kubernetes cluster, it is a great option if you need to assign fine-grained permissions, i.e., limit view access only to pods within specific namespace.&lt;/p&gt;

&lt;p&gt; 
We had the following requirements:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Users should be able to view and manage resources only within their namespace&lt;/li&gt;
  &lt;li&gt;Accesses should be easy to maintain - if you need to add/remove permissions for a user, it should be enough to add/remove that user from a list&lt;/li&gt;
  &lt;li&gt;Should be automated (no one likes manual work…)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;implementation&quot;&gt;Implementation&lt;/h2&gt;
&lt;p&gt;As we needed more granular control, we decided to create a custom GCP IAM role, which would grant minimal required GKE permissions and then leave the rest of access management to Kubernetes RBAC. We took the following steps:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Use Terraform for automation&lt;/li&gt;
  &lt;li&gt;Create a YAML file with list of users&lt;/li&gt;
  &lt;li&gt;Create and assign a custom GCP IAM role&lt;/li&gt;
  &lt;li&gt;Create Kubernetes Role and RoleBinding objects&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Note: I won’t be describing how to set up Terraform providers in this post, and will assume that you already have them. For convenience, we will have all Terraform resources described in one *main.tf&lt;/em&gt; file.&lt;/strong&gt;*&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3 id=&quot;creating-a-yaml-file&quot;&gt;Creating a YAML file&lt;/h3&gt;
&lt;p&gt;As Google Groups were not an option, we decided to hold the list of users in a YAML file (&lt;code&gt;users.yaml&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;first.user@gmail.com: [&quot;default&quot;, &quot;backend&quot;, &quot;frontend&quot;]
second.user@gmail.com: [&quot;frontend&quot;]
third.user@gmail.com: [&quot;backend&quot;]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, the format is pretty simple: &lt;code&gt;users_email: [list of namespaces to grant access to]&lt;/code&gt;, it allows us to easily add or remove users, as well as effortlessly control which namespaces they can access. We will be using &lt;code&gt;yamldecode&lt;/code&gt; to read this information in Terraform:&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;locals {
  users = yamldecode(file(&quot;users.yaml&quot;))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3 id=&quot;creating-and-assigning-custom-gcp-iam-role&quot;&gt;Creating and assigning custom GCP IAM role&lt;/h3&gt;
&lt;p&gt;As per &lt;a href=&quot;https://cloud.google.com/kubernetes-engine/docs/how-to/role-based-access-control#iam-interaction&quot;&gt;GKE docs&lt;/a&gt; at minimum users require &lt;code&gt;container.clusters.get&lt;/code&gt; permission, so users could authenticate with a cluster. We also decided to add &lt;code&gt;container.clusters.list&lt;/code&gt; to allow users to list those clusters.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &quot;google_project_iam_custom_role&quot; &quot;gke_minimal_permissions&quot; {
  role_id     = &quot;minimalGKEPermissions&quot;
  title       = &quot;Minimal GKE permissions&quot;
  permissions = [&quot;container.clusters.get&quot;, &quot;container.clusters.list&quot;]
  project     = &quot;project-name&quot;
  description = &quot;This role provides minimal GKE permissions&quot;
}

resource &quot;google_project_iam_member&quot; &quot;gke_minimal_role&quot; {
  for_each = local.users

  member  = &quot;user:${each.key}&quot;
  role    = google_project_iam_custom_role.gke_minimal_permissions.name
  project = &quot;project-name&quot;

}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3 id=&quot;create-kubernetes-role-and-rolebinding-objects&quot;&gt;Create Kubernetes Role and RoleBinding objects&lt;/h3&gt;
&lt;p&gt;Finally, we needed to create Role and RoleBinding objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Role Object&lt;/strong&gt; - is a set of permissions within a specific namespace.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;RoleBinding Object&lt;/strong&gt; - allows you to grant permissions of a Role object to list of Kubernetes subjects (users, groups, service accounts).&lt;/p&gt;

&lt;p&gt;Knowing above, we had to ensure that a Role would be created in all namespaces and that a RoleBinding would assign a Role in a correct namespace. For that, we had to format the input from &lt;code&gt;users.yaml&lt;/code&gt; file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;locals {
  users      = yamldecode(file(&quot;users.yaml&quot;))
  namespaces = [&quot;default&quot;, &quot;frontend&quot;, &quot;backend&quot;]
  members = flatten(
    [for k, v in local.users :
      [for ns in v :
        { user = k, namespace = ns }
      ]
    ]
  )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We loop through every user and then loop again through every namespace assigned to a user to create a list of objects in format:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;namespace&quot; = &quot;namespace_name&quot;
  &quot;user&quot;  = &quot;users_email&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;flatten()&lt;/code&gt; function creates one list from multiple lists and is critical here, as without it, we would just have a list of list of objects, which would be of no use for us. With our &lt;code&gt;users.yaml&lt;/code&gt; file, the output would be:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/78643754/181909443-7afde914-8128-4a41-a994-fb88631ad659.PNG&quot; alt=&quot;members&quot; /&gt;&lt;/p&gt;

&lt;p&gt; 
Having data in a format we can use, we can finally create Role and RoleBinding objects (we will be granting view permissions for all resources in namespaces that are assigned to a user):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;resource &quot;kubernetes_role&quot; &quot;resource_viewer&quot; {
  for_each = toset(local.namespaces)

  metadata {
    name      = &quot;resource-viewer&quot;
    namespace = each.key
  }

  rule {
    api_groups = [&quot;&quot;]
    resources  = [&quot;*&quot;]
    verbs      = [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  }
}

resource &quot;kubernetes_role_binding&quot; &quot;resource_viewer_users&quot; {
  for_each = { for member in local.members : &quot;${member.user}-${member.namespace}&quot; =&amp;gt; member }

  metadata {
    name      = &quot;resource-viewer-user&quot;
    namespace = each.value.namespace
  }

  role_ref {
    api_group = &quot;rbac.authorization.k8s.io&quot;
    kind      = &quot;Role&quot;
    name      = kubernetes_role.resource_viewer[each.value.namespace].metadata[0].name
  }

  subjects {
    api_group = &quot;rbac.authorization.k8s.io&quot;
    kind      = &quot;User&quot;
    name      = each.value.user
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h4 id=&quot;few-things-to-note&quot;&gt;Few things to note&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;for_each = toset(local.namespaces)&lt;/code&gt; we are converting a list to a set, so we could use &lt;code&gt;for_each&lt;/code&gt; instead of &lt;code&gt;count&lt;/code&gt;. It is important for setting correct Role name when creating a RoleBinding.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;for_each = { for member in local.members : &quot;${member.user}-${member.namespace}&quot; =&amp;gt; member }&lt;/code&gt; from our formatted data we are creating a new object, so we could access required fields. &lt;code&gt;&quot;${member.user}-${member.namespace}&quot; =&amp;gt; member&lt;/code&gt; this part specifies how the new object key =&amp;gt; value should look. With our data, we have:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/78643754/181914014-9f2fe3b6-971f-4b67-887a-5c158ae2025f.PNG&quot; alt=&quot;data&quot; /&gt;&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;complete-maintf&quot;&gt;Complete main.tf&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;locals {
  users      = yamldecode(file(&quot;users.yaml&quot;))
  namespaces = [&quot;default&quot;, &quot;frontend&quot;, &quot;backend&quot;]
  members = flatten(
    [for k, v in local.users :
      [for ns in v :
        { user = k, namespace = ns }
      ]
    ]
  )
}

resource &quot;google_project_iam_custom_role&quot; &quot;gke_minimal_permissions&quot; {
  role_id     = &quot;minimalGKEPermissions&quot;
  title       = &quot;Minimal GKE permissions&quot;
  permissions = [&quot;container.clusters.get&quot;, &quot;container.clusters.list&quot;]
  project     = &quot;project-name&quot;
  description = &quot;This role provides minimal GKE permissions&quot;
}

resource &quot;google_project_iam_member&quot; &quot;gke_minimal_role&quot; {
  for_each = local.users

  member  = &quot;user:${each.key}&quot;
  role    = google_project_iam_custom_role.gke_minimal_permissions.name
  project = &quot;project-name&quot;

  depends_on = [google_project_iam_custom_role.gke_minimal_permissions]
}

resource &quot;kubernetes_role&quot; &quot;resource_viewer&quot; {
  for_each = toset(local.namespaces)

  metadata {
    name      = &quot;resource-viewer&quot;
    namespace = each.key
  }

  rule {
    api_groups = [&quot;&quot;]
    resources  = [&quot;*&quot;]
    verbs      = [&quot;get&quot;, &quot;list&quot;, &quot;watch&quot;]
  }
}

resource &quot;kubernetes_role_binding&quot; &quot;resource_viewer_users&quot; {
  for_each = { for member in local.members : &quot;${member.user}-${member.namespace}&quot; =&amp;gt; member }

  metadata {
    name      = &quot;resource-viewer-user&quot;
    namespace = each.value.namespace
  }

  role_ref {
    api_group = &quot;rbac.authorization.k8s.io&quot;
    kind      = &quot;Role&quot;
    name      = kubernetes_role.resource_viewer[each.value.namespace].metadata[0].name
  }

  subjects {
    api_group = &quot;rbac.authorization.k8s.io&quot;
    kind      = &quot;User&quot;
    name      = each.value.user
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;final-words&quot;&gt;Final words&lt;/h2&gt;
&lt;p&gt;GCP IAM is a great tool to manage GKE permissions, if you do not need fine-grained control, however if more precise permissions are required, Kubernetes RBAC is the way to go. As seen above, Kubernetes RBAC can be easily managed in an automated way with Terraform.&lt;/p&gt;</content><author><name>taulev</name></author><category term="Other" /><summary type="html">Lately, I have been working on setting up proper accesses for our GKE (Google Kubernetes Engine) cluster. There are 2 options for controlling access within GKE cluster - GCP Identity and Access Management (IAM) and Kubernetes role-based access control (RBAC).</summary></entry><entry><title type="html">How We Chose Our Goteleport Setup</title><link href="https://taulev.github.io/2022/07/22/how-we-chose-our-goTeleport-setup.html" rel="alternate" type="text/html" title="How We Chose Our Goteleport Setup" /><published>2022-07-22T00:00:00+00:00</published><updated>2022-07-22T00:00:00+00:00</updated><id>https://taulev.github.io/2022/07/22/how-we-chose-our-goTeleport-setup</id><content type="html" xml:base="https://taulev.github.io/2022/07/22/how-we-chose-our-goTeleport-setup.html">&lt;p&gt;Recently, I had the pleasure of setting up GoTeleport in highly available fashion. As described in their &lt;a href=&quot;https://goteleport.com/docs&quot;&gt;GoTeleport&lt;/a&gt; docs:&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Teleport is a certificate authority and access plane for your infrastructure. With Teleport you can:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;Use a single solution to access your SSH servers, Kubernetes clusters, databases, desktops, and web applications.&lt;/li&gt;
    &lt;li&gt;Define sophisticated access policies for every component of your infrastructure, with fine-grained audit logs and session recordings.&lt;/li&gt;
    &lt;li&gt;Automatically on– and off-board users via integrations with single sign-on providers like GitHub, Okta, and Google Workspace.&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt; 
We required high availability as our infrastructure would be reachable only via Teleport, so downtime meant users wouldn’t be able to reach required servers, thus we wanted no downtime or at most few minutes of it if one of Teleport components would fail.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;initial-setup&quot;&gt;Initial setup&lt;/h2&gt;
&lt;p&gt;Teleport has the following data types:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Core cluster state - cluster configuration and identity&lt;/li&gt;
  &lt;li&gt;Audit events - events from the audit log&lt;/li&gt;
  &lt;li&gt;Session recordings - raw terminal recordings of interactive sessions&lt;/li&gt;
  &lt;li&gt;Teleport instance state - ID and credentials of a non-auth teleport instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of the data type has its own supported storage backends - default is a local directory, but GCP/AWS are also supported (teleport instance state data type supports only local directory). Core cluster state can also be stored in etcd, which is great as GCP/AWS were not an option for us.&lt;/p&gt;

&lt;p&gt;Our initial setup was 1 Load Balancer, 2 Teleport proxy servers, 2 Teleport auth servers and 3 etcd servers:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/78643754/180383573-6dbe4670-7083-4495-8758-38b2d2b549e7.jpg&quot; alt=&quot;Original teleport&quot; /&gt;&lt;/p&gt;

&lt;p&gt;This setup had the following benefits:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Different Teleport components were in different data centers&lt;/li&gt;
  &lt;li&gt;Easy to scale&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h3 id=&quot;different-data-centers&quot;&gt;Different data centers&lt;/h3&gt;
&lt;p&gt;Different components were in different data centers, meaning if Teleport Proxy 1 datacenter would experience issues, Teleport Proxy 2 would still be up (same for Teleport Auth and etcd servers). This means that some serious problems would need to occur for Teleport to be completely down.&lt;/p&gt;

&lt;h3 id=&quot;scaling&quot;&gt;Scaling&lt;/h3&gt;
&lt;p&gt;This setup also made it effortless to scale the Teleport cluster, as you could just add required components.&lt;/p&gt;

&lt;p&gt; 
However, this setup has few drawbacks.&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Audit events and session recordings are on separate Teleport auth servers&lt;/li&gt;
  &lt;li&gt;Price&lt;/li&gt;
  &lt;li&gt;Speed&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;audit-events-and-session-recordings&quot;&gt;Audit events and session recordings&lt;/h3&gt;
&lt;p&gt;If you were to try to inspect logs/recordings, you wouldn’t see the whole picture. This wasn’t a dealbreaker as audit events could be aggregated easily as they are in JSON format. Each session recording is in a separate file, so gathering them in one place wouldn’t be an issue. This would be a bigger problem if one would only rely on Teleport Web UI to view this information.&lt;/p&gt;

&lt;h3 id=&quot;price&quot;&gt;Price&lt;/h3&gt;
&lt;p&gt;More servers you have, more money you pay. Though the costs weren’t concerning, it still felt a little of an overkill as our infrastructure doesn’t consist of many servers or users.&lt;/p&gt;

&lt;h3 id=&quot;speed&quot;&gt;Speed&lt;/h3&gt;
&lt;p&gt;This was the part which caused us to reconsider our setup. Connection time increased by 3-4 seconds, and it was even worse via VPN. This doesn’t sound too bad, but it felt too much for our infrastructure.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;current-setup&quot;&gt;Current setup&lt;/h2&gt;
&lt;p&gt;After some discussion, we decide to go with much simpler option - Active/Passive setup with 2 servers each of them has both Teleport Proxy and Teleport Auth and stores all data types locally:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/78643754/180402907-cc02cf08-5470-4214-8c63-d483174be8ad.jpg&quot; alt=&quot;Current teleport&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Compared to original setup, we observed following advantages:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Speed increase&lt;/li&gt;
  &lt;li&gt;Cost reduction&lt;/li&gt;
  &lt;li&gt;Fewer things to maintain&lt;/li&gt;
  &lt;li&gt;Audit events and session recordings in one place&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;speed-increase&quot;&gt;Speed increase&lt;/h3&gt;
&lt;p&gt;With this setup, connection time increase is up to 2 seconds via VPN, and it is barely noticeable without VPN.&lt;/p&gt;

&lt;h3 id=&quot;cost-reduction&quot;&gt;Cost reduction&lt;/h3&gt;
&lt;p&gt;From 8 servers, it went down to 2, which naturally reflects in costs.&lt;/p&gt;

&lt;h3 id=&quot;fewer-things-to-maintain&quot;&gt;Fewer things to maintain&lt;/h3&gt;
&lt;p&gt;Apart from cost reduction, you also have fewer things to maintain, it is a lot easier to secure and keep software up to date of 2 servers than 8.&lt;/p&gt;

&lt;h3 id=&quot;audit-events-and-session-recordings-1&quot;&gt;Audit events and session recordings&lt;/h3&gt;
&lt;p&gt;Now as you have only 1 active Teleport Auth server at a time all session recordings and audit event logs will be in one place, so aggregating is no longer necessary and Teleport Web UI become reliable for inspecting them.&lt;/p&gt;

&lt;p&gt; 
Sadly, everything has its disadvantages:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Core cluster state backups are a must&lt;/li&gt;
  &lt;li&gt;Harder to scale&lt;/li&gt;
  &lt;li&gt;Downtime&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;backups&quot;&gt;Backups&lt;/h3&gt;
&lt;p&gt;In the original setup, core cluster state was stored in etcd cluster, meaning if one of the Teleport Auth servers would go down, the second server would be fine. Now, if an active Teleport Auth server goes down, and you don’t have a backup of a core cluster state, you will lose all user data. Luckily, it is an SQLite file, so backing it up is a minor inconvenience.&lt;/p&gt;

&lt;h3 id=&quot;scaling-1&quot;&gt;Scaling&lt;/h3&gt;
&lt;p&gt;Since Teleport Proxy and Auth are on the same server, it became a lot harder to scale. Scaling Teleport Auth in this setup is impossible and to scale Teleport Proxy you would need to separate components.&lt;/p&gt;

&lt;h3 id=&quot;downtime&quot;&gt;Downtime&lt;/h3&gt;
&lt;p&gt;If an active Teleport server goes under, you would instantly have downtime, which would last until you switch DNS records to point to the passive server. Furthermore, if backed up core cluster state is not present on a passive server, there would be additional downtime until it would be moved there. As our cluster state is backed up few times a day, and it is always present on a passive server, downtime wouldn’t last longer than few minutes, which was fine with us.&lt;/p&gt;

&lt;p&gt;Possible user data inconsistency is another minor problem that rises due to this setup. If the active Teleport server goes down some hours after the latest backup, a passive server might not have the newest information. One of the scenarios where user could feel it would be password change, if a user changes the password and the backup didn’t go through yet, after the switch to passive server user’s password would be the old one. Luckily, it is something that we are alright with.&lt;/p&gt;

&lt;p&gt; &lt;/p&gt;
&lt;h2 id=&quot;wrapping-up&quot;&gt;Wrapping up&lt;/h2&gt;
&lt;p&gt;As discussed, each setup has its advantages and disadvantages, so in the end we had to make a choice and sacrifice some advantages of one setup to get the benefits of another. Fortunately, our requirements allowed us some downtime, which in turn led to faster connection times, reduced maintenance work and costs.&lt;/p&gt;</content><author><name>taulev</name></author><category term="Other" /><summary type="html">Recently, I had the pleasure of setting up GoTeleport in highly available fashion. As described in their GoTeleport docs: Teleport is a certificate authority and access plane for your infrastructure. With Teleport you can: Use a single solution to access your SSH servers, Kubernetes clusters, databases, desktops, and web applications. Define sophisticated access policies for every component of your infrastructure, with fine-grained audit logs and session recordings. Automatically on– and off-board users via integrations with single sign-on providers like GitHub, Okta, and Google Workspace.</summary></entry></feed>