<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Alen Komljen]]></title><description><![CDATA[Doing DevOps the Cloud Native Way.]]></description><link>https://akomljen.com/</link><image><url>https://akomljen.com/favicon.png</url><title>Alen Komljen</title><link>https://akomljen.com/</link></image><generator>Ghost 3.40</generator><lastBuildDate>Mon, 12 Jan 2026 20:40:32 GMT</lastBuildDate><atom:link href="https://akomljen.com/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Running Java Apps on Kubernetes ARM Nodes]]></title><description><![CDATA[<p><strong>This article is originally posted on the Faire’s technical blog - <a href="https://medium.com/faire-the-craft">The Craft</a>.</strong></p><p>Major cloud providers like Amazon are betting on custom-built ARM processors. Amazon built the first version of the <a href="https://aws.amazon.com/ec2/graviton/">Graviton processor</a> in 2018. Two years later, they introduced a new version, Graviton2, with some significant improvements and</p>]]></description><link>https://akomljen.com/running-java-apps-on-kubernetes-arm-nodes/</link><guid isPermaLink="false">601442348a12d50001bbb8eb</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[docker]]></category><category><![CDATA[arm]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Fri, 29 Jan 2021 17:29:15 GMT</pubDate><media:content url="https://akomljen.com/content/images/2021/01/100-Potrero-office.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2021/01/100-Potrero-office.jpg" alt="Running Java Apps on Kubernetes ARM Nodes"><p><strong>This article is originally posted on the Faire’s technical blog - <a href="https://medium.com/faire-the-craft">The Craft</a>.</strong></p><p>Major cloud providers like Amazon are betting on custom-built ARM processors. Amazon built the first version of the <a href="https://aws.amazon.com/ec2/graviton/">Graviton processor</a> in 2018. Two years later, they introduced a new version, Graviton2, with some significant improvements and a 40% better price/performance over comparable x86-based instances. Those are big numbers.</p><p>Also, you probably heard about Apple's M1 ARM-based SoC and how good it is. Soon, likely all Mac lineups will be powered by ARM. There is a really interesting post on <a href="https://debugger.medium.com/why-is-apples-m1-chip-so-fast-3262b158cba2">why M1 is superior</a> and different from traditional CPUs, which I highly recommend you to read if you want to learn more.</p><p>At Faire, we are exploring Amazon's Graviton2 based instances to run Java/Kotlin apps on the Kubernetes platform for some gains in performance and lower prices. Running Kubernetes on ARM instances and building ARM containers sounds like a significant change. However, it is not that complicated because Kubernetes and docker are built with multiple architectures in mind. Let’s see how it works.</p><h3 id="kubernetes-arm-nodes-on-aws">Kubernetes ARM Nodes on AWS</h3><p>Our Kubernetes cluster is running on AWS EKS. The setup can have small differences for other cloud providers or standalone installations, but it should be similar. It doesn't matter which platform your master nodes are running, as you will not run any of your apps on those.</p><p>Before you can utilize Amazon ARM instances for Kubernetes worker nodes, there are a few preparation steps. The first step would be to add another node group of ARM instances. There is nothing special about it, except that you need to choose an ARM-based instance type, for example, M6g (g stands for Graviton2). It is pretty simple and <a href="https://aws.amazon.com/about-aws/whats-new/2020/08/amazon-eks-support-for-arm-based-instances-powered-by-aws-graviton-now-generally-available/">officially supported</a> in EKS, so please check the docs.</p><p>In a nutshell, ARM support means that you have to use ARM-based instance type, ARM OS, with all dependencies like docker, kubelet daemon, etc., built for ARM. When adding a new Kubernetes node group, I suggest that you taint and label those nodes to prevent running non-compatible containers.</p><p>We use the following services on each worker node, and in practice, this means that each one of those containers needs to be compatible with ARM architecture:</p><ul><li>AWS node (AWS VPC native CNI plugin)</li><li>kube-proxy</li><li><a href="https://akomljen.com/integrating-aws-iam-and-kubernetes-with-kube2iam/">kube2iam</a> (IAM authentication)</li><li>Datadog agent</li><li>Linkerd proxy</li></ul><p>Only the kube2iam image wasn't available for the ARM platform when writing this post, and we had to build it. The process is straightforward so let's get into that.</p><h3 id="docker-multi-architecture-images">Docker Multi Architecture Images</h3><p>There is an architecture label for each docker image. You can check the architecture of the image with the <code>docker image inspect</code> command, for example:</p><pre><code>$ docker pull openjdk:15
$ docker image inspect openjdk:15 --format='{{.Architecture}}'
amd64
</code></pre><p>Docker pulled the amd64 image because it's running on an amd64 machine in this case. If you run the same command on the ARM platform, you would probably get arm64 as an architecture label. So, how is this possible, the image has the same tag?</p><p>One way to achieve this is by creating a <a href="https://docs.docker.com/engine/reference/commandline/manifest/">docker manifest</a> list for identical images in function available for different architectures. First, you can create two images with separate tags, e.g., <code>openjdk:15-amd64</code> and <code>openjdk:15-arm64</code>, and then create a manifest list combining those two images as <code>openjdk:15</code> and push it to the registry. You will see the whole process with the Java app example below.</p><h3 id="docker-buildx-plugin">Docker Buildx Plugin</h3><p>Buildx is a docker plugin that extends the docker build command with the full support of <a href="https://github.com/moby/buildkit">BuildKit</a>. BuildKit library is bundled into docker daemon. One of many interesting features of BuildKit is that it is designed to work for building multi-platform images and not relying on underneath architecture and operating system.</p><p>If you are using <strong>Docker for Desktop</strong> on Mac, you can enable experimental features in preferences to use buildx. Make sure you are running Docker for Desktop version 3.0.0 and up. Older versions had experimental features in Edge builds only.</p><p>To verify that buildx is available on the system, run:</p><pre><code>$ docker buildx ls
NAME/NODE DRIVER/ENDPOINT STATUS  PLATFORMS
default * docker
  default default         running linux/amd64, linux/arm64, linux/riscv64, linux/ppc64le, linux/s390x, linux/386, linux/arm/v7, linux/arm/v6</code></pre><p>From the above output, you can see all supported platform versions.</p><p><strong>On Linux nodes</strong>, the easiest way to build multi-architecture images is by utilizing QEMU, a well-known machine emulator and virtualizer. The setup is pretty straightforward as well, and here is a short version of it.</p><p>First, you have to install the latest buildx plugin:</p><pre><code>$ wget https://github.com/docker/buildx/releases/download/v0.5.1/buildx-v0.5.1.linux-amd64
$ mkdir ~/.docker/cli-plugins
$ mv buildx-v0.5.1.linux-amd64 ~/.docker/cli-plugins/docker-buildx
$ chmod a+x ~/.docker/cli-plugins/docker-buildx</code></pre><p>The command <code>docker buildx</code> should work now. The next step is to install a cross-platform emulator collection distributed as a docker image:</p><pre><code>$ docker run --privileged --rm tonistiigi/binfmt --install all
</code></pre><p>The above command should install and print out all supported emulators. Now let's start the new multi-platform builder instance:</p><pre><code>$ docker buildx create --name multiplatform
$ docker buildx use multiplatform</code></pre><p>Running <code>docker buildx inspect --bootstrap</code> should show all supported platforms. For more details on this setup and how it works under the hood, please check <a href="https://github.com/docker/buildx">buildx docs</a>.</p><p>Once the buildx is ready, you can try to build an ARM kube2iam image with it. Kube2iam is written in Go, which means that you need to cross-compile the code for different platforms to produce a valid binary. This is true for many other programming languages as well. Let’s try it:</p><pre><code>$ git clone https://github.com/jtblin/kube2iam.git
$ cd kube2iam

$ docker buildx build \
    --platform linux/arm64 \
    -t fairewholesale/kube2iam:0.10.11-arm64 .</code></pre><p>The above build image process with buildx and arm64 platform automatically produce ARM binaries because Dockerfile for kube2iam has a build stage in it as well, and docker engine will automatically pull the ARM version of builder image <code>golang:1.14.0</code>.</p><h3 id="java-apps-on-arm">Java Apps on ARM</h3><p>The whole point of this blog post was to see how you can run the Java apps. It turns out to be the easiest part after you figure out how to run ARM nodes and build docker images that can run on them.</p><p>You already saw how to build a kube2iam container for ARM, so you might be wondering how Java is different? For Java apps, the only thing you need to have is JVM binaries capable of running on the ARM platform. You don't need to cross-compile the code like in the previous example. When producing an ARM container that will run Java code, you can compile code on any platform, then copy the build artifact to the ARM platform and build the image there. What you will get, however, is a docker image capable of running on the ARM platform only.</p><p>Let's see this through a simple Java app and docker build process. Get the app and run it locally:</p><pre><code>$ git clone https://github.com/Faire/javaosarch
$ cd javaosarch
$ gradle run

&gt; Task :run
OS name : Mac OS X
OS arch : x86_64

BUILD SUCCESSFUL in 1s
2 actionable tasks: 2 executed</code></pre><p>Assuming you also have the <code>x86_64</code> platform above, now build the jar and create a docker image on the arm64 platform:</p><pre><code>$ gradle build

BUILD SUCCESSFUL in 943ms
5 actionable tasks: 4 executed, 1 up-to-date

$ docker buildx build \
    --platform linux/arm64 \
    -t fairewholesale/javaosarch:1.0-arm64 .

$ docker run \
    --platform linux/arm64 \
    fairewholesale/javaosarch:1.0-arm64 \
    java -cp javaosarch-1.0.jar javaosarch.JavaOsArch
OS name : Linux
OS arch : aarch64</code></pre><p>From this example, you can see that it is not important on which platform you compile the Java code. The only thing that matters is that you build a container for ARM.</p><p>Now, let's build the amd64 version of the above image and create a manifest to combine both images under the same tag:</p><pre><code>$ docker buildx build \
    --platform linux/amd64 \
    -t fairewholesale/javaosarch:1.0-amd64 .

$ docker push fairewholesale/javaosarch:1.0-arm64
$ docker push fairewholesale/javaosarch:1.0-amd64

$ docker manifest create \
    fairewholesale/javaosarch:1.0 \
    --amend fairewholesale/javaosarch:1.0-arm64 \
    --amend fairewholesale/javaosarch:1.0-amd64

$ docker manifest push fairewholesale/javaosarch:1.0</code></pre><p>You can also inspect the manifest, which will show the information about available image architectures:</p><pre><code>$ docker manifest inspect fairewholesale/javaosarch:1.0 | jq .manifests[].platform
{
  "architecture": "amd64",
  "os": "linux"
}
{
  "architecture": "arm64",
  "os": "linux"
}
</code></pre><p>And that should be it, the <a href="https://hub.docker.com/r/fairewholesale/javaosarch/tags">fairewholesale/javaosarch:1.0</a> will work on both amd64 and arm64 machines.</p><p>When setting up a deployment for Kubernetes, you don’t need to take care of which image a particular node will pull. Docker engine will figure that out and pull the right image based on its running platform.</p><p>Some other important details you need to be aware of:</p><ul><li>If you are using natively-built libraries through JNI, you may need to get those libraries for the ARM platform.</li><li>The official OpenJDK image starting from openjdk:15 is available for the ARM platform. Older versions are not supported.</li><li>The final container image may have some other dependencies, small binaries that you want to include, and all of those need to be compiled for ARM. In our case, that was <a href="https://github.com/krallin/tini">tini</a> and <a href="https://github.com/olix0r/linkerd-await">linkerd-await</a>.</li></ul><h3 id="summary">Summary</h3><p>We are still experimenting with ARM builds. The hardest part is changing the CI pipeline, where we build a bunch of other stuff. Jenkins nodes are one of the most significant computing expenses, and the speed of our builds is critical to us. So, using Graviton2 instances would be a big money saver, maybe a time saver as well. I cannot talk about real-world performance, as this is still a work in progress. One thing is sure, Java is ready for production deployments on ARM, and it seems like the right time to explore it further.</p>]]></content:encoded></item><item><title><![CDATA[Stopping Docker Containers Gracefully]]></title><description><![CDATA[<p><strong>This is a post from my old blog, originally written in 2015. The old blog is gone, and I decided to repost it here to redirect old links. The content is just slightly adjusted.</strong></p><p>I started to work with Docker containers seven years ago. I made my first <a href="https://github.com/komljen/dockerfile-examples">Docker playground</a></p>]]></description><link>https://akomljen.com/stopping-docker-containers-gracefully/</link><guid isPermaLink="false">5ecd0260f73d4a000185265f</guid><category><![CDATA[docker]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Wed, 27 May 2020 10:31:38 GMT</pubDate><media:content url="https://akomljen.com/content/images/2020/05/florian-schmaezz-ByX4Xoy4ifo-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2020/05/florian-schmaezz-ByX4Xoy4ifo-unsplash.jpg" alt="Stopping Docker Containers Gracefully"><p><strong>This is a post from my old blog, originally written in 2015. The old blog is gone, and I decided to repost it here to redirect old links. The content is just slightly adjusted.</strong></p><p>I started to work with Docker containers seven years ago. I made my first <a href="https://github.com/komljen/dockerfile-examples">Docker playground</a> with a bunch of different images. As I began to work on enterprise-level applications deployment, I found out that there were a lot of things I was doing wrong. One of them was how I started applications inside a container.</p><p>Almost all my Dockerfiles have some bash script at the end to make some minor changes before starting the application. I usually add a bash script to <code>CMD</code> instruction in the Dockerfile. I thought there isn't anything wrong with that, except that <code>docker stop</code> didn't work as it should.</p><h3 id="docker-containers-and-pid-1">Docker Containers and PID 1</h3><p>When you run a bash script in a container, it will get PID 1, and the application will be a child process as PPID 1. This is a problem because Bash will not forward the <a href="https://www.gnu.org/software/libc/manual/html_node/Termination-Signals.html">termination signal</a> SIGTERM to the app on container stop instruction. Instead, Docker kills the container after 10 seconds. You can adjust the stop timeout, of course, but the main reason it exists is if the application needs more time to stop gracefully.</p><p>There is an easy way to handle this with the <a href="https://www.man7.org/linux/man-pages/man3/exec.3.html">exec</a> command inside a bash script. Exec will replace the shell without creating a new process, and the application will get PID 1 instead.<br><br>Let’s test both scenarios first. For testing purposes, you can use this simple Redis Dockerfile:</p><pre><code>FROM ubuntu:trusty
ENV DEBIAN_FRONTEND noninteractive

RUN \
  apt-get update &amp;&amp; \
  apt-get -y install \
          software-properties-common &amp;&amp; \
  add-apt-repository -y ppa:chris-lea/redis-server &amp;&amp; \
  apt-get update &amp;&amp; \
  apt-get -y install \
          redis-server &amp;&amp; \
  rm -rf /var/lib/apt/lists/*

COPY start.sh start.sh
RUN chmod +x start.sh

EXPOSE 6379

RUN rm /usr/sbin/policy-rc.d
CMD ["/start.sh"]</code></pre><p>And here is the <code>start.sh</code> script which will change recommended kernel settings and start Redis server:</p><pre><code>#!/usr/bin/env bash

# Disable THP Support in kernel
echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled
# TCP backlog setting (defaults to 128)
sysctl -w net.core.somaxconn=16384
#---------------------------------------------------------------
/usr/bin/redis-server</code></pre><p>Now let’s build and run this container (privileged option must be set to true when starting a container because of kernel changes):</p><pre><code>docker build -t my/redis .
docker run -d --privileged --name test my/redis</code></pre><p>Then check for the processes running inside the container:</p><pre><code>docker exec test ps -ef
  UID        PID  PPID  C STIME TTY          TIME CMD
  root         1     0  0 13:20 ?        00:00:00 bash /start.sh
  root         6     1  0 13:20 ?        00:00:00 /usr/bin/redis-server *:6379</code></pre><p>As you can see, Redis is running as PID 6, which is why graceful stop doesn't work. Let’s try to stop this container with a <code>docker stop test</code>. Docker will kill the container after 10 seconds. If you check container logs <code>docker logs test</code>, the last message will be <code>Ready to accept connections</code>, meaning Redis didn't receive termination signal.</p><p>The simplest way of gracefully stopping Redis container is to change the last line in <code>start.sh</code> script to <code>exec /usr/bin/redis-server</code>:</p><pre><code>#!/usr/bin/env bash

# Disable THP Support in kernel
echo never &gt; /sys/kernel/mm/transparent_hugepage/enabled
# TCP backlog setting (defaults to 128)
sysctl -w net.core.somaxconn=16384
#---------------------------------------------------------------
exec /usr/bin/redis-server</code></pre><p>Rebuild the image, start the container, and again check for running processes:</p><pre><code>docker exec test ps -ef
  UID        PID  PPID  C STIME TTY          TIME CMD
  root         1     0  1 13:24 ?        00:00:00 /usr/bin/redis-server *:6379</code></pre><p>As you can see now, Redis is running as PID 1, and <code>docker stop</code> will work just fine. Let's try it first and recheck the Docker logs. You should see this message in the Redis log <code>Received SIGTERM scheduling shutdown...</code></p><p>In the above cases, Redis is running as root user, which a bad practice. Here are a few more examples of how I’m using exec with Postgres and Tomcat containers where processes are not running with root user:</p><pre><code>exec sudo -E -u tomcat7 ${CATALINA_HOME}/bin/catalina.sh run
exec su postgres -c "${POSTGRES_BIN} -D ${PGDATA} -c config_file=${CONF}"</code></pre><p>I will not go into the details for the above commands, but in this case, processes will not be running as PID 1 because of <code>sudo</code> and <code>su</code> commands. However, Docker stop works correctly in both cases. Those commands will forward the SIGTERM signal to child processes, unlike Bash.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet" data-width="550"><p lang="en" dir="ltr">Interesting post on &quot;Stopping <a href="https://twitter.com/Docker?ref_src=twsrc%5Etfw">@Docker</a> <a href="https://twitter.com/hashtag/Containers?src=hash&amp;ref_src=twsrc%5Etfw">#Containers</a> Gracefully&quot; by <a href="https://twitter.com/alenkomljen?ref_src=twsrc%5Etfw">@alenkomljen</a> via last week&#39;s <a href="https://twitter.com/hashtag/DockerWeekly?src=hash&amp;ref_src=twsrc%5Etfw">#DockerWeekly</a>: <a href="http://t.co/KSibQSBJ8c">http://t.co/KSibQSBJ8c</a> <a href="https://twitter.com/hashtag/Docker?src=hash&amp;ref_src=twsrc%5Etfw">#Docker</a></p>&mdash; Docker (@Docker) <a href="https://twitter.com/Docker/status/628788890472681472?ref_src=twsrc%5Etfw">August 5, 2015</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[Kubernetes Backup and Restore with Velero]]></title><description><![CDATA[<p>Recently I migrated some Kubernetes clusters, managed by Amazon EKS. The clusters were running in public subnets, so I wanted to make them more secure by utilizing private and public subnets where needed. Changing networking settings is not possible once you create the service in AWS. Any service, not just</p>]]></description><link>https://akomljen.com/kubernetes-backup-and-restore-with-velero/</link><guid isPermaLink="false">5e9aa7742a9f5400015aa5d5</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[addons]]></category><category><![CDATA[velero]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sat, 23 May 2020 20:02:59 GMT</pubDate><media:content url="https://akomljen.com/content/images/2020/05/maksym-kaharlytskyi-Q9y3LRuuxmg-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2020/05/maksym-kaharlytskyi-Q9y3LRuuxmg-unsplash.jpg" alt="Kubernetes Backup and Restore with Velero"><p>Recently I migrated some Kubernetes clusters, managed by Amazon EKS. The clusters were running in public subnets, so I wanted to make them more secure by utilizing private and public subnets where needed. Changing networking settings is not possible once you create the service in AWS. Any service, not just EKS. Since I already had <a href="https://velero.io/">Velero</a> installed for backups with S3 provider, the most natural thing was to use it to restore all resources on the new cluster as well.</p><h2 id="velero-installation">Velero Installation</h2><blockquote>Velero (formerly Heptio Ark) gives you tools to back up and restore your Kubernetes cluster resources and persistent volumes. </blockquote><p>With Velero, you can do disaster recovery, data migration, and data protection. Once installed and running, it will backup all Kubernetes resources to S3 compatible object store and make a snapshot of persistent volumes. It supports all major cloud providers and <a href="https://velero.io/docs/master/supported-providers/">many more</a>.<br><br><strong>NOTE:</strong> Installation instructions assume that you are familiar with tools like kube2iam or kiam for providing secure access to AWS resources. If you are not, please check my post first <a href="https://akomljen.com/integrating-aws-iam-and-kubernetes-with-kube2iam/">Integrating AWS IAM and Kubernetes with kube2iam</a>.<br><br>You have to do some preparation steps. Install <a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-install.html">AWS CLI</a>, and follow the below commands.<br><br>1. Create an S3 bucket, and make it private:</p><pre><code>aws s3api create-bucket \
  --bucket velero-test-backups \
  --region us-east-1

aws s3api put-public-access-block --bucket velero-test-backups \
  --public-access-block-configuration "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true" \
  --region us-east-1</code></pre><p>2. Create an IAM Role which will be assumed by Velero pod (more details on node trust policy in <a href="https://akomljen.com/integrating-aws-iam-and-kubernetes-with-kube2iam/">kube2iam blog post</a> mentioned before):</p><pre><code>cat &gt; node-trust-policy.json &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::1234567890:role/k8s-worker-nodes-NodeInstanceRole-1W9NK0A56SMQ6"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

aws iam create-role \
  --role-name k8s-velero \
  --assume-role-policy-document \
  file://node-trust-policy.json</code></pre><p>3. Create and attach IAM policy to previously created IAM role for EC2 and S3 access:</p><pre><code>cat &gt; s3-velero-policy.json &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeVolumes",
        "ec2:DescribeSnapshots",
        "ec2:CreateTags",
        "ec2:CreateVolume",
        "ec2:CreateSnapshot",
        "ec2:DeleteSnapshot"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:DeleteObject",
        "s3:PutObject",
        "s3:AbortMultipartUpload",
        "s3:ListMultipartUploadParts"
      ],
      "Resource": [
        "arn:aws:s3:::velero-test-backups/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::velero-test-backups"
      ]
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name k8s-velero \
  --policy-name s3 \
  --policy-document file://s3-velero-policy.json</code></pre><p>4. Install Velero with official <a href="https://github.com/vmware-tanzu/helm-charts/tree/master/charts/velero">Helm chart</a>:</p><pre><code>cat &gt; velero-values.yaml &lt;&lt;EOF
podAnnotations:
  iam.amazonaws.com/role: k8s-velero

configuration:
  provider: aws
  backupStorageLocation:
    name: aws
    bucket: velero-test-backups
    config:
      region: us-east-1
  volumeSnapshotLocation:
    name: aws
    config:
      region: us-east-1

initContainers:
  - name: velero-plugin-for-aws
    image: velero/velero-plugin-for-aws:v1.0.0
    imagePullPolicy: IfNotPresent
    volumeMounts:
      - mountPath: /target
        name: plugins
EOF

helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts

helm install backup \
  --namespace kube-system \
  -f velero-values.yaml \
  vmware-tanzu/velero</code></pre><p>5. <a href="https://velero.io/docs/master/basic-install/#install-the-cli">Install Velero CLI</a> to manage backups and restores:</p><pre><code>brew intstall velero</code></pre><p>If everything is ok, you should be able to run Velero commands, for example, to create a new backup:</p><pre><code>velero -n kube-system backup create test

velero -n kube-system backup get
NAME	STATUS      CREATED                          EXPIRES   STORAGE LOCATION   SELECTOR
test	Completed   2020-05-22 14:06:12 +0200 CEST   29d       default            &lt;none&gt;</code></pre><p><strong>NOTE:</strong> If you have persistent volumes backed by EBS, each time you create a new backup, Velero will create an EBS snapshot. Snapshot creation can take some time, and the completed status you get from the Velero doesn't mean that snapshot is ready.</p><p>Or, you can run backups on a schedule, for example, to create a daily backup with expiration set to 90 days:</p><pre><code>velero -n kube-system create schedule daily \
  --schedule="0 0 * * *" \
  --ttl 2160h0m0s</code></pre><p>Here are some more backup and restore command examples to get you started:</p><pre><code>Backup:

# create a backup containing all resources
velero backup create backup1

# create a backup including only the nginx namespace
velero backup create nginx-backup --include-namespaces nginx

# create a backup excluding the velero and default namespaces
velero backup create backup2 --exclude-namespaces velero,default

# view the YAML for a backup that doesn't snapshot volumes, without sending it to the server
velero backup create backup3 --snapshot-volumes=false -o yaml

# wait for a backup to complete before returning from the command
velero backup create backup4 --wait

Restore:

# create a restore named "restore-1" from backup "backup-1"
velero restore create restore-1 --from-backup backup-1

# create a restore with a default name ("backup-1-&lt;timestamp&gt;") from backup "backup-1"
velero restore create --from-backup backup-1

# create a restore from the latest successful backup triggered by schedule "schedule-1"
velero restore create --from-schedule schedule-1

# create a restore from the latest successful OR partially-failed backup triggered by schedule "schedule-1"
velero restore create --from-schedule schedule-1 --allow-partially-failed

# create a restore for only persistentvolumeclaims and persistentvolumes within a backup
velero restore create --from-backup backup-2 --include-resources persistentvolumeclaims,persistentvolumes</code></pre><p>Instead of relying on this post, which will be outdated in a few months, do yourself a favor and just run commands with a help argument to check on all available options.</p><h3 id="cluster-migration">Cluster Migration</h3><p>As I mentioned at the beginning, I used Velero to migrate to a new cluster. Since all backups are in the S3 bucket, you can do a full restore fairly easily.<br><br>1. Install Velero on the new cluster, using the same config. If you are using kube2iam, you will have to install it as well. At this point, if you try to get backups on the new cluster, you should see the same data.<br><br>2. Create a manual backup on the old cluster and wait for it to finish <code>velero -n kube-system backup create migration</code>.<br><br>3. Switch to a new cluster and do a full restore <code>velero -n kube-system restore create --from-backup migration</code>.<br><br>Depending on what you run in the cluster, you might need to adjust a few more things, but all resources that you have in the old cluster should be in the new one as well.<br><br>A few more notes:</p><ul><li>If some resource already exists and differs from one in the backup, Velero will not overwrite it. Instead, you should get a warning message.</li><li>Even if you remove a restore point from Velero with <code>velero restore delete</code>, Kubernetes resources will left intact.</li><li>All resources after a restore will have additional Velero labels <code>velero.io/backup-name</code> and <code>velero.io/restore-name</code>.</li><li>The pods that had persistent volume attached will have a new volume created from a snapshot. I suggest stopping stateful services in the original cluster before tacking a backup, to have all the data.</li></ul><h3 id="summary">Summary</h3><p>Velero is a must-have tool when running critical apps on the Kubernetes cluster. This post is just a quick introduction to show you how it works, but feel free to explore all options and make it work for your particular use case.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet" data-width="550"><p lang="en" dir="ltr">Check out my latest blog post on Kubernetes Velero <a href="https://t.co/IvQ0JkHrZp">https://t.co/IvQ0JkHrZp</a></p>&mdash; Alen (@alenkomljen) <a href="https://twitter.com/alenkomljen/status/1264288316007964672?ref_src=twsrc%5Etfw">May 23, 2020</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
</figure>]]></content:encoded></item><item><title><![CDATA[Moving the Remote Terraform State Items]]></title><description><![CDATA[<p>Due to recent refactoring, I figured out that I need to move some Terraform state items from one S3 path to another. And then to merge configurations with other stuff at the destination directory. Terraform can <a href="https://www.terraform.io/docs/commands/state/mv.html">move state items around</a>, but this feature doesn't work with remote states. Here is</p>]]></description><link>https://akomljen.com/moving-the-remote-terraform-state-items/</link><guid isPermaLink="false">5e79e23d2a9f5400015aa3b3</guid><category><![CDATA[terraform]]></category><category><![CDATA[aws]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sat, 28 Mar 2020 19:56:51 GMT</pubDate><media:content url="https://akomljen.com/content/images/2020/03/jordi-fernandez-9IdzBgFTHnk-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2020/03/jordi-fernandez-9IdzBgFTHnk-unsplash.jpg" alt="Moving the Remote Terraform State Items"><p>Due to recent refactoring, I figured out that I need to move some Terraform state items from one S3 path to another. And then to merge configurations with other stuff at the destination directory. Terraform can <a href="https://www.terraform.io/docs/commands/state/mv.html">move state items around</a>, but this feature doesn't work with remote states. Here is one way of doing it.</p><h3 id="example-use-case">Example Use Case</h3><p>First, let's consider the following situation, this is configuration directory tree output:</p><pre><code>.
├── db
│   └── test
│       ├── main.tf (s3 key: aws/db/test/terraform.tfstate)
│       └── rds.tf
├── test
│   ├── main.tf (s3 key: aws/test/terraform.tfstate)
│   ├── sqs.tf</code></pre><p>You want to merge <code>db/test</code> state items into <code>aws/test/terraform.tfstate</code> state and move Terraform files together to match the following directory structure:</p><pre><code>.
├── test
│   ├── main.tf (s3 key: aws/test/terraform.tfstate)
│   ├── sqs.tf
│   ├── rds.tf</code></pre><p><strong>NOTE:</strong> If you are using multiple workspaces, make sure they are the same when going through directories.<br><br>First, make local backups of both remote state files with the following command:</p><pre><code>terraform state pull &gt; terraform.tfstate
</code></pre><p>Then go to the directory which state items you want to migrate, in this case, <code>db/test</code>. Run the following commands:</p><pre><code># List all available items
terraform state list

# Move item from one state to another
terraform state mv -state-out=../test/terraform.tfstate aws_rds_cluster.test aws_rds_cluster.test</code></pre><p>Each time you run <code>terraform state mv</code> command, Terraform automatically creates a backup of state file as well. Keep in mind that all this is happening locally, regardless of how you configured the backend. Go to the consolidated directory <code>test</code> and push the state file to the remote:</p><pre><code>terraform state push terraform.tfstate</code></pre><p>Then, if you do <code>terraform state list</code> in this directory, you should see moved state items. You can copy the remaining <code>.tf</code> files in this directory and make changes as needed.</p><p>If you want to move all state items with one command you could use simple bash one-liner:</p><pre><code>for i in $(terraform state list); do terraform state mv -state-out=../test/terraform.tfstate $i $i; done</code></pre><p>After you check that all is good, you can delete local state files and all auto-created backups with <code>rm terraform.tfstate*</code>.</p><h3 id="summary">Summary</h3><p>Maybe there is a better way to do this, but I didn't want to spend too much time researching. I hope this article will help someone with a similar issue.</p>]]></content:encoded></item><item><title><![CDATA[Quick Working From Home Tips]]></title><description><![CDATA[<p>You will see hundreds of new <em>working from home tips</em> blog posts in the coming months. Most tech companies shifted to working from home (WFH) because of a global pandemic. We are all different, and what works for me will not work for you. I wrote this post to tell</p>]]></description><link>https://akomljen.com/quick-working-from-home-tips/</link><guid isPermaLink="false">5e75ec162a9f5400015aa159</guid><category><![CDATA[career]]></category><category><![CDATA[tips]]></category><category><![CDATA[wfh]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sun, 22 Mar 2020 12:57:16 GMT</pubDate><media:content url="https://akomljen.com/content/images/2020/03/christopher-gower-vjMgqUkS8q8-unsplash-1.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2020/03/christopher-gower-vjMgqUkS8q8-unsplash-1.jpg" alt="Quick Working From Home Tips"><p>You will see hundreds of new <em>working from home tips</em> blog posts in the coming months. Most tech companies shifted to working from home (WFH) because of a global pandemic. We are all different, and what works for me will not work for you. I wrote this post to tell you that working from home is not rocket science. All you need is some routine. Don't try to copy, or blindly follow the rules. Build your own. Here is my daily routine, what works for me, and what I discovered during the last four years. Keep in mind, some of those will not work during the global lockdown, but here you have it, to get some new ideas.</p><h3 id="my-daily-routine"><strong>My Daily Routine</strong></h3><p>I usually woke up at 7 am, brush my teeth, take a shower, eat 1000 kcal breakfast, and then start my working day. But, sometimes, I broke this routine by waking up and laying in my bed for an hour, checking the phone for no reason. Yeah, it happened, and I'm aware that this is not an excellent way to start my day. I try to leave my phone far away from the bed because I don't want to grab it first thing in the morning.</p><p>While in-home, I wear some pants, a t-shirt, and socks, like a typical day, spent indoor. Wearing a suit will not make me more productive. I like to make myself comfortable, but not too comfortable. Yeah, I had moments of working in pajama. It doesn't work, period. Taking your pajama off tells your brain that time is for something new. I'm making this up, but that is how I see it.</p><p>I have built my working space in an extra room, with a standing desk. But I'm too lazy to work standing. I bought a comfortable and expensive chair; it would be a bad investment if I'm standing all day. Jokes aside, a good chair and desk are essential, and rising from time to time will help to maintain a healthy lifestyle. Also, no matter how good your laptop is, posture while working on a laptop is very bad for your back. If you don't have space or don't want to invest in a monitor, consider getting a laptop stand to bring up display a little bit higher.</p><p>Lunchtime: Do not eat at your desk! That is my friendly advice. I had moments, but it is a bad habit. Have time for lunch even when working from home. My routine is to go to the kitchen, prepare a meal if I didn't do it a day before, eat and take some rest after. Also, I try to stay away from a phone, or a TV during lunch break. Meditation is great, but I have a hard time sticking to it. And if I want to take a quick snack, I eat it while standing in the kitchen. Sometimes I go outside for lunch with my friends.</p><p>I make short pauses during the day. Yeah, coding is all fun, but I need to stay healthy. Walking around in the home is an excellent way to think if you live alone. Also, I do some other stuff, like going to a grocery store or walking outside when the weather is nice.</p><p>Finishing a working day from home can be hard to do. You don't see other people leaving the office, and you don't need to catch public transport or something. Writing sync of what I did that day, and the plan for the next one helps me a lot. Then, going to the gym helps me to stay away from my laptop for a few hours, which makes an internal switch in my body that the working day ended.</p><p>And last, do whatever makes you happy afterward. Talk to people, go out, enjoy your family time. Simple stuff done today will make you more productive tomorrow.</p><h3 id="summary"><strong>Summary</strong></h3><p>After you read all this, you will see that productivity has nothing to do with working from home. Some of those things are good habits when working in the office as well. Organized people will be ok working from home, no questions. Some will even thrive more. Don't try to spice it up too much; it will go wrong eventually. Good luck to everyone!</p>]]></content:encoded></item><item><title><![CDATA[Alerting on Kubernetes Events with EFK Stack]]></title><description><![CDATA[<p>You probably care about gathering application logs only. Still, since the application is running on Kubernetes, you could get a lot of information about what is happening in the cluster by gathering events as well. Whatever happens inside the cluster, an event is recorded. You can check those events with</p>]]></description><link>https://akomljen.com/alerting-on-kubernetes-events-with-efk-stack/</link><guid isPermaLink="false">5d07cdf712a0ff0001f6ea2d</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[monitoring]]></category><category><![CDATA[efk]]></category><category><![CDATA[alerting]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sun, 03 Nov 2019 07:09:45 GMT</pubDate><media:content url="https://akomljen.com/content/images/2019/10/jeremy-yap-PQWDsr78l8w-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2019/10/jeremy-yap-PQWDsr78l8w-unsplash.jpg" alt="Alerting on Kubernetes Events with EFK Stack"><p>You probably care about gathering application logs only. Still, since the application is running on Kubernetes, you could get a lot of information about what is happening in the cluster by gathering events as well. Whatever happens inside the cluster, an event is recorded. You can check those events with <code>kubectl events</code>, but they are short-lived. To search or alert on a particular activity, you need to store them in a central place first. Now, let's see how to do that and then how to configure alerts.</p><h3 id="storing-events-in-elasticsearch">Storing Events In Elasticsearch</h3><p>The main requirement for this setup is the Elasticsearch cluster. If you don't know how to run EFK stack on Kubernetes, I suggest that you go through my post <a href="https://akomljen.com/get-kubernetes-logs-with-efk-stack-in-5-minutes/"><em>Get Kubernetes Logs with EFK Stack in 5 Minutes</em></a> to learn more about it. If you already use my helm chart to deploy EFK stack, you should know that <a href="https://github.com/komljen/helm-charts/commit/ff35598cba0696ba9ac30761be03f4339385da94">I improved it</a> and added a switch to enable gathering events as well. However, if you already have your "version" of the EFK cluster, you could install Elastic's <a href="https://www.elastic.co/products/beats/metricbeat">Metricbeat</a> agent and configure it to ship events to that cluster instead. So, assuming you already have EFK stack, go ahead and install Metricbeat with helm:</p><pre><code>$ cat &gt; values-metricbeat.yaml&lt;&lt;EOF 
daemonset:
  enabled: false

deployment:
  config:
    setup.template.name: "kubernetes_events"
    setup.template.pattern: "kubernetes_events-*"
    output.elasticsearch:
      hosts: ["http://elasticsearch-efk-cluster:9200"]
      index: "kubernetes_events-%{[beat.version]}-%{+yyyy.MM.dd}"
    output.file:
      enabled: false
  modules:
    kubernetes:
      enabled: true
      config:
        - module: kubernetes
          metricsets:
            - event
EOF

$ helm install --name events \
    --namespace logging \
    -f values-metricbeat.yaml \
    stable/metricbeat
</code></pre><p><strong>NOTE:</strong> Use your hostname in the above configuration.</p><p>Events are available through Kubernetes API, and only one Metricbeat agent pod is enough to feed all events into the Elasticsarch. The next step is to configure Kibana for the new index. Go to settings, configure index to <code>kubernetes_events-*</code>, choose a <code>@timestamp</code>, and Kibana is ready. In the discovery tab, you should see all the events from all namespaces in your Kubernetes cluster. You can search for events as needed.</p><p><strong>NOTE:</strong> Metricbeat adds quite a lot of fields, and by default, the Kibana wildcard search will not work as expected because it is limited to 1024 fields. You can still search a particular field, or increase the limit.</p><h3 id="configuring-alerts">Configuring Alerts</h3><p>Now when all events are indexed, you can send alerts when a particular query matches. After some research, I found <a href="https://github.com/Yelp/elastalert">ElastAlert</a> quite excellent and simple to configure. You can install it with helm as well, again matching to your Elasticsearch host:</p><pre><code>$ cat &gt; values-elastalert.yaml&lt;&lt;EOF 
replicaCount: 1

elasticsearch:
  host: elasticsearch-efk-cluster
  port: 9200

realertIntervalMins: "0"

rules:
  k8s_events_killing_pod: |-
    ---
    name: Kubernetes Events
    index: kubernetes_events-*

    type: any

    filter:
    - query:
        query_string:
          query: "kubernetes.event.message: Killing*probe*"
          analyze_wildcard: true

    alert:
    - "slack"
    alert_text_type: exclude_fields
    alert_text: |
      Event count {0}
      ```{1}```
      ```
      Kind - {2}
      Name - {3}
      ```
    alert_text_args:
    - kubernetes.event.count
    - kubernetes.event.message
    - kubernetes.event.involved_object.kind
    - kubernetes.event.involved_object.name

    slack:
    slack_title: Kubernetes Events
    slack_title_link: &lt;YOUR_KIBANA_URL_SAVED_SEARCH&gt;
    slack_webhook_url: &lt;YOUR_SLACK_URL&gt;
    slack_msg_color: warning
EOF

$ helm install --name efk-alerts \
    --namespace logging \
    -f values-elastalert.yaml \
    stable/elastalert</code></pre><p>In the above example, I configured ElastAlert to send an alert to a Slack channel when the pod gets killed because of the liveness probe. You need to set <code>slack_webhook_url</code> and <code>slack_title_link</code>. For slack title link I usually put saved Kibana search URL that matches the same query <code>kubernetes.event.message: Killing<em>*</em>probe*</code>.</p><p>ElastAlert instance can be used to add other alerts as well, like matching particular application log messages. Just add a new alert rule to <code>values-elastalert.yaml</code> and upgrade the helm chart to configure it:</p><pre><code>$ helm upgrade efk-alerts \
    --namespace logging \
    -f values-elastalert.yaml \
    stable/elastalert
    </code></pre><p>To learn more about all the options for ElastAlert, please check <a href="https://elastalert.readthedocs.io/en/latest/">official documents</a>. There are a lot of options and ways to configure it.</p><h3 id="summary">Summary</h3><p>This article was just a short introduction to the primary use case where you want to gather all Kubernetes events in one place and to send an alert when a particular circumstance happens. I found it very useful, and I hope it will help you as well. Stay tuned for the next one.</p>]]></content:encoded></item><item><title><![CDATA[Installing Kubernetes Dashboard per Namespace]]></title><description><![CDATA[<p>Even though I'm not <a href="https://github.com/kubernetes/dashboard">Kubernetes Dashboard</a> user, I understand why it is the easiest way for most people to interact with their apps running on top of Kubernetes. If you are interacting with it daily or managing the cluster itself, you are probably more fine with CLI, aka kubectl. Kubernetes</p>]]></description><link>https://akomljen.com/installing-kubernetes-dashboard-per-namespace/</link><guid isPermaLink="false">5cd974865ccf7f000176d636</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[addons]]></category><category><![CDATA[dashboard]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sun, 26 May 2019 12:05:22 GMT</pubDate><media:content url="https://akomljen.com/content/images/2019/05/ui-dashboard.png" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2019/05/ui-dashboard.png" alt="Installing Kubernetes Dashboard per Namespace"><p>Even though I'm not <a href="https://github.com/kubernetes/dashboard">Kubernetes Dashboard</a> user, I understand why it is the easiest way for most people to interact with their apps running on top of Kubernetes. If you are interacting with it daily or managing the cluster itself, you are probably more fine with CLI, aka kubectl. Kubernetes Dashboard is easy to install, but you might want to have it per namespace and to limit what users can do. Let's see how to install and configure it for this scenario.</p><h3 id="the-problem">The Problem</h3><p>The latest version of Kubernetes dashboard v2.0 is running without a <code>cluster-admin</code> role, which was too dangerous. On top of that, all secrets are explicitly created, and ServiceAccount doesn't have permission to create any secret. This is excellent news. But, you probably want to limit users for touching anything that is not part of their namespace. Or even more, make it read-only and disable access to some sensitive info, like secrets.</p><h3 id="installation-and-configuration">Installation and Configuration</h3><p>Kubernetes Dashboard does have namespace support. Installing the dashboard is a pretty straightforward process. So, let's say you want to install it in the <code>default</code> namespace. First, create a custom config for <a href="https://github.com/kubernetes/dashboard/tree/master/aio/deploy/helm-chart/kubernetes-dashboard">kubernetes-dashboard</a> helm chart:</p><pre><code>cat &gt; values-dashboard.yaml&lt;&lt;EOF 
extraArgs:
  - --system-banner="Test Cluster"
  - --namespace=default

metricsScraper:
  enabled: true
EOF</code></pre><p><strong>NOTE:</strong> If you have a single sign-on solution with ingress, you can set <code>--enable-skip-login</code> and <code>--enable-insecure-login</code> extra args to disable dashboard authentication. With ingress terminating SSL, you need to set <code>protocolHttp: true</code> as well.</p><p>In the above config, I enabled metrics scraper. It is required to have a metrics server running in the cluster for this to work. If you don't have it installed, you can enable it <a href="https://github.com/kubernetes/dashboard/blob/master/aio/deploy/helm-chart/kubernetes-dashboard/values.yaml#L153-L164">via config</a>.<br><br>Let's make this dashboard read only by adding custom role:</p><pre><code>cat &gt; kubernetes-dashboard-role.yaml&lt;&lt;EOF
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: Role
metadata:
  name: dash-kubernetes-dashboard-read-only
rules:
- apiGroups:
  - "*"
  resources:
  - configmaps
  - cronjobs
  - daemonsets
  - deployments
  - events
  - ingresses
  - jobs
  - persistentvolumeclaims
  - persistentvolumes
  - pods
  - pods/log
  - replicasets
  - replicationcontrollers
  - services
  - statefulsets
  verbs:
  - describe
  - get
  - list
  - watch
EOF

cat &gt; kubernetes-dashboard-rolebinding.yaml&lt;&lt;EOF
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: RoleBinding
metadata:
  name: dash-kubernetes-dashboard-read-only
  labels:
    app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: dash-kubernetes-dashboard-read-only
subjects:
- kind: ServiceAccount
  name: dash-kubernetes-dashboard
  namespace: default
EOF</code></pre><p><strong>NOTE:</strong> Set a preferred namespace - <code>default</code> in this case.</p><p>The last step it to apply all those resources and to install the dashboard:</p><pre><code>kubectl apply -f kubernetes-dashboard-role.yaml -n default
kubectl apply -f kubernetes-dashboard-rolebinding.yaml -n default

helm repo add kubernetes-dashboard https://kubernetes.github.io/dashboard/

helm install --name dash \
  --namespace default \
  -f values-dashboard.yaml \
  kubernetes-dashboard/kubernetes-dashboard</code></pre><p>You can go ahead and check if the dashboard works as expected. If you try to list namespaces or check cluster-wide resources, you will get <code>the namespaces is forbidden</code> error message, which is what you wanted to achieve by running it per namespace. You can further adjust `dash-kubernetes-dashboard-read-only` role to make it work for your use case.</p><h3 id="summary">Summary</h3><p>Installing a Kubernetes Dashboard per namespace and with limited options is a simple way of adding more visibility to the Kubernetes cluster without violating security. Stay tuned for the next one.</p>]]></content:encoded></item><item><title><![CDATA[Integrating AWS IAM and Kubernetes 
with kube2iam]]></title><description><![CDATA[<p>Containers deployed on top of Kubernetes sometimes requires easy access to AWS services. You have a few options to configure this. Most common is providing AWS access credentials to a particular pod or updating existing worker nodes IAM role with additional access rules. Pods in the AWS environment, by default,</p>]]></description><link>https://akomljen.com/integrating-aws-iam-and-kubernetes-with-kube2iam/</link><guid isPermaLink="false">5cd2d4395ccf7f000176d601</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[aws]]></category><category><![CDATA[security]]></category><category><![CDATA[iam]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sat, 18 May 2019 10:57:34 GMT</pubDate><media:content url="https://akomljen.com/content/images/2019/05/larry-farr-1211714-unsplash-crop.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2019/05/larry-farr-1211714-unsplash-crop.jpg" alt="Integrating AWS IAM and Kubernetes 
with kube2iam"><p>Containers deployed on top of Kubernetes sometimes requires easy access to AWS services. You have a few options to configure this. Most common is providing AWS access credentials to a particular pod or updating existing worker nodes IAM role with additional access rules. Pods in the AWS environment, by default, have the same access rules as underlying nodes. However, both solutions are a terrible practice, because there are projects that resolve this issue more elegantly. Two most popular are <a href="https://github.com/jtblin/kube2iam">kube2iam</a> and <a href="https://github.com/uswitch/kiam">KIAM</a>. They are pretty similar, but let's focus on kube2iam in this post.</p><h3 id="the-problem-and-a-solution">The Problem and a Solution</h3><p>I usually go ahead with installation and configuration, but you should understand <a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction.html">AWS IAM</a> and the problem in the environments like Kubernetes where containers are sharing the underlying nodes. I am noting a few sentences from <a href="https://github.com/jtblin/kube2iam/blob/master/README.md">the official kube2iam readme</a>.<br><br><em>Traditionally in AWS, service level isolation is done using IAM roles. IAM roles are attributed through instance profiles and are accessible by services through the transparent usage by the aws-sdk of the ec2 metadata API. When using the aws-sdk, a call is made to the EC2 metadata API which provides temporary credentials that are then used to make calls to the AWS service.</em></p><p>The problem with this approach is that you cannot isolate a particular container for access to some AWS service with IAM roles - shared nodes.</p><p><em>The solution is to redirect the traffic that is going to the ec2 metadata API for docker containers to a container running on each instance, make a call to the AWS API to retrieve temporary credentials and return these to the caller. Other calls will be proxied to the EC2 metadata API. This container will need to run with host networking enabled so that it can call the EC2 metadata API itself.</em></p><h3 id="installation-and-configuration">Installation and Configuration</h3><p>Tools that you need to follow this guide are <a href="https://github.com/helm/helm">helm</a> for installation and AWS CLI for interacting with AWS. First, gather some info about your cluster to be able to configure kube2iam pods. For EKS based clusters use <code>eni+</code> as interface name. You can find more interfaces based on your CNI provider <a href="https://github.com/jtblin/kube2iam#iptables">here</a>. Also, to get Amazon Resource Name (ARN) from instance profiles, you can use this command:</p><pre><code>$ aws iam list-instance-profiles | jq -r '.InstanceProfiles[].Roles[].Arn'</code></pre><p>With output like this <code>arn:aws:iam::1234567890:role/test-worker-nodes-NodeInstanceRole-1W9NK0A56SMQ6</code>, the first part is base role ARN <code>arn:aws:iam::1234567890:role/</code> and the second part is node instance role name <code>test-worker-nodes-NodeInstanceRole-1W9NK0A56SMQ6</code>. You will need those below.</p><p>Here is the finalized config and installation command:</p><pre><code>$ cat &gt; values-kube2iam.yaml &lt;&lt;EOF
extraArgs:
  base-role-arn: arn:aws:iam::1234567890:role/
  default-role: kube2iam-default

host:
  iptables: true
  interface: "eni+"

rbac:
  create: true
EOF

$ helm install --name iam \
    --namespace kube-system \
    -f values-kube2iam.yaml \
    stable/kube2iam
</code></pre><p><strong>NOTE:</strong> iptables rules prevent containers from having direct access to EC2 metadata API. Please <a href="https://github.com/jtblin/kube2iam#iptables">read this part carefully</a> to understand what is happening in the background.</p><p><em>Kube2iam works by intercepting traffic from the containers to the EC2 Metadata API, calling the AWS Security Token Service (STS) API to obtain temporary credentials using the pod configured role, then using these temporary credentials to perform the original request.</em></p><p>You have to create a policy file to add permissions for AWS STS to assume roles on worker nodes:</p><pre><code>$ cat &gt; kube2iam-policy.json &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "sts:AssumeRole"
      ],
      "Effect": "Allow",
      "Resource": [
        "arn:aws:iam::1234567890:role/k8s-*"
      ]
    }
  ]
}
EOF

$ aws iam put-role-policy \
    --role-name test-worker-nodes-NodeInstanceRole-1W9NK0A56SMQ6 \
    --policy-name kube2iam \
    --policy-document file://kube2iam-policy.json</code></pre><p><strong>NOTE:</strong> When you create the roles that the pods can assume, they need to start with <code>k8s-</code>, and that is why I put a wildcard in the above policy.</p><p>If everything works as expected, curl command from a new pod to a metadata API, should return <code>kube2iam</code>:</p><pre><code>$ curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
kube2iam</code></pre><h3 id="real-world-examples">Real World Examples</h3><p>Let's see how to use kube2iam to give <a href="https://github.com/jetstack/cert-manager">Cert Manager</a> pods access to Route53 to manage records. DNS cluster issuer needs access to Route53 for DNS records validation. </p><p>First, you need to define the trust policy of the role to allow kube2iam (via the worker node IAM Instance Profile Role) to assume the pod role:</p><pre><code>$ cat &gt; node-trust-policy.json &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    },
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::1234567890:role/test-worker-nodes-NodeInstanceRole-1W9NK0A56SMQ6"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

$ aws iam create-role \
    --role-name k8s-cert-manager \
    --assume-role-policy-document \
    file://node-trust-policy.json</code></pre><p>Then define and attach Route53 policy to the above role name:</p><pre><code>$ cat &gt; route53-policy.json &lt;&lt;EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "route53:GetChange",
      "Resource": "arn:aws:route53:::change/*"
    },
    {
      "Effect": "Allow",
      "Action": "route53:ChangeResourceRecordSets",
      "Resource": "arn:aws:route53:::hostedzone/*"
    },
    {
      "Effect": "Allow",
      "Action": "route53:ListHostedZonesByName",
      "Resource": "*"
    }
  ]
}
EOF

$ aws iam put-role-policy \
    --role-name k8s-cert-manager \
    --policy-name route53 \
    --policy-document file://route53-policy.json</code></pre><p>If you want to add some other services and to use kube2iam, reuse the existing node trust policy file to define a new role. For example, if you want to deploy <a href="https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler">Cluster Autoscaler</a>:</p><pre><code>$ aws iam create-role \
    --role-name k8s-cluster-autoscaler \
    --assume-role-policy-document \
    file://node-trust-policy.json</code></pre><p>Then define a new policy and attach it to <code>k8s-cluster-autoscaler</code> role. <br><br>The last step is to configure pods to use particular role name by providing annotation <code>iam.amazonaws.com/role: k8s-cert-manager</code> or <code>iam.amazonaws.com/role: k8s-cluster-autoscaler</code>, as defined in those examples.</p><p>Another useful feature of kube2iam is <a href="https://github.com/jtblin/kube2iam#namespace-restrictions">namespace restrictions</a>, but I'm sure you would figure it out after reading this post.</p><h3 id="summary">Summary</h3><p>When I was working with Kubernetes and AWS IAM roles for the first time, I spent more time than planned to figure it out. Maybe lack of AWS IAM knowledge, but I hope that this guide will help you to get started easier. I also recommend trying KIAM before deciding which solution works best for you.</p>]]></content:encoded></item><item><title><![CDATA[An Easy Way to Track New Releases on GitHub]]></title><description><![CDATA[<p>As a software developer, you need to keep track of many projects/tools hosted on GitHub. While GitHub has a watch feature, I found it too noisy. I open GitHub notifications once in a year, or maybe less. If you like GitHub notifications, great, you can watch a repo only</p>]]></description><link>https://akomljen.com/an-easy-way-to-track-new-releases-on-github/</link><guid isPermaLink="false">5c38a004265cfd00012d5dbd</guid><category><![CDATA[github]]></category><category><![CDATA[tips]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sat, 12 Jan 2019 13:35:24 GMT</pubDate><media:content url="https://akomljen.com/content/images/2019/01/andreas-klassen-401337-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2019/01/andreas-klassen-401337-unsplash.jpg" alt="An Easy Way to Track New Releases on GitHub"><p>As a software developer, you need to keep track of many projects/tools hosted on GitHub. While GitHub has a watch feature, I found it too noisy. I open GitHub notifications once in a year, or maybe less. If you like GitHub notifications, great, you can watch a repo only for releases; <a href="https://github.com/isaacs/github/issues/410#issuecomment-442385230">they made this possible two months ago</a>. This blog post probably doesn't make any sense for you then.</p><p>However, I don't want to get a notification on new releases; I want a simple way to check if new release came out for the projects that I use when I want.</p><p>Background story, I switched from Firefox to Chrome a few years ago, when Firefox was so slow, that I needed to ditch it. When I finally switched to Chrome, I immediately missed the <a href="https://support.mozilla.org/en-US/kb/live-bookmarks ">Firefox live bookmarks</a> feature, to be able to subscribe to RSS/Atom feeds and read them from bookmarks toolbar. For Chrome I found <a href="https://chrome.google.com/webstore/detail/foxish-live-rss/jpgagcapnkccceppgljfpoadahaopjdb">Foxish Live RSS</a> add-on which does, well, almost the same job. So, you need to install this or similar plugin for this guide.</p><p>Now, go to <code>chrome://bookmarks/</code>, and in the right up corner create a new folder, I called it GitHub releases. You can show a bookmarks toolbar under the URL bar to be always visible, but I guess you already know how to do it.</p><p>So, how do you subscribe for GitHub releases? For example, I want to add the Kubernetes project. Go to releases page of this project <a href="https://github.com/kubernetes/kubernetes/releases.atom">https://github.com/kubernetes/kubernetes/releases</a>, and add <code>.atom</code> in the URL path, like this <a href="https://github.com/kubernetes/kubernetes/releases.atom">https://github.com/kubernetes/kubernetes/releases.atom</a>. Yes, GitHub supports Atom feeds. The new page pops up (if you have Foxish Live RSS add-on installed):</p><figure class="kg-card kg-image-card"><img src="https://akomljen.com/content/images/2019/01/Screenshot-2019-01-11-15.19.24.png" class="kg-image" alt="An Easy Way to Track New Releases on GitHub"></figure><p>Choose a parent folder, GitHub releases in my case, and click on subscribe. Now, you can check if there is a new Kubernetes release with one click:</p><figure class="kg-card kg-image-card"><img src="https://akomljen.com/content/images/2019/01/Screenshot-2019-01-11-15.04.32.png" class="kg-image" alt="An Easy Way to Track New Releases on GitHub"></figure><p>You can click on the version number, and the new page opens with release details. I hope this will help you to track some exciting projects and boost your productivity while having fewer notifications.</p>]]></content:encoded></item><item><title><![CDATA[AWS ALB Ingress Controller for Kubernetes]]></title><description><![CDATA[<p>More than one year ago CoreOS introduced AWS ALB (Application Load Balancer) support for Kubernetes. <em>This project was born out of Ticketmaster's tight relationship with CoreOS.</em> It was in an alpha state for a long time, so I waited for some beta/stable release to put my hands on it.</p>]]></description><link>https://akomljen.com/aws-alb-ingress-controller-for-kubernetes/</link><guid isPermaLink="false">5b76a52752f9e80001f4cdaa</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[ingress]]></category><category><![CDATA[aws]]></category><category><![CDATA[alb]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sun, 06 Jan 2019 18:54:52 GMT</pubDate><media:content url="https://akomljen.com/content/images/2019/01/mat-reding-695856-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2019/01/mat-reding-695856-unsplash.jpg" alt="AWS ALB Ingress Controller for Kubernetes"><p>More than one year ago CoreOS introduced AWS ALB (Application Load Balancer) support for Kubernetes. <em>This project was born out of Ticketmaster's tight relationship with CoreOS.</em> It was in an alpha state for a long time, so I waited for some beta/stable release to put my hands on it. The project is donated to Kubernetes SIG-AWS on June 1, 2018, and now there is a lot more activity. A few months ago the first stable version got released. Let's try the ALB ingress and see how it compares to <a href="https://akomljen.com/kubernetes-nginx-ingress-controller/">Nginx ingress</a> or more advanced <a href="https://akomljen.com/kubernetes-contour-ingress-controller-for-envoy-proxy/">Contour ingress</a> that I wrote about in some previous posts.</p><h2 id="how-does-it-work">How Does it Work?</h2><p>On this picture you can see how ALB ingress fits together with Kubernetes:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://raw.githubusercontent.com/kubernetes-sigs/aws-alb-ingress-controller/master/docs/imgs/controller-design.png" class="kg-image" alt="AWS ALB Ingress Controller for Kubernetes"><figcaption>1) ALB ingress controller (https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/master/docs/imgs/controller-design.png)</figcaption></figure><p>And here is a <em>standard</em> ingress controller like Nginx for comparison:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://akomljen.com/content/images/2019/01/kubernetes_with_ingress_aws.png" class="kg-image" alt="AWS ALB Ingress Controller for Kubernetes"><figcaption>2) Standard ingress controller</figcaption></figure><p>The significant difference when comparing these two is that <em>standard</em> ingress is running in the cluster. Name-based routing and SSL termination are happening inside the pod which shares the same cluster resources as your app. ELB/NLB (Network Load Balancer) acts just like a gateway to the outside world, and only ingress controller service is connected to it.</p><p>In the case of ALB, you can clearly see that app services are exposed to nodes via node port and all routing happens inside the ALB. This also means that <a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/issues/168">you cannot use cert-manager with ALB ingress</a> to automatically get SSL certificates for example, because ALB is outside of cluster scope. Instead, you could use AWS certificates, create them in advance and just select which one to use. More on SSL with ALB <a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/blob/master/docs/guide/ingress/annotation.md#ssl">here</a>.</p><p>Managing ALBs is automatic, and you only need to define your ingress resources as you would typically do. ALB ingress controller pod which is running inside the Kubernetes cluster communicates with Kubernetes API and does all the work. However, this pod is only a control plane; it doesn't do any proxying and stuff like that.</p><p>Keep in mind that ALB is layer 7 load balancer, so no TCP here. If you want TCP capabilities, you could define NLB and put it in front of ALB. Putting NLB in front also helps if you want static IPs, but it is a manual step.</p><h2 id="deployment">Deployment</h2><p>Let's deploy ALB ingress controller with Helm:</p><pre><code>$ helm repo add akomljen-charts https://raw.githubusercontent.com/komljen/helm-charts/master/charts/

$ helm install --name=alb \
    --namespace ingress \
    --set-string autoDiscoverAwsRegion=true \
    --set-string autoDiscoverAwsVpcID=true \
    --set clusterName=k8s.test.akomljen.com \
    --set extraEnv.AWS_ACCESS_KEY_ID=&lt;YOUR_ACCESS_KEY&gt; \
    --set extraEnv.AWS_SECRET_ACCESS_KEY=&lt;YOUR_SECRET_KEY&gt; \
    akomljen-charts/alb-ingress
</code></pre><p><strong>NOTE:</strong> Keep in mind that using AWS access and secret keys is not a good idea for production. Check this post - <a href="https://akomljen.com/integrating-aws-iam-and-kubernetes-with-kube2iam/">Integrating AWS IAM and Kubernetes with kube2iam</a>.</p><p>After a few minutes the ALB controller should be up and running:</p><pre><code> $ kubectl get pods -l "app=alb-ingress,release=alb" -n ingress
NAME                               READY   STATUS    RESTARTS   AGE
alb-alb-ingress-5bcd44fb59-mtf65   1/1     Running   0          1m</code></pre><p>Actual ALB will not be created until you create an ingress object which is expected. Let's try to create some sample app:</p><pre><code>$ cat &gt; sample-app.yaml &lt;&lt;EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog
spec:
  selector:
    matchLabels:
      app: blog
  replicas: 3
  template:
    metadata:
      labels:
        app: blog
    spec:
      containers:
      - name: blog
        image: dockersamples/static-site
        env:
        - name: AUTHOR
          value: blog
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: blog
  name: blog
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: blog
EOF

$ kubectl create -f sample-app.yaml

$ kubectl get all --selector=app=blog
NAME                        READY   STATUS    RESTARTS   AGE
pod/blog-696457695f-6h9h2   1/1     Running   0          21s
pod/blog-696457695f-bws5v   1/1     Running   0          21s
pod/blog-696457695f-qqc8h   1/1     Running   0          21s

NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
service/blog   ClusterIP   100.67.82.181   &lt;none&gt;        80/TCP    20s

NAME                              DESIRED   CURRENT   READY   AGE
replicaset.apps/blog-696457695f   3         3         3       21s</code></pre><p>Service of the app<strong> needs to be exposed as NodePort</strong> (picture 1) for ALB to function properly.</p><p>When creating an ALB ingress resource you need to specify at least two subnets using <code>alb.ingress.kubernetes.io/subnets</code> annotation. You could also rely on subnet auto-discovery, but then you need to tag your subnets with:</p><ul><li><code>kubernetes.io/cluster/&lt;CLUSTER_NAME&gt;: owned</code></li><li><code>kubernetes.io/role/internal-elb: 1</code> (for internal ELB)</li><li><code>kubernetes.io/role/elb: 1</code> (for external ELB)</li></ul><p>If you deployed Kubernetes cluster with <a href="https://github.com/kubernetes/kops">kops</a>, subnet tags already exist and you should be fine. By default ALB will be internal, so you need to add <code>alb.ingress.kubernetes.io/scheme: internet-facing</code> annotation if you want to access the app externally. Check <a href="https://kubernetes-sigs.github.io/aws-alb-ingress-controller/guide/ingress/annotation/">the full list of annotations</a> supported by ALB ingress to suit your needs. Now, let's create the ALB ingress resource for the above app:</p><pre><code>$ cat &gt; ingress.yaml &lt;&lt;EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: blog
  labels:
    app: blog
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: blog
              servicePort: 80
EOF

$ kubectl apply -f ingress.yaml

$ kubectl get ingress -o wide
NAME   HOSTS   ADDRESS                                                             PORTS   AGE
blog   *       f5427a54-default-blog-c930-1940190599.eu-west-1.elb.amazonaws.com   80      5m21s</code></pre><p><strong>NOTE:</strong> If you specify a host for ingress, you need to add ALB address to Route53 to be able to access it externally. Or, deploy <a href="https://github.com/kubernetes-incubator/external-dns">external DNS</a> to manage Route53 records automatically, which is also recommended.</p><p>The sample app should be available using the above ingress address.</p><h3 id="one-alb-for-all-hosts">One ALB for All Hosts</h3><p>For each additional ALB ingress resource, the completely new ALB will be created. You maybe want as few ALBs as possible for all ingresses instead of 1-to-1 mapping - <a href="https://github.com/kubernetes-sigs/aws-alb-ingress-controller/issues/298">check this issue for more details</a>. Luckily there is a workaround that you could use, meet the <a href="https://github.com/jakubkulhan/ingress-merge">ingress merge controller</a>.</p><p>This is how it works:</p><ol><li>You create ingress objects like usual, but they need to be annotated with <code>kubernetes.io/ingress.class: merge</code> and <code>merge.ingress.kubernetes.io/config: &lt;CONFIG_MAP_NAME&gt;</code> where you specify a config map name which holds the annotations for resulting ingress</li><li>Merge controller watches for ingress resources annotated with <code>kubernetes.io/ingress.class: merge</code> and using defined config map, merges them together resulting in new ingress object</li></ol><p>Let's deploy the ingress merge controller and try it:</p><pre><code>$ helm repo add akomljen-charts https://raw.githubusercontent.com/komljen/helm-charts/master/charts/

$ helm install --name imc \
    --namespace ingress \
    akomljen-charts/merge-ingress

$ kubectl get pods --selector=app=merge-ingress -n ingress
NAME                                 READY   STATUS    RESTARTS   AGE
imc-merge-ingress-579fcd6f54-kdg8f   1/1     Running   0          55s</code></pre><p>First, you need to create a config map which holds annotations for resulting ingress:</p><pre><code>$ cat &gt; merged-ingress-cm.yaml &lt;&lt;EOF
apiVersion: v1
kind: ConfigMap
metadata:
  name: blog-ingress
data:
  annotations: |
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
EOF

$ kubectl apply -f merged-ingress-cm.yaml</code></pre><p>Then you can delete existing ingress created in the previous step and create the new one, but now use <code>kubernetes.io/ingress.class: merge</code> and <code>merge.ingress.kubernetes.io/config: blog-ingress</code> annotations:</p><pre><code>$ kubectl delete -f ingress.yaml

$ cat &gt; merged-ingress.yaml &lt;&lt;EOF
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: blog
  labels:
    app: blog
  annotations:
    kubernetes.io/ingress.class: merge
    merge.ingress.kubernetes.io/config: blog-ingress
spec:
  rules:
    - http:
        paths:
          - path: /*
            backend:
              serviceName: blog
              servicePort: 80
EOF

$ kubectl create -f merged-ingress.yaml

$ kubectl get ingress -o wide
NAME           HOSTS   ADDRESS                                                                 PORTS   AGE
blog           *       f5427a54-default-blogingre-b47d-460392593.eu-west-1.elb.amazonaws.com   80      48s
blog-ingress   *       f5427a54-default-blogingre-b47d-460392593.eu-west-1.elb.amazonaws.com   80      48s
</code></pre><p>Seeing two ingresses with same ALB address is confusing, but merge ingress controller is just propagating the status of merged ingress <code>blog-ingress</code> to <code>blog</code> ingress.</p><p>The <em>downside</em> of using ingress merge controller is that all ingresses shares the same annotations defined in the config map. However, you can create more config maps per ALB <em>ingress group</em>. Then you have the flexibility of using just enough ALBs to cover all groups of ingress resources. Also, for each namespace, you still need another ALB.</p><h3 id="summary">Summary</h3><p>Here you go, a new option to expose your services when running the Kubernetes cluster on AWS. I hope that this post will help you to decide which ingress controller to use and to understand some significant differences when comparing different ingress controllers.</p>]]></content:encoded></item><item><title><![CDATA[10 Most Read Kubernetes Articles on My Blog in 2018]]></title><description><![CDATA[<p>Let's start this year with some stats from the last one, 2018. Probably 99% of the articles on this blog are Kubernetes related. I wrote 28 articles in 2018 which is good, but my goal was 50 actually. I think that Kubernetes adoption in 2019 will grow, at least stats</p>]]></description><link>https://akomljen.com/10-most-read-kubernetes-articles-on-my-blog/</link><guid isPermaLink="false">5c2a3c2be3d7ed0001673e05</guid><category><![CDATA[year in review]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Tue, 01 Jan 2019 19:37:41 GMT</pubDate><media:content url="https://akomljen.com/content/images/2019/01/mpho-mojapelo-127562-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2019/01/mpho-mojapelo-127562-unsplash.jpg" alt="10 Most Read Kubernetes Articles on My Blog in 2018"><p>Let's start this year with some stats from the last one, 2018. Probably 99% of the articles on this blog are Kubernetes related. I wrote 28 articles in 2018 which is good, but my goal was 50 actually. I think that Kubernetes adoption in 2019 will grow, at least stats from my blog shows that and you will see the same in Google trends:</p><figure class="kg-card kg-image-card"><img src="https://akomljen.com/content/images/2019/01/Screenshot-2019-01-01-18.51.20.png" class="kg-image" alt="10 Most Read Kubernetes Articles on My Blog in 2018"></figure><p>Before listing 10 most read Kubernetes articles on my blog in 2018, I will share some stats from Google Analytics.</p><h3 id="stats-from-google-analytics">Stats from Google Analytics</h3><p>Most of the users on this blog come from organic search, actually, 74,8% of them, 13,4% direct, 7,1% referral and 4.7% come from social networks, email, and other channels:</p><figure class="kg-card kg-image-card"><img src="https://akomljen.com/content/images/2019/01/Screenshot-2019-01-01-18.56.09.png" class="kg-image" alt="10 Most Read Kubernetes Articles on My Blog in 2018"></figure><p>This blog visited <strong>146,367 unique users</strong> that come from all over the world. Here are the top 10 countries:</p><figure class="kg-card kg-image-card"><img src="https://akomljen.com/content/images/2019/01/Screenshot-2019-01-01-20.35.39.png" class="kg-image" alt="10 Most Read Kubernetes Articles on My Blog in 2018"></figure><h3 id="10-most-read-kubernetes-articles-in-2018">10 Most Read Kubernetes Articles in 2018</h3><p>Now let's see which articles are the most visited:</p><ol><li><a href="https://akomljen.com/set-up-a-jenkins-ci-cd-pipeline-with-kubernetes/">Set Up a Jenkins CI/CD Pipeline with Kubernetes</a> - 43,717 (13,85%)</li><li><a href="https://akomljen.com/get-kubernetes-cluster-metrics-with-prometheus-in-5-minutes/">Get Kubernetes Cluster Metrics with Prometheus in 5 Minutes</a> - 29,744 (9.43%)</li><li><a href="https://akomljen.com/kubernetes-nginx-ingress-controller/">Kubernetes Nginx Ingress Controller</a> - 28,528 (9.04%)</li><li><a href="https://akomljen.com/kubernetes-persistent-volumes-with-deployment-and-statefulset/">Kubernetes Persistent Volumes with Deployment and StatefulSet</a> - 21,092 (6.68%)</li><li><a href="https://akomljen.com/get-kubernetes-logs-with-efk-stack-in-5-minutes/">Get Kubernetes Logs with EFK Stack in 5 Minutes</a> - 17,151 (5.44%)</li><li><a href="https://akomljen.com/kubernetes-service-mesh/">Kubernetes Service Mesh</a> - 15,589 (4.94%)</li><li><a href="https://akomljen.com/kubernetes-environment-variables/">Kubernetes Environment Variables</a> - 13,843 (4.39%)</li><li><a href="https://akomljen.com/get-automatic-https-with-lets-encrypt-and-kubernetes-ingress/">Get Automatic HTTPS with Let's Encrypt and Kubernetes Ingress</a> - 12,930 (4.10%)</li><li><a href="https://akomljen.com/rook-cloud-native-on-premises-persistent-storage-for-kubernetes-on-kubernetes/">Rook: Cloud Native On-Premises Persistent Storage for Kubernetes on Kubernetes</a> - 10,305 (3.27%)</li><li><a href="https://akomljen.com/kubernetes-cluster-autoscaling-on-aws/">Kubernetes Cluster Autoscaling on AWS</a> - 9,350 (2,96%)</li></ol><p>Please keep in mind that some posts are written earlier and those will probably have more visits. For example, the first on the list is written in February, while the second is written two months later, so normally it will have more visits.</p><p>I wish you a happy new year and a smooth transition to Kubernetes if that is your tool of choice!</p>]]></content:encoded></item><item><title><![CDATA[Kubernetes API Resources: Which Group and Version to Use?]]></title><description><![CDATA[Find out which "kind" and "apiVersion" to use next time you see Kubernetes resource definition.]]></description><link>https://akomljen.com/kubernetes-api-resources-which-group-and-version-to-use/</link><guid isPermaLink="false">5c00f236d4abf300011f2bd4</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[api]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Fri, 30 Nov 2018 13:43:23 GMT</pubDate><media:content url="https://akomljen.com/content/images/2018/11/javier-allegue-barros-761133-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2018/11/javier-allegue-barros-761133-unsplash.jpg" alt="Kubernetes API Resources: Which Group and Version to Use?"><p>Kubernetes uses declarative API which makes the system more robust. But, this means that we create an object using CLI or REST to represent what we want the system to do. For representation, we need to define things like API resource name, group, and version. But users get confused. The main reason for the confusion is that we as humans are not good at remembering things like this. In one deployment definition you could see this <code>apiVersion: apps/v1beta2</code>, and in another <code>apiVersion: apps/v1</code>. Which one is correct? Which you should use? How to check which are supported on your Kubernetes cluster? Those are all valid questions and I will try to explain it using simple trick, the <code>kubectl</code>.</p><h3 id="api-resources">API Resources</h3><p>You can get all API resources supported by your Kubernetes cluster using this command:</p><pre><code>$ kubectl api-resources -o wide
NAME                              SHORTNAMES   APIGROUP                       NAMESPACED   KIND                             VERBS
bindings                                                                      true         Binding                          [create]
componentstatuses                 cs                                          false        ComponentStatus                  [get list]
configmaps                        cm                                          true         ConfigMap                        [create delete deletecollection get list patch update watch]
endpoints                         ep                                          true         Endpoints                        [create delete deletecollection get list patch update watch]
events                            ev                                          true         Event                            [create delete deletecollection get list patch update watch]
limitranges                       limits                                      true         LimitRange                       [create delete deletecollection get list patch update watch]
namespaces                        ns                                          false        Namespace                        [create delete get list patch update watch]
nodes                             no                                          false        Node                             [create delete deletecollection get list patch proxy update watch]
persistentvolumeclaims            pvc                                         true         PersistentVolumeClaim            [create delete deletecollection get list patch update watch]
persistentvolumes                 pv                                          false        PersistentVolume                 [create delete deletecollection get list patch update watch]
pods                              po                                          true         Pod                              [create delete deletecollection get list patch proxy update watch]
podtemplates                                                                  true         PodTemplate                      [create delete deletecollection get list patch update watch]
replicationcontrollers            rc                                          true         ReplicationController            [create delete deletecollection get list patch update watch]
resourcequotas                    quota                                       true         ResourceQuota                    [create delete deletecollection get list patch update watch]
secrets                                                                       true         Secret                           [create delete deletecollection get list patch update watch]
serviceaccounts                   sa                                          true         ServiceAccount                   [create delete deletecollection get list patch update watch]
services                          svc                                         true         Service                          [create delete get list patch proxy update watch]
mutatingwebhookconfigurations                  admissionregistration.k8s.io   false        MutatingWebhookConfiguration     [create delete deletecollection get list patch update watch]
validatingwebhookconfigurations                admissionregistration.k8s.io   false        ValidatingWebhookConfiguration   [create delete deletecollection get list patch update watch]
customresourcedefinitions         crd          apiextensions.k8s.io           false        CustomResourceDefinition         [create delete deletecollection get list patch update watch]
apiservices                                    apiregistration.k8s.io         false        APIService                       [create delete deletecollection get list patch update watch]
controllerrevisions                            apps                           true         ControllerRevision               [create delete deletecollection get list patch update watch]
daemonsets                        ds           apps                           true         DaemonSet                        [create delete deletecollection get list patch update watch]
deployments                       deploy       apps                           true         Deployment                       [create delete deletecollection get list patch update watch]
replicasets                       rs           apps                           true         ReplicaSet                       [create delete deletecollection get list patch update watch]
statefulsets                      sts          apps                           true         StatefulSet                      [create delete deletecollection get list patch update watch]
...</code></pre><p>I trimmed the output as there are many of them. There is a lot of useful information here, let's explain some interesting ones:</p><ul><li>SHORTNAMES - you can use those shortcuts with <code>kubectl</code></li><li>APIGROUP - <a href="https://kubernetes.io/docs/reference/using-api/#api-groups">check the official docs to learn more</a>, but in short, you will use it like this <code>apiVersion: &lt;APIGROUP&gt;/v1</code> in yaml files</li><li>KIND - the resource name</li><li>VERBS - available methods, also useful when you want to define <code>ClusterRole</code> RBAC rules</li></ul><p>You also have the option to get API resources for a particular API group, for example:</p><pre><code>$ kubectl api-resources --api-group apps -o wide
NAME                  SHORTNAMES   APIGROUP   NAMESPACED   KIND                 VERBS
controllerrevisions                apps       true         ControllerRevision   [create delete deletecollection get list patch update watch]
daemonsets            ds           apps       true         DaemonSet            [create delete deletecollection get list patch update watch]
deployments           deploy       apps       true         Deployment           [create delete deletecollection get list patch update watch]
replicasets           rs           apps       true         ReplicaSet           [create delete deletecollection get list patch update watch]
statefulsets          sts          apps       true         StatefulSet          [create delete deletecollection get list patch update watch]</code></pre><p>For each of those kinds you could use <code>kubectl explain</code> to get more info about the particular resource:</p><pre><code>$ kubectl explain configmap
KIND:     ConfigMap
VERSION:  v1

DESCRIPTION:
     ConfigMap holds configuration data for pods to consume.

FIELDS:
   apiVersion	&lt;string&gt;
     APIVersion defines the versioned schema of this representation of an
     object. Servers should convert recognized schemas to the latest internal
     value, and may reject unrecognized values. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#resources

   data	&lt;map[string]string&gt;
     Data contains the configuration data. Each key must consist of alphanumeric
     characters, '-', '_' or '.'.

   kind	&lt;string&gt;
     Kind is a string value representing the REST resource this object
     represents. Servers may infer this from the endpoint the client submits
     requests to. Cannot be updated. In CamelCase. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds

   metadata	&lt;Object&gt;
     Standard object's metadata. More info:
     https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata</code></pre><p>Please note that explain may show an old group/version, but you can explicitly set it with <code>--api-version</code>, for example, <code>kubectl explain replicaset --api-version apps/v1</code>. Thanks <a href="https://twitter.com/markoluksa">@markoluksa</a> for the tip!</p><h3 id="api-versions">API Versions</h3><p>You can also get all API versions supported by your cluster using this command:</p><pre><code>$ kubectl api-versions
admissionregistration.k8s.io/v1beta1
apiextensions.k8s.io/v1beta1
apiregistration.k8s.io/v1beta1
apps/v1
apps/v1beta1
apps/v1beta2
authentication.k8s.io/v1
authentication.k8s.io/v1beta1
authorization.k8s.io/v1
authorization.k8s.io/v1beta1
autoscaling/v1
autoscaling/v2beta1
batch/v1
batch/v1beta1
certificates.k8s.io/v1beta1
certmanager.k8s.io/v1alpha1
enterprises.upmc.com/v1
events.k8s.io/v1beta1
extensions/v1beta1
metrics.k8s.io/v1beta1
monitoring.coreos.com/v1
networking.k8s.io/v1
policy/v1beta1
rbac.authorization.k8s.io/v1
rbac.authorization.k8s.io/v1beta1
storage.k8s.io/v1
storage.k8s.io/v1beta1
v1</code></pre><p>The output is presented in form of "group/version". <a href="https://kubernetes.io/docs/reference/using-api/#api-versioning">Check this page</a>, to learn more about API versioning in Kubernetes.<br><br>Sometimes, you just want to check if a particular group/version is available for some resource. Most resources have available <code>get</code> method, so just try to get a resource while providing API version and group <code>kubectl get &lt;API_RESOURCE_NAME&gt;.&lt;API_VERSION&gt;.&lt;API_GROUP&gt;</code>. For example:</p><pre><code>$ kubectl get deployments.v1.apps -n kube-system
NAME                                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
autoscaler-aws-cluster-autoscaler       1         1         1            1           55d
calico-kube-controllers                 1         1         1            1           317d
calico-policy-controller                0         0         0            0           317d
dns-controller                          1         1         1            1           317d
elasticsearch-operator                  1         1         1            1           274d
hpa-hpa-operator                        1         1         1            1           60d
hpa-metrics-server                      1         1         1            1           60d
kube-dns                                2         2         2            2           317d
kube-dns-autoscaler                     1         1         1            1           317d
spot-rescheduler-k8s-spot-rescheduler   1         1         1            1           136d
tiller-deploy                           1         1         1            1           315d</code></pre><p>You will get the error if the resource doesn't exist with specified group/version combination or if the resource doesn't exist at all.</p><h3 id="summary">Summary</h3><p>This article will help you to understand what are those two lines in yaml, kind and apiVersion next time you see them. If you want to learn more about Kubernetes design I recommend <a href="https://thenewstack.io/kubernetes-design-and-development-explained/">checking this post</a>. Stay tuned for the next one!</p>]]></content:encoded></item><item><title><![CDATA[How to Run Dev.to on Kubernetes]]></title><description><![CDATA[<p>Recently I was checking <a href="https://dev.to">Dev.to</a> community. I must say, I really like how the application looks, clean and simple. And more important I like the community there. I also started to republish some posts because I want to show Kubernetes to the larger audience, preferably developers. But, any time</p>]]></description><link>https://akomljen.com/how-to-run-dev-to-on-kubernetes/</link><guid isPermaLink="false">5bf49689e398770001775889</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[devto]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sun, 25 Nov 2018 19:31:57 GMT</pubDate><media:content url="https://akomljen.com/content/images/2018/11/rawpixel-778699-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2018/11/rawpixel-778699-unsplash.jpg" alt="How to Run Dev.to on Kubernetes"><p>Recently I was checking <a href="https://dev.to">Dev.to</a> community. I must say, I really like how the application looks, clean and simple. And more important I like the community there. I also started to republish some posts because I want to show Kubernetes to the larger audience, preferably developers. But, any time I check something new I get some new ideas. This time I saw that Dev.to is open source and thought, it would be pretty interesting for people to see how to run it on Kubernetes. I will explain how I do it as an experienced Kubernetes user. So, let's start!</p><h3 id="check-the-installation-docs">Check the Installation Docs</h3><p>Kind of obvious, this is the first step. Look into repo docs, getting started guides, check for Dockerfiles and other goodies. If the repo has a Dockerfile available, great, you will save some time on building docker image yourself.</p><p>I also found one Dev.to image on DockerHub, but it is old and I will not use it. Dockerfile from the repo is last updated a month ago at the time of writing, which is good. I will just have to build the image and store it under my namespace on <a href="https://hub.docker.com/">DockerHub</a>. You can use any Docker registry, doesn't matter.</p><p>Also, I will need to run PostgreSQL. I know that there is an image available for sure, so I went right ahead and checked if there is also a helm chart available. I will use helm to package this app for easier installation on Kubernetes. I was writing about helm before so you might check it out - <a href="https://akomljen.com/package-kubernetes-applications-with-helm/">Package Kubernetes Applications with Helm</a>. Seems like there is an <a href="https://github.com/helm/charts/tree/master/stable/postgresql">upstream PostgreSQL chart</a>, so less work for me 😎.</p><p>The helm is not a must have, but is the way how I like to run apps on Kubernetes.</p><h3 id="build-docker-image">Build Docker Image</h3><p>I see that there is a <a href="https://github.com/thepracticaldev/dev.to#docker-installation-beta">getting started guide for Docker</a> in the repo. This is good, if you can run it and test using docker compose, it will be easier to migrate it to Kubernetes later. Also, you can learn a lot about app architecture just by looking into Dockerfile, docker-compose.yaml, and other config files.</p><p>While checking the available Dockerfile I figure out that it will not work for Kubernetes. The reason for this is not because it needs to be special, but this Dockerfile is for development only which assume that you will mount the local repo data into the container. I will have to clone repo inside the Dockerfile to get the code. Here is updated Dockerfile:</p><pre><code>FROM ruby:2.5.3

# Make nodejs and yarn as dependencies
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN curl -sL https://deb.nodesource.com/setup_8.x | bash
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list

# Install dependencies and perform clean-up
RUN apt-get update -qq &amp;&amp; apt-get install -y \
   build-essential \
   nodejs \
   yarn \
 &amp;&amp; apt-get -q clean \
 &amp;&amp; rm -rf /var/lib/apt/lists

RUN git clone https://github.com/thepracticaldev/dev.to /usr/src/app
WORKDIR /usr/src/app
ENV RAILS_ENV development

# Installing Ruby dependencies
RUN gem install bundler
RUN bundle install --jobs 20 --retry 5

# Install JavaScript dependencies
ENV YARN_INTEGRITY_ENABLED "false"
RUN yarn install &amp;&amp; yarn check --integrity

ENTRYPOINT ["bundle", "exec"]

CMD ["rails", "server", "-b", "0.0.0.0", "-p", "3000"]
</code></pre><p><strong>NOTE:</strong> This Dockerfile is not good for production, I'm pretty much aware of its size and other things but this is not the point of this blog post.</p><p>And the diff when compared to original Dockerfile in the repo:</p><pre><code>&lt; RUN git clone https://github.com/thepracticaldev/dev.to /usr/src/app
&gt; COPY Gemfile* ./
&gt; COPY yarn.lock ./
</code></pre><p>As you can see, just a simple change. Let's build the Dev.to docker image:</p><pre><code>$ docker build -t komljen/devto:latest .
$ docker push komljen/devto:latest</code></pre><p>Building image will take some time. The image is a little bit on a large side, 1.76GB, but ok. Don't go into optimizations at this point.</p><p>I always skip some steps from getting started guides. Definitely not proud of that. Didn't even try to follow instructions, but that is how I learn, the hard way.</p><h3 id="time-for-kubernetes">Time for Kubernetes</h3><p>And here is the tricky part, but only because I need to learn more about how everything is glued together for the app to work correctly. After some research, I think I got it. So, this is how it will look:</p><ul><li>Create the new helm chart for Dev.to frontend</li><li>Put PostgreSQL as a requirement, so when you install Dev.to chart, PG will be installed also. PG will be persistent of course</li><li>Set default values for both, PG and Dev.to in one values.yaml file</li><li>Run rails <code>db:setup</code> as init container on install, and on upgrade, it will become rails <code>db:migrate</code>. Would be nicer if I could run only migrate</li><li>For config, create a config map and secret. Both will be available as env variables in the container</li><li>For DB access you simply use config map/secret from PG chart</li><li>Worker jobs will be running in a separate container, but the same pod</li></ul><p>And after some trial and error here is <a href="https://github.com/komljen/helm-charts/tree/master/devto">Dev.to helm chart</a>. Most of the issues I had during the development of this chart was because I don't have much experience with Ruby apps and especially about running Dev.to. Also, I found it very difficult to run the app in production mode because of too many dependencies on external services. So I gave up on that after I saw 30+ API keys missing. Definitely, the app is not developed with Kubernetes or containers in mind and some changes might be required to make it a first-class citizen.</p><p>I will not go into the details of this helm chart, it would be too much for a single post, but here are some tips when developing a new chart.</p><p>When writing the new helm chart I often reuse some existing that I wrote before. Who would say? Then I just adjust what is needed for the new app. You could also run <code>helm create &lt;chart_name&gt;</code> and helm would prepare some templates, to begin with. During testing, I often use <code>helm template</code> command which populates templates without using <em>Tiller</em> (the server component of helm which is running in Kubernetes cluster). Also, you could use <code>--dry-run --debug</code> options to see populated files with <code>helm install</code>.</p><h3 id="installation">Installation</h3><p>First, prepare your values file to match with your environment. You only need Algolia Search API access for the development environment and you can find instructions for setting up a new account <a href="https://docs.dev.to/backend/algolia/">here</a>.</p><p>Now, you can install the chart:</p><pre><code>$ cat &gt; myvalues.yaml &lt;&lt;EOF
secret:
  ALGOLIASEARCH_API_KEY: &lt;YOUR_API_KEY&gt;
  ALGOLIASEARCH_APPLICATION_ID: &lt;YOUR_APPLICATION_ID&gt;
  ALGOLIASEARCH_SEARCH_ONLY_KEY: &lt;YOUR_SEARCH_ONLY_KEY&gt;

ingress:
  enabled: true
  annotations:
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
  hosts:
    - devto.test.akomljen.com
  tls:
   - secretName: devto
     hosts:
       - devto.test.akomljen.com
EOF

$ helm repo add akomljen-charts https://raw.githubusercontent.com/komljen/helm-charts/master/charts/      
      
$ helm install --name dev \
    -f myvalues.yaml \
    akomljen-charts/devto

$ kubectl get po
NAME                        READY     STATUS    RESTARTS   AGE
dev-devto-d7f847969-dbtkq   2/2       Running   0          3m
dev-postgresql-0            1/1       Running   0          3m      
</code></pre><p>My Kubernetes cluster has a Nginx ingress running with<a href="https://akomljen.com/get-automatic-https-with-lets-encrypt-and-kubernetes-ingress/"> automatic SSL</a>, so after installation, I was able to access the app immediately. It is slow when accessing the app for the first time because in development mode it runs webpack compile.</p><p>Also, I see that app fails to load profile pictures, but I didn't have time to dig further into it. Maybe you know the answer. Either way, you saw how I run the <em>not so ready for containers app</em> in Kubernetes.</p><h3 id="summary">Summary</h3><p>The time that I spent to write this post and prepare everything was around 6h. I would say not too much considering that Dev.to is pretty new to me. And also most of the time was some polishing and proofreading this post. Hope it will help you to understand the process of running open source apps on Kubernetes better. Stay tuned for the next one!</p>]]></content:encoded></item><item><title><![CDATA[Kubernetes Contour Ingress Controller for Envoy Proxy]]></title><description><![CDATA[Introduction to Kubernetes contour ingress controller for Envoy proxy and ingress route objects.]]></description><link>https://akomljen.com/kubernetes-contour-ingress-controller-for-envoy-proxy/</link><guid isPermaLink="false">5bd6dd88a0aa62000197fbcb</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[ingress]]></category><category><![CDATA[contour]]></category><category><![CDATA[envoy]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Thu, 08 Nov 2018 18:49:16 GMT</pubDate><media:content url="https://akomljen.com/content/images/2018/11/pierre-chatel-innocenti-654090-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2018/11/pierre-chatel-innocenti-654090-unsplash.jpg" alt="Kubernetes Contour Ingress Controller for Envoy Proxy"><p>Most users while starting to learn Kubernetes will get to the point of exposing some resources outside the cluster. This is like a <em>Hello World</em> example in the <a href="https://akomljen.com/tag/kubernetes/">Kubernetes</a> world. And in most cases, the solution to this problem is the ingress controller. Think of ingress as a reverse proxy. Ingress sits between the Kubernetes service and Internet. It provides name-based routing, SSL termination, and other goodies. Often when approaching this problem users will choose Nginx. And the reason is simple, it is all over the place, almost every article about ingress refers to Nginx. The main reason for this is that Nginx was here from the start, almost. I was referring to it in <a href="https://akomljen.com/tag/ingress/">my blog post as well</a>. But, the situation is quite different today as we have some great alternatives. Welcome to <a href="https://github.com/heptio/contour">Heptio Contour</a> ingress controller.</p><h3 id="the-raise-of-crds">The Raise of CRDs</h3><p>Before talking about Contour and how it is different compared to Nginx for example, or any other "standard" ingress controller I have to mention<a href="https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/#customresourcedefinitions"> Custom Resource Definitions or CRDs</a>. Actually, I'm mentioning it a lot on this blog, but you need to appreciate how easy is to extend Kubernetes with custom resources.</p><p>As the name suggests, with custom resources you can define additional objects and extend your Kubernetes cluster with new features. Contour team did a great job introducing <code>IngressRoute</code> object which doesn't depend on standard ingress. I encourage you to take a look at <a href="https://github.com/heptio/contour/blob/master/design/ingressroute-design.md">design doc</a> to learn more. This means that the team behind Contour can extend its functionality without depending on the whole community, but at the same time they give us new ideas. In the end, we can expect that some of those things will end up in upstream Kubernetes as well. Maybe an ingress v2 😉.</p><h3 id="deployment">Deployment</h3><p>I created a <a href="https://github.com/komljen/helm-charts/tree/master/contour">helm chart for Contour</a> deployment. The chart will install the Contour and Envoy proxy as deployment, both running in the same pod. We could have those separate, or even run it as daemon set. Maybe I will add it as an option to the helm chart later. I know, I also need to add a README.</p><p>Some notes:</p><ul><li>If you are running on-premises you could expose Envoy proxy as node port and then you will be able to access your service on each k8s node.</li><li>When running in the cloud you will have an additional component that sits between Envoy proxy and the Internet, load balancer. If you are running on AWS preferred load balancer is NLB, which compared to classic ELB, doesn't terminate the connection and has a lower latency. Also, it is cheaper.</li></ul><p>Let's deploy Contour ingress controller with Envoy proxy, and use NLB as my cluster is running on AWS:</p><pre><code>$ helm repo add akomljen-charts https://raw.githubusercontent.com/komljen/helm-charts/master/charts/

$ helm install --name heptio \
  --namespace ingress \
  --set proxy.service.annotations."service\.beta\.kubernetes\.io/aws-load-balancer-type"=nlb \
  akomljen-charts/contour

$ kubectl get pod -n ingress --selector=app=contour
NAME                              READY     STATUS    RESTARTS   AGE
heptio-contour-7b7694f98d-cxfnx   2/2       Running   0          1m</code></pre><p><strong>NOTE:</strong> If you are running k8s v1.9 or lower, NLB will not work! More info <a href="https://github.com/kubernetes/kubernetes/pull/56759">here</a>.</p><p>If everything goes well you should get ELB/NLB running in your cluster. You can get its address with:</p><pre><code>$ kubectl get svc heptio-contour -o jsonpath='{.status.loadBalancer.ingress[0].hostname}' -n ingress
a00950ebcfd0411e740ee0207cf10ce8-1089949860.eu-west-1.nlb.amazonaws.com</code></pre><p>And then use this address to create a wildcard DNS A record <code>*.test.example.com</code> in Route53.</p><p><strong>NOTE:</strong> <a href="https://github.com/kubernetes-incubator/external-dns">External DNS</a> is the project that you might want to look at, but not the scope of this post and above wildcard DNS will be ok for ingress testing.</p><h3 id="example-workloads">Example Workloads</h3><p>You can now run different workloads and use ingress route objects to create ingress rules. Of course, standard ingress is also supported. Let's test a few examples. First I need to run some test app. I will create a simple web app based on <code>dockersamples/static-site</code> docker image. This is a Nginx container that will display a unique name which will help us to identify which app we are accessing. Let's create a deployment:</p><pre><code>$ cat &gt; blog.yaml &lt;&lt;EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: blog
spec:
  selector:
    matchLabels:
      app: blog
  replicas: 3
  template:
    metadata:
      labels:
        app: blog
    spec:
      containers:
      - name: blog
        image: dockersamples/static-site
        env:
        - name: AUTHOR
          value: blog
        ports:
        - containerPort: 80
EOF</code></pre><p>And a service:</p><pre><code>$ cat &gt; s1.yaml &lt;&lt;EOF
apiVersion: v1
kind: Service
metadata:
  labels:
    app: blog
  name: s1
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: blog
EOF</code></pre><p>Let's create both:</p><pre><code>$ kubectl apply -f blog.yaml -f s1.yaml

$ kubectl get po --selector=app=blog
NAME                    READY     STATUS    RESTARTS   AGE
app1-5d4d466cc7-4vh9l   1/1       Running   0          10s
app1-5d4d466cc7-fj489   1/1       Running   0          10s
app1-5d4d466cc7-wndhn   1/1       Running   0          10s</code></pre><p>Ok, so the service is running and we can expose it now. Let's say I want to have this service available on <code>app.test.example.com</code>:</p><pre><code>$ cat &gt; main.yaml &lt;&lt;EOF
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
  name: main
spec: 
  virtualhost:
    fqdn: app.test.example.com
  routes: 
    - match: /
      services:
        - name: s1
          port: 80
EOF

$ kubectl apply -f main.yaml

$ kubectl get ingressroute main -o jsonpath='{.status.currentStatus}'
valid</code></pre><p>If you try to access <code>app.test.example.com</code> you should get this page:</p><figure class="kg-card kg-image-card"><img src="https://akomljen.com/content/images/2018/11/blog.png" class="kg-image" alt="Kubernetes Contour Ingress Controller for Envoy Proxy"></figure><p>Nothing special here, but let's adjust a different path now. Instead of <code>match: /</code> set <code>match: /blog</code> and apply changes. If you try to access <code>app.test.example.com/blog</code> it will not work. This is expected because the service itself doesn't have <code>/blog</code> path available. You can resolve this issue with rewriting to <code>/</code>. Just add <code>prefixRewrite: "/"</code> and apply the changes again:</p><pre><code>spec: 
  routes: 
    - match: /blog
      prefixRewrite: "/"
      services: 
        - name: s1
          port: 80</code></pre><p>Now it should work again. The big difference, when compared to standard ingress object, is the ability to set prefix rewrite per route. This is not possible with Nginx because it uses annotations. You could do some workaround, but it's messy.</p><p>All the above is not much different from standard ingress. The key features of ingress route are:</p><ul><li>Better support of multi-team Kubernetes clusters</li><li>A delegation of routing configuration for a path or namespace</li><li>Multiple services within a single route</li><li>Supports defining service weighting and load balancing strategy (no annotations here)</li></ul><p>Probably the most interesting Contour feature is the ability to delegate one route to another. Basically, you can connect multiple ingress route objects to work like one. In above example, you might want to delegate <code>/</code> path to another ingress route object. That object can also be in the different namespace.</p><p>Let's create another deployment <code>app2</code> in test namespace this time:</p><pre><code>$ cat &gt; app2.yaml &lt;&lt;EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app2
spec:
  selector:
    matchLabels:
      app: app2
  replicas: 3
  template:
    metadata:
      labels:
        app: app2
    spec:
      containers:
      - name: app2
        image: dockersamples/static-site
        env:
        - name: AUTHOR
          value: app2
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: app2
  name: s2
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: app2
EOF

$ kubectl apply -f app2.yaml -n test</code></pre><p>I already have ingress route <code>main</code> in the default namespace, and now I want for that ingress route to delegate <code>/</code> path to ingress route in <strong>test namespace</strong>:</p><pre><code>$ cat &gt; delegate-from-main.yaml &lt;&lt;EOF
apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
  name: delegate-from-main
spec:
  routes:
    - match: /
      services:
        - name: s2
          port: 80
EOF

$ kubectl apply -f delegate-from-main.yaml -n test

$ kubectl get ingressroute delegate-from-main -o jsonpath='{.status.currentStatus}' -n test
orphaned</code></pre><p>As you can see the status is <code>orphaned</code> because this ingress route doesn't have a host. The last step is to edit the existing main ingress route in <strong>default namespace</strong> and add a delegate rule:</p><pre><code>apiVersion: contour.heptio.com/v1beta1
kind: IngressRoute
metadata:
  name: main
spec:
  virtualhost:
    fqdn: app.test.example.com
  routes:
    - match: /blog
      prefixRewrite: "/"
      services:
        - name: s1
          port: 80
    - match: /
      delegate:
        name: delegate-from-main
        namespace: test</code></pre><p>And if you check the status of new ingress route, it has changed from <code>orphaned</code> to <code>valid</code>. Finally, <code>app.test.example.com</code> will point to <code>app2</code> and <code>app.test.example.com/blog</code> to <code>blog</code>.</p><p>There are other interesting features which I didn't cover here:</p><ul><li>The ability to <a href="https://github.com/heptio/contour/blob/master/docs/ingressroute.md#per-upstream-active-health-checking">run health checks</a> from Envoy proxy (those are completely separate from k8s health checks)</li><li>You can <a href="https://github.com/heptio/contour/blob/master/docs/ingressroute.md#upstream-weighting">add weights</a> to different routes (canary deployments) </li><li>Support for different <a href="https://github.com/heptio/contour/blob/master/docs/ingressroute.md#load-balancing-strategy">load-balancing strategies</a></li><li><a href="https://github.com/heptio/contour/blob/master/docs/ingressroute.md#websocket-support">WebSocket support</a></li></ul><p>So, is there anything missing? Most users are using <a href="https://akomljen.com/get-automatic-https-with-lets-encrypt-and-kubernetes-ingress/">automatic Let's Encrypt SSL</a> with <a href="https://github.com/jetstack/cert-manager">cert manager</a>. Unfortunately, cert manager will not work with ingress route, yet. For more details please check <a href="https://github.com/heptio/contour/issues/509">this issue</a>. In any case, you can still use Contour with standard ingress objects and have SSL.</p><h3 id="summary">Summary</h3><p>I hope I give you some ideas when considering Contour as your default ingress controller and embracing the ingress route. The more we use it, the better it gets. Stay tuned for the next one!</p>]]></content:encoded></item><item><title><![CDATA[Kubernetes Add-ons for more Efficient Computing]]></title><description><![CDATA[A list of Kubernetes add-ons or plugins for more efficient computing.]]></description><link>https://akomljen.com/kubernetes-add-ons-for-more-efficient-computing/</link><guid isPermaLink="false">5b9fcfa8f31724000121d865</guid><category><![CDATA[kubernetes]]></category><category><![CDATA[addons]]></category><dc:creator><![CDATA[Alen Komljen]]></dc:creator><pubDate>Sun, 30 Sep 2018 21:23:32 GMT</pubDate><media:content url="https://akomljen.com/content/images/2018/09/luca-bravo-204056-unsplash.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://akomljen.com/content/images/2018/09/luca-bravo-204056-unsplash.jpg" alt="Kubernetes Add-ons for more Efficient Computing"><p>I will say that "starting" a <a href="https://akomljen.com/tag/kubernetes/">Kubernetes</a> cluster is a relatively easy job. Deploying your application to work on top of Kubernetes requires more effort especially if you are new to containers. For people that worked with Docker this can also be a relatively easy job, but of course, you need to master new tools like Helm for example. Then, when you put all together and when you try to run your application in production you will find out there are a lot of missing pieces. Probably Kubernetes doesn't do much, right? Well, Kubernetes is extensible, and there are some plugins or add-ons that will make your life easier.</p><h3 id="what-are-kubernetes-add-ons">What are Kubernetes Add-ons?</h3><p>In short, <em>add-ons extend the functionality of Kubernetes</em>. There are many of them, and chances are that you already using some. For example, network plugins or CNIs like Calico or Flannel, or CoreDNS (now a default DNS manager), or famous Kubernetes Dashboard. I say famous because that is probably the first thing that you will try to deploy once the cluster is running :). Those listed above are some core components, CNIs are must have, the same for DNS to have your cluster function properly. But there is much more you can do once you start deploying your applications. Enter the Kubernetes add-ons for more efficient computing!</p><h3 id="cluster-autoscaler-ca">Cluster Autoscaler - CA</h3><p><strong><a href="https://github.com/kubernetes/autoscaler/tree/master/cluster-autoscaler">Cluster Autoscaler</a> scales your cluster nodes based on utilization.</strong> CA will scale up the cluster if you have pending pods and scale it down if nodes are not utilized that much - default set to <code>0.5</code> and configurable with <code>--scale-down-utilization-threshold</code>. You definitely don't want to have pods in pending state and at the same time, you don't want to run underutilized nodes - waste of money!</p><p><strong>Use case:</strong> You have two instance groups or autoscaling groups in your AWS cluster. They are running in two availability zones 1 and 2. You want to scale your cluster based on utilization, but also you want to have a similar number of nodes in both zones. Also, you want to use CA auto-discovery feature, so that you don't need to define min and max number of nodes in CA as those are already defined in your auto scaling groups. And you want to deploy CA on your master nodes.</p><p>Here is the example installation of CA via Helm to match above use case:</p><pre><code>⚡ helm install --name autoscaler \
    --namespace kube-system \
    --set autoDiscovery.clusterName=k8s.test.akomljen.com \
    --set extraArgs.balance-similar-node-groups=true \
    --set awsRegion=eu-west-1 \
    --set rbac.create=true \
    --set rbac.pspEnabled=true \
    --set nodeSelector."node-role\.kubernetes\.io/master"="" \
    --set tolerations[0].effect=NoSchedule \
    --set tolerations[0].key=node-role.kubernetes.io/master \
    stable/cluster-autoscaler</code></pre><p>There are some additional changes you need to make for this to work. Please check this post for more details - <a href="https://akomljen.com/kubernetes-cluster-autoscaling-on-aws/">Kubernetes Cluster Autoscaling on AWS</a>.</p><h3 id="horizontal-pod-autoscaler-hpa">Horizontal Pod Autoscaler - HPA</h3><p><em><strong>The <a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/">Horizontal Pod Autoscaler</a> automatically scales the number of pods in a replication controller, deployment or replica set based on observed CPU utilization. With <a href="https://git.k8s.io/community/contributors/design-proposals/instrumentation/custom-metrics-api.md">custom metrics</a> support, on some other application-provided metrics as well. </strong></em></p><p>HPA is not something new in the Kubernetes world, but Banzai Cloud recently released <a href="https://github.com/banzaicloud/hpa-operator">HPA Operator</a> which simplifies it. All you need to do is to provide annotations to your Deployment or StatefulSet and HPA operator will do the rest. Take a look at supported annotations <a href="https://github.com/banzaicloud/hpa-operator#annotations-explained">here</a>.</p><p>Installation of HPA operator is fairly simple with Helm:</p><pre><code>⚡ helm repo add akomljen-charts https://raw.githubusercontent.com/komljen/helm-charts/master/charts/

⚡ helm install --name hpa \
    --namespace kube-system \
    akomljen-charts/hpa-operator

⚡ kubectl get po --selector=release=hpa -n kube-system
NAME                                  READY     STATUS    RESTARTS   AGE
hpa-hpa-operator-7c4d47dd4-9khpv      1/1       Running   0          1m
hpa-metrics-server-7766d7bc78-lnhn8   1/1       Running   0          1m</code></pre><p>With Metrics Server deployed you also have <code>kubectl top pods</code> command available. It could be useful to monitor your CPU or memory usage for pods! ;)</p><p>HPA can fetch metrics from a series of aggregated APIs ( <code>metrics.k8s.io</code>, <code>custom.metrics.k8s.io</code>, and <code>external.metrics.k8s.io</code>). But, usually, HPA will use <code>metrics.k8s.io</code> API provided by Heapster (deprecated as of Kubernetes 1.11) or Metrics Server.</p><p>After you add annotations to your Deployment you should be able to monitor it with:</p><pre><code>⚡ kubectl get hpa
NAME       REFERENCE             TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
test-app   Deployment/test-app   0%/70%    1         4         1          10m</code></pre><p>Keep in mind that the CPU target that you see above is based on defined CPU requests for this particular pod, not overall CPU available on the node.</p><h3 id="addon-resizer">Addon Resizer</h3><p><a href="https://github.com/kubernetes/autoscaler/tree/master/addon-resizer">Addon resizer</a> is an interesting plugin that you could use with Metrics Server in the above scenario. As you deploy more pods to your cluster eventually Metrics Server will need more resources. <strong>Addon resizer container watches over another container in a Deployment (Metrics Server for example) and vertically scales the dependent container up and down.</strong> Addon resizer can scale Metrics Server linearly based on the number of nodes. For more details check official docs.</p><h3 id="vertical-pod-autoscaler-vpa">Vertical Pod Autoscaler - VPA</h3><p>You need to define CPU and memory requests for services that you will deploy on Kubernetes. If you don't default CPU request is set to <code>100m</code> or <code>0,1</code> of available CPUs. Resource requests help <code>kube-scheduler</code> to decide on which node to run a particular pod. But, it is hard to define "good enough" values that will be suitable for more environments. <strong><a href="https://github.com/kubernetes/autoscaler/tree/master/vertical-pod-autoscaler">Vertical Pod Autoscaler</a> adjusts CPU and memory requests automatically based on the resource used by a pod.</strong> It uses Metrics Server to get pod metrics. Keep in mind that you still need to define resource limits manually.</p><p>I will not cover the details here as VPA really needs a dedicated blog post, but there are a few things that you should know:</p><ul><li>VPA is still an early stage project, so be aware</li><li>Your cluster must support <code>MutatingAdmissionWebhooks</code>, which is enabled by default since Kubernetes 1.9</li><li>It doesn't work together with HPA</li><li>It will restart all your pods when resource requests are updated, kind of expected</li></ul><h3 id="descheduler">Descheduler</h3><p>The <code>kube-scheduler</code> is a component responsible for scheduling in Kubernetes. But, sometimes pods can end up on the wrong node due to Kubernetes dynamic nature. You could be editing existing resources, to add node affinity or (anti) pod affinity, or you have more load on some servers and some are running almost on idle. Once the pod is running <code>kube-scheduler</code> will not try to reschedule it again. Depending on the environment you might have a lot of moving parts.</p><p><strong><a href="https://github.com/kubernetes-incubator/descheduler">Descheduler</a> checks for pods that can be moved and evicts them based on defined policies.</strong> Descheduler is not a default scheduler replacement and depends on it. This project is currently in Kubernetes incubator and not ready for production yet. But, I found it very stable and it worked nicely. Descheduler will run in your cluster as CronJob.</p><p>I wrote a dedicated post <a href="https://akomljen.com/meet-a-kubernetes-descheduler/">Meet a Kubernetes Descheduler</a> which you should check for more details.</p><h3 id="k8s-spot-rescheduler">k8s Spot Rescheduler </h3><p>I was trying to solve an issue of managing multiple auto scaling groups on AWS, where one group are on-demand instances and others are a spot. The problem is that once you scale up the spot instance group you want to move the pods from on-demand instances so you can scale it down. <strong><a href="https://github.com/pusher/k8s-spot-rescheduler">k8s spot rescheduler</a> tries to reduce the load on on-demand instances by evicting pods to spots if they are available.</strong> <em>In reality, the rescheduler can be used to remove load from any group of nodes onto a different group of nodes. They just need to be labeled appropriately.</em></p><p>I also created a Helm chart for easier deployment:</p><pre><code>⚡ helm repo add akomljen-charts https://raw.githubusercontent.com/komljen/helm-charts/master/charts/

⚡ helm install --name spot-rescheduler \
    --namespace kube-system \
    --set image.tag=v0.2.0 \
    --set cmdOptions.delete-non-replicated-pods="true" \
    akomljen-charts/k8s-spot-rescheduler</code></pre><p>For a full list of <code>cmdOptions</code> check <a href="https://github.com/pusher/k8s-spot-rescheduler#flags">here</a>. </p><p>For k8s spot rescheduler to work properly you need to label your nodes:</p><ul><li>on-demand nodes - <code>node-role.kubernetes.io/worker: "true"</code></li><li>spot nodes - <code>node-role.kubernetes.io/spot-worker: "true"</code></li></ul><p>and add <code>PreferNoSchedule</code> taint on on-demand instances to ensure that k8s spot rescheduler prefers spots when making scheduling decisions.</p><h3 id="summary">Summary</h3><p>Please keep in mind that some of the above add-ons are not compatible to work together! Also, there might be some interesting add-on that I missed here, so please let us know in comments. Stay tuned for the next one.</p>]]></content:encoded></item></channel></rss>