<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Niklas</title>
    <description>The latest articles on DEV Community by Niklas (@niklasmtj).</description>
    <link>https://dev.to/niklasmtj</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F169030%2Fb79510aa-832e-4e47-b12e-eb841f4ea1ce.JPG</url>
      <title>DEV Community: Niklas</title>
      <link>https://dev.to/niklasmtj</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/niklasmtj"/>
    <language>en</language>
    <item>
      <title>Deno and GitHub Actions workflows</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Sat, 09 Mar 2024 10:12:15 +0000</pubDate>
      <link>https://dev.to/niklasmtj/deno-and-github-actions-workflows-1dgm</link>
      <guid>https://dev.to/niklasmtj/deno-and-github-actions-workflows-1dgm</guid>
      <description>&lt;p&gt;Today I want to talk about Deno and GitHub Actions. For the last year or so, I have only built new projects with Deno. I really like the runtime, it's typescript out of the box config, formatting, linting etc. It is just a lot of fun to work with.&lt;/p&gt;

&lt;p&gt;Since I still wanted to make sure that everything works when pushed to our Github repositories, I had to build some GitHub actions. Today I want to share a repository where I collect useful actions. At the time of writing, the repository contains 3 different workflows. You can find the repository at &lt;a href="https://github.com/niklasmtj/deno-actions"&gt;niklasmtj/deno-actions on GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI
&lt;/h2&gt;

&lt;p&gt;The CI workflow runs on every PR creation or commit. It checks the format, runs tests, and lints the whole project. Since these steps are usually really fast, I did not limit it to just the changed files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CI"&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Checkout"&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Setup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Deno"&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;denoland/setup-deno@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;deno-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.x"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tests"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deno task test&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Lint"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deno task lint&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Format"&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deno task format&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To run this workflow you need to set up 3 tasks in the &lt;code&gt;deno.json&lt;/code&gt; file. First set up &lt;code&gt;test&lt;/code&gt; with the required permissions. Then &lt;code&gt;lint&lt;/code&gt; and &lt;code&gt;format&lt;/code&gt;. This is done in &lt;code&gt;deno.json&lt;/code&gt; under the &lt;code&gt;tasks&lt;/code&gt; key as you can see in the following block of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"lint"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deno lint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deno fmt --check"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"test"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"deno test --allow-read --allow-write --allow-net"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses the `--allow-read --allow-write --allow-net' flags to run the tests. When setting up your workflow tasks, you can easily check the commands locally. Deno will help you figure out the permission flags you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Docker Container Images
&lt;/h2&gt;

&lt;p&gt;The second action will build a container image using Docker. The workflow runs when new pushes are made to the `main' branch. This happens most often after merging a pull request.&lt;/p&gt;

&lt;p&gt;Make sure that you have a &lt;code&gt;Dockerfile&lt;/code&gt; describing your desired container image in the root directory of the repository. This is used by the &lt;code&gt;build-and-push&lt;/code&gt; action as it's image recipe.&lt;/p&gt;

&lt;p&gt;It is set up using &lt;code&gt;buildx&lt;/code&gt;, which allows you to build multi-architecture images. For example, if you are building your containers locally on an M1 Mac, these are &lt;code&gt;arm64&lt;/code&gt; images. If your hosting provider does not support &lt;code&gt;arm64&lt;/code&gt; images, but rather uses Intel or AMD processors on their servers, you will need to build &lt;code&gt;amd64&lt;/code&gt; images. Using &lt;code&gt;buildx&lt;/code&gt; and &lt;code&gt;qemu&lt;/code&gt; we can make sure we're able to build both in the same build. This is why you see &lt;code&gt;linux/amd64,linux/arm64&lt;/code&gt; in the platforms fields in the following workflow file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Build&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Container&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Image"&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ci&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Checkout"&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up QEMU&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-qemu-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Docker Buildx&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/setup-buildx-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;platforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;linux/amd64,linux/arm64&lt;/span&gt;

      &lt;span class="c1"&gt;# Login to Docker Hub or any other registry here&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Login to Docker Hub&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.DOCKERHUB_TOKEN }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and push&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/build-push-action@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
          &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;niklasmtj/deno-image:latest&lt;/span&gt;
          &lt;span class="na"&gt;cache-from&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha&lt;/span&gt;
          &lt;span class="na"&gt;cache-to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type=gha,mode=max&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The built image here will then be pushed to Docker Hub after login. Make sure your organization or repository already has the &lt;code&gt;DOCKERHUB_USERNAME&lt;/code&gt; and &lt;code&gt;DOCKERHUB_TOKEN&lt;/code&gt; secrets set. These are set in a repository under `Settings -&amp;gt; Secrets and Variables -&amp;gt; Actions -&amp;gt; Repository secrets'. How to obtain a Docker Hub token can be found in the &lt;a href="https://docs.docker.com/security/for-developers/access-tokens/#create-an-access-token"&gt;Docker documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Caching is also set up when the container image is built to ensure that the subsequent execution is as fast as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Test Coverage Website and Publish to GitHub Pages
&lt;/h2&gt;

&lt;p&gt;Testing should be an integral part of application development. To make sure that every part of the application is covered with tests, Deno supports creating a coverage report right out of the box (🤝). The third action supports building just that. Create the coverage and push it to GitHub pages. To be able to publish, you need to make sure that GitHub Pages is enabled for the repository you want to build coverage for.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`yaml&lt;br&gt;
name: Code Coverage&lt;/p&gt;

&lt;p&gt;on:&lt;br&gt;
  push:&lt;br&gt;
    branches: [main]&lt;br&gt;
  workflow_dispatch:&lt;/p&gt;

&lt;p&gt;permissions:&lt;br&gt;
  contents: read&lt;br&gt;
  pages: write&lt;br&gt;
  id-token: write&lt;/p&gt;

&lt;p&gt;jobs:&lt;br&gt;
  ci:&lt;br&gt;
    runs-on: ubuntu-latest&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;steps:
  - name: Setup repo
    uses: actions/checkout@v4

  - name: Setup Deno
    uses: denoland/setup-deno@v1

  - name: Create lcov file
    run: deno task coverage

  - name: Setup Pages
    uses: actions/configure-pages@v4

  - name: Upload artifact
    uses: actions/upload-pages-artifact@v3
    with:
      path: './coverage/html'

  - name: Deploy to GitHub Pages
    id: deployment
    uses: actions/deploy-pages@v4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;One thing you need to make sure is that GitHub Actions has permissions to publish the created website. Make sure you set it under the &lt;code&gt;permissions&lt;/code&gt; key in the yaml file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This repository is not set in stone. I will try to keep the dependencies up to date (shout out to Dependabot) and will add or change actions in the future. So make sure you keep an eye on the repo (click the watch button in the top right corner).&lt;/p&gt;

&lt;p&gt;Thanks for reading and have a great day!&lt;/p&gt;

</description>
      <category>deno</category>
      <category>devops</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Conditional GitHub Actions job</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Tue, 14 Feb 2023 15:15:00 +0000</pubDate>
      <link>https://dev.to/niklasmtj/conditional-github-actions-job-1an4</link>
      <guid>https://dev.to/niklasmtj/conditional-github-actions-job-1an4</guid>
      <description>&lt;p&gt;With GitHub Actions it is possible to trigger jobs under certain conditions. GitHub provides the &lt;code&gt;if&lt;/code&gt; for this purpose, which can be called at job level. In the following you will see how this can be achieved. &lt;/p&gt;

&lt;h2&gt;
  
  
  Syntax
&lt;/h2&gt;

&lt;p&gt;As mentioned above the syntax of the &lt;code&gt;if&lt;/code&gt; can be found on the sub level of the job name.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;example-job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref_name == 'main'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here it is possible to check for different conditions. GitHub offers various variables in the &lt;sup id="fnref1"&gt;1&lt;/sup&gt;, which can also be combined with the &lt;sup id="fnref2"&gt;2&lt;/sup&gt; provided.&lt;/p&gt;

&lt;p&gt;The Ifs can follow two different formats. Both are equivalent to each other in the case. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;if: github.ref_name == 'main'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;if : {{if: github.ref_name == 'main'}}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; When using literals in expressions it is needed to enclose them using single quotes. Double quotes will fail &lt;sup id="fnref3"&gt;3&lt;/sup&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1r0xz2zyr2r3uesxxuu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb1r0xz2zyr2r3uesxxuu.png" alt="jobs screenshot from GitHub" width="800" height="131"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  On push to main branch
&lt;/h3&gt;

&lt;p&gt;The following GitHub Action will return "This is the main branch." only when the push is done to the named branch. Otherwise it will be skipped and the action will not start.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-main-branch&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;check-main-branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.ref_name == 'main'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "This is the main branch."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Output
&lt;/h4&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6bz7sby098gu9psat8d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz6bz7sby098gu9psat8d.png" alt="check-main-branch output" width="800" height="293"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  On Release
&lt;/h3&gt;

&lt;p&gt;This GitHub Action will only run when a release was published which has the tag name including &lt;code&gt;-beta&lt;/code&gt;. When the tag is named different it is not triggered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requirement:&lt;/strong&gt; Use of semantic versioning&lt;sup id="fnref4"&gt;4&lt;/sup&gt;. Example tag: &lt;code&gt;1.0.0-beta&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-beta-tag&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;published&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;beta-deployment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;contains(github.ref_name, '-beta')&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;echo "This is the beta tag of $GITHUB_REF_NAME"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; &lt;code&gt;$GITHUB_REF_NAME&lt;/code&gt; is part of the exposed environment variables &lt;sup id="fnref5"&gt;5&lt;/sup&gt; of the runner.&lt;/p&gt;

&lt;h4&gt;
  
  
  Output
&lt;/h4&gt;

&lt;p&gt;Following is the output of the actions which are triggered after a release. In the first picture you can also see that the main-branch action is triggered but skipped, since the tag name is not &lt;code&gt;main&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4lnm5khvpx4t6panxw6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw4lnm5khvpx4t6panxw6.png" alt="Actions overview after beta release" width="800" height="92"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphs3md3x2p6bu5ulxyl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fphs3md3x2p6bu5ulxyl1.png" alt="Output after beta release" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions" rel="noopener noreferrer"&gt;https://docs.github.com/en/actions/learn-github-actions/expressions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions/contexts" rel="noopener noreferrer"&gt;GitHub Actions Contexts&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#functions" rel="noopener noreferrer"&gt;Expressions' built in functions&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions/expressions#literals" rel="noopener noreferrer"&gt;Expressions with literals&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;Semantic Versioning&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn5"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables" rel="noopener noreferrer"&gt;Default Environment Variables&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>vibecoding</category>
    </item>
    <item>
      <title>Use GitHub Container Registry (GHCR) to host your Helm Charts</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Sun, 08 May 2022 20:14:29 +0000</pubDate>
      <link>https://dev.to/niklasmtj/use-github-container-registry-ghcr-to-host-your-helm-charts-nef</link>
      <guid>https://dev.to/niklasmtj/use-github-container-registry-ghcr-to-host-your-helm-charts-nef</guid>
      <description>&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; Full commands can be found at the end of the post.&lt;/p&gt;

&lt;p&gt;I recently started to check out &lt;a href="https://helm.sh"&gt;Helm&lt;/a&gt; and thought about the combination of hosting the charts on &lt;a href="https://ghcr.io"&gt;GitHub Container Registry (GHCR)&lt;/a&gt; since the charts follow the regular OCI (Open Container Image) standard which is also used by Docker container images. Because of that, I tried to understand the steps necessary to host my charts on GHCR since public charts / containers are free of charge. The following post will describe the necessary steps that are required. So let’s dive right in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Helm
&lt;/h2&gt;

&lt;p&gt;First of all, make sure that you have Helm installed in a version &lt;code&gt;&amp;gt;3.8&lt;/code&gt; since OCI support before that is experimental. It is possible to use the feature before &lt;code&gt;3.8&lt;/code&gt; with the environmental variable &lt;code&gt;HELM_EXPERIMENTAL_OCI=1&lt;/code&gt; set.&lt;br&gt;
If you don’t have Helm installed yet you can do so via &lt;code&gt;brew install helm&lt;/code&gt; on macOS. For different operating systems please check Helm’s docs for more installation options &lt;sup id="fnref1"&gt;1&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;For presentation purposes, I will use the default chart created by Helm which is the result of running &lt;code&gt;helm create &amp;lt;chart-name&amp;gt;&lt;/code&gt;. For this post, I will call it &lt;code&gt;helm create example-chart&lt;/code&gt;. The content of the created directory is not the subject of this post but more information can be found in the Helm docs &lt;sup id="fnref2"&gt;2&lt;/sup&gt;.&lt;/p&gt;

&lt;p&gt;To package it all up and make it ready for pushing to GHCR Helm provides the &lt;code&gt;helm package&lt;/code&gt; command which takes the argument of the directory to package. In the chart’s parent directory. So using &lt;code&gt;helm package example-chart&lt;/code&gt; will create a new package with the name of &lt;code&gt;example-chart-0.1.0.tgz&lt;/code&gt;. The version number can be changed in &lt;code&gt;example-chart/Chart.yaml&lt;/code&gt; under the &lt;code&gt;version&lt;/code&gt; attribute.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Container Registry Preparation
&lt;/h2&gt;

&lt;p&gt;To push to GitHub Container Registry we need to authenticate with a Personal Access Token. If you never created one you can follow the steps described here &lt;sup id="fnref3"&gt;3&lt;/sup&gt;. Make sure that your Token has the following permissions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;read:packages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;write:packages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;delete:packages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can eather just export your Access Token for the terminal session and use &lt;code&gt;export GHCR_PAT=ghp_...&lt;/code&gt; with your Token after the &lt;code&gt;=&lt;/code&gt; sign or use your &lt;code&gt;.zshrc&lt;/code&gt; or &lt;code&gt;.bashrc&lt;/code&gt;. To do so, edit your file with e.g. &lt;code&gt;nano .zshrc&lt;/code&gt; and add the export line in your file. The one you need depends on your current default shell. To see which one is your default use &lt;code&gt;echo $SHELL&lt;/code&gt; in your terminal.&lt;br&gt;
After saving and closing the editor via &lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;x&lt;/code&gt; and confirming with &lt;code&gt;y&lt;/code&gt; you have to reload your &lt;code&gt;.zshrc&lt;/code&gt; with &lt;code&gt;source .zshrc&lt;/code&gt;. After that, the Token should be available for use.&lt;/p&gt;
&lt;h2&gt;
  
  
  Logging in
&lt;/h2&gt;

&lt;p&gt;To check if things are working use &lt;code&gt;echo $GHCR_PAT | docker login ghcr.io -u &amp;lt;GITHUB-USERNAME&amp;gt; --password-stdin&lt;/code&gt; with your username following the &lt;code&gt;-u&lt;/code&gt; flag.&lt;br&gt;
If everything is configured correctly you should get the answer of &lt;code&gt;Login Succeeded&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Push to GHCR
&lt;/h2&gt;

&lt;p&gt;Pushing now is easy. Get the current chart version and use Helm to push to GHCR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CHART_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'version:'&lt;/span&gt; ./example-chart/Chart.yaml | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{ print $2}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
helm push example-chart-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CHART_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tgz oci://ghcr.io/niklasmtj
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get something similar like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pushed: ghcr.io/niklasmtj/example-chart:0.1.0
Digest: sha256:c13e9bc40b48460a7b3af6a5df78b4faeff6af7d0688333124884117478be18c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now your package is pushed and you can check it out via your profile under the tab &lt;em&gt;packages&lt;/em&gt;. For example, this example chart can be found under &lt;a href="https://github.com/users/niklasmtj/packages/container/package/example-chart"&gt;https://github.com/users/niklasmtj/packages/container/package/example-chart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This is how you can host your Helm charts on GitHub’s Container Registry.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR commands
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm create &amp;lt;chart-name&amp;gt;
helm package &amp;lt;chart-name&amp;gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$GHCR_PAT&lt;/span&gt; | docker login ghcr.io &lt;span class="nt"&gt;-u&lt;/span&gt; &amp;lt;GITHUB-USERNAME&amp;gt; &lt;span class="nt"&gt;--password-stdin&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CHART_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s1"&gt;'version:'&lt;/span&gt; ./path/to/Chart.yaml | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-n1&lt;/span&gt; | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{ print $2 }'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
helm push &amp;lt;chart-name&amp;gt;-&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CHART_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;.tgz oci://ghcr.io/&amp;lt;GITHUB-USERNAME&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://helm.sh/docs/intro/install/"&gt;https://helm.sh/docs/intro/install/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://helm.sh/docs/chart_template_guide/getting_started/"&gt;https://helm.sh/docs/chart_template_guide/getting_started/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry"&gt;https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>github</category>
    </item>
    <item>
      <title>How to install Weave's Ignite for Firecracker VMs with simple script</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Sun, 20 Feb 2022 15:29:23 +0000</pubDate>
      <link>https://dev.to/niklasmtj/how-to-install-weaves-ignite-for-firecracker-vms-with-simple-script-2b0</link>
      <guid>https://dev.to/niklasmtj/how-to-install-weaves-ignite-for-firecracker-vms-with-simple-script-2b0</guid>
      <description>&lt;p&gt;Since I want to get more into &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker MicroVMs&lt;/a&gt; I started playing around with &lt;a href="https://ignite.readthedocs.io/en/stable/"&gt;Weave’s Ignite&lt;/a&gt; which gives a familiar interface to &lt;code&gt;docker&lt;/code&gt; to interact with the VMs. I do this with &lt;a href="https://m.do.co/c/c0cb5eb6c95e"&gt;DigitalOcean’s&lt;/a&gt; droplets  (Affiliate link, get $100 in credits for 60 days for free) since they have KVM enabled and are pretty inexpensive. This script will also work on the $5/month Droplets. Since I set up a new droplet every time to save costs when testing Ignite out I wanted to keep it simple and "automate" the installation with a quick bash script. The steps are taken from the installation page from the &lt;a href="https://ignite.readthedocs.io/en/stable/installation/"&gt;Ignite docs&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;First, you have to create the script on your VPS which can be done by opening it with an editor of choice. On a Digital Ocean droplet, you can use for example &lt;code&gt;vim&lt;/code&gt;. To create the bash script file type &lt;code&gt;vim install-ignite.sh&lt;/code&gt; in your terminal window.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script
&lt;/h2&gt;

&lt;p&gt;Now we go over to the more interesting part of the post. The script itself. It will install all the dependencies of Ignite (as of February 2022) and install Ignite afterwards. After the installation, it will check if &lt;code&gt;ignite&lt;/code&gt; was installed successfully by checking the currently installed version. So now go forward and copy the following script in your open editor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#! /usr/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;# Update apt-get repository and install dependencies&lt;/span&gt;
apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; dmsetup openssh-client git binutils

&lt;span class="c"&gt;# Install containerd if it's not present -- prevents breaking docker-ce installations&lt;/span&gt;
which containerd &lt;span class="o"&gt;||&lt;/span&gt; apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; &lt;span class="nt"&gt;--no-install-recommends&lt;/span&gt; containerd

&lt;span class="c"&gt;# Installing CNI&lt;/span&gt;
&lt;span class="c"&gt;# Current version from https://github.com/containernetworking/cni/releases&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CNI_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v1.0.1
&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"x86_64"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;amd64 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo &lt;/span&gt;arm64&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;ARCH
&lt;span class="nb"&gt;sudo mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/cni/bin
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/containernetworking/plugins/releases/download/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CNI_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/cni-plugins-linux-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CNI_VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tgz"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /opt/cni/bin

&lt;span class="c"&gt;# Installing Ignite&lt;/span&gt;
&lt;span class="c"&gt;# Get the current version from https://github.com/weaveworks/ignite/releases&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v0.10.0
&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;go &lt;span class="nb"&gt;env &lt;/span&gt;GOARCH 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"amd64"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;GOARCH

&lt;span class="k"&gt;for &lt;/span&gt;binary &lt;span class="k"&gt;in &lt;/span&gt;ignite ignited&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Installing &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;binary&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;..."&lt;/span&gt;
    curl &lt;span class="nt"&gt;-sfLo&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;binary&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; &lt;span class="s2"&gt;"https://github.com/weaveworks/ignite/releases/download/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;binary&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;GOARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;chmod&lt;/span&gt; +x &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;binary&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;
    &lt;span class="nb"&gt;sudo mv&lt;/span&gt; &lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;binary&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; /usr/local/bin
&lt;span class="k"&gt;done&lt;/span&gt;

&lt;span class="c"&gt;# Check if the installation was successful&lt;/span&gt;
ignite version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Make it executable
&lt;/h2&gt;

&lt;p&gt;To use the script now we have to make it executable. This is easily done with &lt;code&gt;chmod +x install-ignite.sh&lt;/code&gt;. Now we can start the script with &lt;code&gt;./install-ignite.sh&lt;/code&gt; from the directory you created the script in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Have fun with Weave’s Ignite
&lt;/h2&gt;

&lt;p&gt;The CLI feels very familiar with everyone who knows about &lt;code&gt;docker&lt;/code&gt; commands. Starting a new VM can be done with &lt;code&gt;ignite run weaveworks/ignite-ubuntu&lt;/code&gt;. From there have fun playing around with Ignite.&lt;/p&gt;

&lt;p&gt;That's it for now but you can be sure that there will be more posts about Firecracker MicroVMs and how to use them in the future.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;

</description>
      <category>devops</category>
      <category>bash</category>
      <category>firecracker</category>
      <category>ignite</category>
    </item>
    <item>
      <title>An alternative Docker installation with Multipass on macOS without using Docker for Mac</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Sat, 29 Jan 2022 18:44:56 +0000</pubDate>
      <link>https://dev.to/niklasmtj/an-alternative-docker-installation-with-multipass-on-macos-without-using-docker-for-mac-5fip</link>
      <guid>https://dev.to/niklasmtj/an-alternative-docker-installation-with-multipass-on-macos-without-using-docker-for-mac-5fip</guid>
      <description>&lt;p&gt;Last week I received an email from the Docker Team which said that Docker for Mac (the software which also comes with a GUI) will be forbidden for commercial use when the company has more than 250 employees AND makes more than $10 million per year. To use it commercially the company has to get licenses for every developer using it, starting at $5/month. This made me think what an alternative could be for devs that don’t want to use Docker for Mac anymore, since I read a lot of posts that many devs don’t even need it. Most of them interact via CLI anyway. I stumbled across &lt;a href="https://medium.com/@gourneau_38759/docker-engine-with-multipass-on-macos-dc44ff09ffb2"&gt;a nice article from Josh Gorneau&lt;/a&gt; where he uses multipass to host his Docker VM. In this case, it is a Ubuntu 20.04 installation. So a couple of commands will be similar to Josh’s article such as the VM configuration used in this post. So let’s start with setting up multipass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install Multipass
&lt;/h2&gt;

&lt;p&gt;Multipass is a project made available by Canonical&lt;sup id="fnref1"&gt;1&lt;/sup&gt; also develops and publish the Ubuntu&lt;sup id="fnref2"&gt;2&lt;/sup&gt; Linux distribution. So install it either from the &lt;a href="https://multipass.run/"&gt;website&lt;/a&gt; or via &lt;a href="https://brew.sh/"&gt;homebrew&lt;/a&gt;.&lt;br&gt;
If you don’t have the Docker CLI yet, install it too&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install multipass docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating an SSH Key
&lt;/h2&gt;

&lt;p&gt;Since we will connect to our VM via SSH we need a new SSH key that will be imported while setting up the VM. To do so we can use &lt;code&gt;ssh-keygen&lt;/code&gt; like in the following. I will create a new one since I use just one for every service I connect to. Follow the prompts to generate a key. Mine will be called &lt;code&gt;docker-multipass&lt;/code&gt;. Since macOS and Ubuntu are pretty similar in creating SSH keys you can also follow the instructions over at &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-set-up-ssh-keys-on-ubuntu-20-04"&gt;DigitalOcean&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;➜  ~ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/user/.ssh/id_rsa): /Users/user/.ssh/docker-multipass
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/user/.ssh/docker-multipass.
Your public key has been saved in /Users/user/.ssh/docker-multipass.pub.
The key fingerprint is:
SHA256:LjhUL0bZ8lXf9iIrOPF1EXjAJguNu3YbvUVH2tUHcak user@domain
The key's randomart image is:
+---[RSA 3072]----+
|         o .oo+o+|
|       oo o.+..==|
|      = .o.+ .++=|
|     o +...  Eoo+|
|    . o S. .o.o..|
|   . o oo+o..+.. |
|    o ..+.oo.o   |
|     . . ....    |
|                 |
+----[SHA256]-----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To get the generated public key which will be imported to the Ubuntu VM on the setup you can use &lt;code&gt;cat /Users/user/.ssh/docker-multipass.pub&lt;/code&gt;. Copy the output since you will need it in the next step.&lt;/p&gt;

&lt;h2&gt;
  
  
  The VM Config
&lt;/h2&gt;

&lt;p&gt;In the following, we will create a config that will be used to set up the VM. This is done with a cloud-init configuration. Cloud-init&lt;sup id="fnref3"&gt;3&lt;/sup&gt; is a configuration tool from Ubuntu that helps set up virtual machines. More Information about it can be found here: &lt;a href="https://ubuntu.com/blog/using-cloud-init-with-multipass"&gt;Using cloud-init with Multipass | Ubuntu&lt;/a&gt;. The config is taken from Josh’s article linked above.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;docker.yaml&lt;/code&gt; file in your current directory and paste in the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;users&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu&lt;/span&gt;
    &lt;span class="na"&gt;sudo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ALL=(ALL) NOPASSWD:ALL&lt;/span&gt;
    &lt;span class="na"&gt;ssh-authorized-keys&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ssh-rsa AAAAB3...&lt;/span&gt;

&lt;span class="na"&gt;package_update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="na"&gt;packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;docker.io&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;avahi-daemon&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;apt-transport-https&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ca-certificates&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;curl&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;gnupg&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;lsb-release&lt;/span&gt;
&lt;span class="na"&gt;runcmd&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sudo curl -fsSL https://get.docker.com | sudo bash&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sudo systemctl enable docker&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sudo systemctl enable -s HUP ssh&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sudo groupadd docker&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sudo usermod -aG docker ubuntu&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The configuration sections explained
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;users&lt;/code&gt; is an Array that defines what users should be set up while creating the VM. Here you will also paste your SSH key created in the last step.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;package_update&lt;/code&gt; tells the Ubuntu VM to run &lt;code&gt;apt-get update&lt;/code&gt; before installing any packages in the following step to make sure the newest packages are available for installation&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;packages&lt;/code&gt; as the name suggests tells Ubuntu which packages to install to proceed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;runcmd&lt;/code&gt; will be run after installing the packages before. In this case, it will download Docker, enabling it via systemctl, creating the docker group and afterwards adding our newly created user to this group. This will make sure that our &lt;em&gt;ubuntu&lt;/em&gt; is allowed to interact with Docker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Launching the Ubuntu VM
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;multipass launch -c 4 -m 8G -d 20G -n docker 20.04 --cloud-init docker.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;-c&lt;/code&gt; this specifies how many CPUs the VM can use. In this case 4&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-m&lt;/code&gt; defines the amount of RAM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-d&lt;/code&gt; is for setting the disk space used by the VM&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;-n&lt;/code&gt; sets the VM’s name&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Handling possible permission problems with a non-root user
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;launch failed: multipass socket access denied
Please check that you have read/write permissions to '/var/run/multipass_socket'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you use a macOS user which is not a local admin like I do you have to give this user the permissions to read/write to multipass’s socket to be able to function. So use the &lt;code&gt;chmod&lt;/code&gt; command to give your user the permissions needed. Otherwise, you will not be able to start multipass instances.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo chmod a+rw /var/run/multipass_socket
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that, the socket should be usable for the non-root user. &lt;/p&gt;

&lt;h2&gt;
  
  
  Log into VM
&lt;/h2&gt;

&lt;p&gt;After launching the multipass Ubuntu VM in the last step check if it is possible to ssh into the newly created VM.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh ubuntu@docker.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Permission denied (publickey)
&lt;/h3&gt;

&lt;p&gt;When the error &lt;code&gt;ubuntu@docker.local: Permission denied (publickey).&lt;/code&gt; will be thrown check if you provide the right ssh key when connecting. Without specifying a key &lt;em&gt;ssh&lt;/em&gt; will use your main ssh key which is most often &lt;code&gt;id_rsa&lt;/code&gt;. To use the key created above add an entry to your &lt;code&gt;.ssh/config&lt;/code&gt; like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host docker.local
  HostName docker.local
  User ubuntu
  IdentityFile ~/.ssh/docker-multipass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you can connect to your newly created VM close the connection with &lt;code&gt;exit&lt;/code&gt; and proceed to the next step where we will now use Docker from our host machine to interact with the Docker installed in the virtual machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Time to use Docker
&lt;/h2&gt;

&lt;p&gt;Now that everything is set up check if Docker is answering. Since we use a custom Docker host we have to provide the cli where we want to execute the commands. This is where &lt;code&gt;DOCKER_HOST&lt;/code&gt; comes into play, so use it like in the example below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DOCKER_HOST="ssh://ubuntu@docker.local" docker version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Save the Docker Host environment variable in your terminal session
&lt;/h3&gt;

&lt;p&gt;Since always putting the &lt;code&gt;DOCKER_HOST&lt;/code&gt; variable in front of the commands can be annoying you can export it in your terminal session or save it in your &lt;code&gt;.bashrc&lt;/code&gt; or &lt;code&gt;.zshrc&lt;/code&gt; to have it in every terminal session in the future.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export DOCKER_HOST="ssh://ubuntu@docker.local"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Have fun using Docker
&lt;/h2&gt;

&lt;p&gt;When everything is set up and your Docker is usable from within your terminal you can now start any container you like.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run -p 9898:9898 stefanprodan/podinfo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Exposing Ports to the Host machine
&lt;/h3&gt;

&lt;p&gt;Right now (January 22) multipass does not automatically port-forward the exposed ports from within the VM. To set this up for a port use ssh with the &lt;code&gt;-L&lt;/code&gt; argument which will block the port on your host machine which is exposed on the connected machine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ssh -L &amp;lt;Host-Port&amp;gt;:localhost:&amp;lt;VM-Port&amp;gt; ubuntu@docker.local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I hope this article helped you set up and use Docker without using the Docker for Mac software.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://canonical.com/"&gt;Canonical&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://ubuntu.com/"&gt;Ubuntu&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://cloud-init.io/"&gt;cloud-init&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>multipass</category>
      <category>macos</category>
    </item>
    <item>
      <title>What to do when macOS keyboard writes wrong special characters</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Mon, 10 Jan 2022 20:31:19 +0000</pubDate>
      <link>https://dev.to/niklasmtj/what-to-do-when-macos-keyboard-writes-wrong-special-characters-45a5</link>
      <guid>https://dev.to/niklasmtj/what-to-do-when-macos-keyboard-writes-wrong-special-characters-45a5</guid>
      <description>&lt;p&gt;At the moment I always have an external Keyboard connected to my MacBooks over a USB-C Dongle when I am working. From time to time the keyboard layout switches after waking the laptop up again after a couple of minutes. For example, it starts to write &lt;code&gt;^&lt;/code&gt;  where normally the &lt;code&gt;&amp;lt;&lt;/code&gt; is. This is my current solution to fix this issue.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check Keyboard settings
&lt;/h2&gt;

&lt;p&gt;First of all, check the keyboard settings in &lt;code&gt;Settings -&amp;gt; Keyboard -&amp;gt; Input Sources&lt;/code&gt; if anything changed. Sometimes this can change since macOS has the default shortcut of &lt;code&gt;^&lt;/code&gt;(control) + &lt;code&gt;space&lt;/code&gt; set. I already had the scenario that I miss pressed this shortcut and changed it to a different input source.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delete the Keyboard preferences file
&lt;/h2&gt;

&lt;p&gt;Since this is most often not the case and my input source is on DE which is my default I try to reset the keyboard settings. When connecting a keyboard for the first time macOS asks which key is next to the left &lt;code&gt;shift&lt;/code&gt; key on the connected keyboard. To toggle this dialogue again one has to delete a &lt;code&gt;plist&lt;/code&gt; file. This is where macOS stores its’ settings.&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;keyboardtype.plist&lt;/em&gt; of the current user can be found in &lt;code&gt;~/Library/Preferences/com.apple.keyboardtype.plist&lt;/code&gt; so removing that is possible with the following command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; ~/Library/Preferences/com.apple.keyboardtype.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this file is not available in the home Library there should be one in the root &lt;em&gt;/Library&lt;/em&gt; of macOS. This can be found in &lt;code&gt;/Library/Preferences/com.apple.keyboardtype.plist&lt;/code&gt; and can be removed with the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo rm&lt;/span&gt; /Library/Preferences/com.apple.keyboardtype.plist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After removing the file(s) remove the keyboard from the macOS device and restart it. After restarting the computer log in to the desired user and plug the keyboard back in. macOS should then show you the wanted keyboard setup screen again. After setting up the keyboard again the special keys should work as expected again.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;

</description>
      <category>macos</category>
    </item>
    <item>
      <title>GitHub Actions workflows in combination with GitHub Container Registry Package Visibility</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Mon, 18 Oct 2021 19:04:39 +0000</pubDate>
      <link>https://dev.to/niklasmtj/github-actions-workflows-in-combination-with-github-container-registry-package-visibility-1no0</link>
      <guid>https://dev.to/niklasmtj/github-actions-workflows-in-combination-with-github-container-registry-package-visibility-1no0</guid>
      <description>&lt;p&gt;Last week my task was to set up a container image that we wanted to use to test the GitHub Container Registry (GHCR). We wanted to see if we could lower our building times for one of our CI jobs when using GitHub’s registry to pull from. The following is a description of an error we encountered and how we got rid of it. Additionally, I will talk about the different visibility types of container images or packages how they are called at GitHub which were a hurdle we had to take with the registry. &lt;/p&gt;

&lt;p&gt;The  &lt;code&gt;workflow.yaml&lt;/code&gt; file we used looked something like the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;continous-integration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Log in to the Container registry&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker/login-action@v1&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;registry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io&lt;/span&gt;
          &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.actor }}&lt;/span&gt;
          &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pull Image from ghcr.io&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;docker pull ghcr.io/USERNAME/MY_PRIVATE_IMAGE&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, we check out the repository and log into the GitHub Container Registry afterwards with our &lt;code&gt;github.actor&lt;/code&gt; as the username and the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; as the password. After a successful login, we’ll pull our container image from the registry. The steps coming afterwards are not subject to the error we encountered so these are left out in this article. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Error
&lt;/h2&gt;

&lt;p&gt;After triggering the workflow with a Pull Request the workflow quickly answered with an &lt;code&gt;Error response from daemon: unauthorized&lt;/code&gt;  when getting into the &lt;code&gt;docker pull ghcr.io/USERNAME/MY_PRIVATE_IMAGE&lt;/code&gt; step.  After searching for an answer in the GitHub docs and the internet we found out that our problem lays inside of the visibility type of the container image. I will go into further detail in the sections below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Package Settings and Visibility
&lt;/h2&gt;

&lt;p&gt;The problem happened with an image built and saved in a GitHub organization account. For regular packages in GHCR, there are two types available – &lt;code&gt;public&lt;/code&gt; and &lt;code&gt;private&lt;/code&gt;. There is also a third visibility type of &lt;code&gt;internal&lt;/code&gt; available in the GitHub Enterprise Cloud plan. After creating and pushing a container image to GitHub Packages the default visibility type after creation is always set to private. This can be changed in the package settings. In the following the 3 options will be described and how they influence the ability to work with these packages in GitHub Actions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Public
&lt;/h2&gt;

&lt;p&gt;Changing the package’s visibility to &lt;code&gt;public&lt;/code&gt; is the easiest option of these 3 to work with. After making the package public it is visible to anyone and it can be pulled anonymously via ghcr.io. This is good for public images of applications like web apps or web servers like nginx. While working with GitHub Actions this is also the easiest option of visibility since we don’t need to login to the registry first. So using a public repository is as straightforward as it gets. However, this was not a viable option for us.&lt;/p&gt;

&lt;p&gt;Caution: When making a package public it cannot be made private again&lt;/p&gt;

&lt;h2&gt;
  
  
  Private
&lt;/h2&gt;

&lt;p&gt;The visibility option of &lt;code&gt;private&lt;/code&gt; is the default one when a container image is created in the first place. Accessibility is restricted only to people who have been given the rights from the package owner in the package’s settings. When given access it is possible to pull or with advanced rights write/update or delete the package entirely. To pull or change the image via CLI or API one has to authenticate with the container registry first. Authentication to pull, update or delete the container image happens via a Personal Access Token (PAT). How to do that is described in the GitHub documentation. &lt;sup id="fnref1"&gt;1&lt;/sup&gt; Without authenticating first one will see the error response &lt;code&gt;unauthorized&lt;/code&gt; from the beginning of this post. For GitHub Actions workflows GitHub recommends using the &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; instead which is available in the workflow. &lt;/p&gt;

&lt;p&gt;Anyway using a private package in a GitHub Action workflow did not work even when the account that triggered the workflow and was used to authenticate with the container registry. The login to the registry even succeeded but the following pull step failed due to the &lt;code&gt;unauthorized&lt;/code&gt; error shown above. Even after giving the repository’s GitHub Actions workflow access to the package &lt;sup id="fnref2"&gt;2&lt;/sup&gt;, it was not possible to pull the image. &lt;/p&gt;

&lt;h2&gt;
  
  
  Internal
&lt;/h2&gt;

&lt;p&gt;The solution came from a GitHub community answer &lt;sup id="fnref3"&gt;3&lt;/sup&gt;. The &lt;code&gt;internal&lt;/code&gt; visibility type is the answer to our problem here. With this setting, the container image is visible within the whole organization or all the organizations in the enterprise. Every member will have at least read/pull access to the package. Write and Delete rights are then given via the package’s settings.&lt;br&gt;
The GitHub Actions documentation is pretty vague about what the internal visibility does differently than giving the user account permissions who triggers the workflow in the first place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;After setting the package’s visibility to &lt;code&gt;internal&lt;/code&gt; it was possible for our workflow to successfully run. Anyway, we found out that the GitHub repository is not yet a better option to migrate to with our CI runs. Having the possibility to cache the container image would make a huge difference in speed. But since this is not (yet?) possible we will stay with our current setup.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas &lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-container-registry#authenticating-to-the-container-registry"&gt;Working with the Container registry - GitHub Docs&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://docs.github.com/en/packages/learn-github-packages/configuring-a-packages-access-control-and-visibility#github-actions-access-for-organization-owned-container-images"&gt;Configuring a package’s access control and visibility - GitHub Docs&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://github.community/t/cant-pull-private-ghcr-io-docker-image-using-github-token/177592/3"&gt;Can’t pull private ghcr.io Docker image using GITHUB_TOKEN - #3 by mpdude - GitHub Actions - GitHub Support Community&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>My first DevOps job interview Part 3 of 3</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Thu, 29 Apr 2021 07:18:39 +0000</pubDate>
      <link>https://dev.to/niklasmtj/my-first-devops-job-interview-part-3-of-3-3n00</link>
      <guid>https://dev.to/niklasmtj/my-first-devops-job-interview-part-3-of-3-3n00</guid>
      <description>&lt;p&gt;&lt;strong&gt;This article is part 3 of a 3 part series of my job interview experience as a DevOps Engineer.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-1"&gt;Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-2"&gt;Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-2"&gt;The last part&lt;/a&gt; was about setting up the &lt;a href="https://kubernetes.io"&gt;Kubernetes&lt;/a&gt; Cluster and deploying the &lt;a href="https://nodejs.org"&gt;NodeJS&lt;/a&gt; app. Today I want to talk about the last exercise and my conclusion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Learning about Auto Scaling
&lt;/h2&gt;

&lt;p&gt;The last task dealt with the topic of resource limits and scaling strategies. I had to think about the factors I would use to determine the scaling strategy. I wasn’t ready to know about resource limits or scaling strategies within my Kubernetes course, so I fired up my search engine of choice &lt;a href="https://niklasmtj.de/blog/what-i-love-about-duckduckgo"&gt;DuckDuckGo&lt;/a&gt; and came across how resource limits and auto scaling works in Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resource limits
&lt;/h3&gt;

&lt;p&gt;Resources can be easily specified in the specification of a pod. To do this, one adds a resource object to the respective container, as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;niklasmtj/exercise-app:v1&lt;/span&gt;
      &lt;span class="s"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you can see that the container may use a maximum of 128 MB Ram as well as half of a CPU. &lt;code&gt;500m&lt;/code&gt; stands for 500 millicpu. You can read more about this in the Kubernetes documentation: &lt;a href="https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#meaning-of-cpu"&gt;Managing Resources for Containers&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto Scaling strategy
&lt;/h3&gt;

&lt;p&gt;As results to my search I found information about &lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/"&gt;Horizontal Pod Autoscaler&lt;/a&gt; (HPA). I never heard of these through my course, as they weren’t explained until the end of Part 3, and I only finished that a few days after these assignments. However, HPAs seemed perfect for me to use for this, as Kubernetes does the auto-scaling of the pods. Below is my decision on the utility of HPAs, as well as the metrics by which the HPA scales the pods.&lt;/p&gt;

&lt;p&gt;The Pods will be scaled horizontally via Kubernetes’ own Horizontal Pod Autoscaler (HPA). Checking the resources is a good indicator of how much an application has to handle at that moment. Because of that the auto scaling begins when the CPU usage of the Pod exceeds 50%. This will trigger Kubernetes’ HPA which then creates a new Pod to handle the traffic.&lt;br&gt;
There are also Vertical Pod Autoscaler (VPA) that will increase the given resources to scale the Pod vertically.&lt;br&gt;
In my opinion checking the &lt;em&gt;number of requests&lt;/em&gt; as a scaling strategy can be possible. &lt;strong&gt;BUT:&lt;/strong&gt; applications use resources very differently. A web application that only serves a simple static file or only text like the used NodeJS app that prints Hello World to the page can handle a lot more requests than a more complex one which has more side effects (e.g. getting data from an remote API). If a developer is really sure about the correlation of &lt;em&gt;number of requests&lt;/em&gt; and &lt;em&gt;resouce usage&lt;/em&gt; then it would be a good idea to use it as a scaling strategy.&lt;/p&gt;

&lt;p&gt;To create a HPA with those 50% cpu restriction, a minimum of 1 Pod and a maximum of 10 pods one can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl autoscale deployment exercise-app --namespace exercise-ns --cpu-percent=50 --min=1 --max=10 --dry-run=client -o yaml &amp;gt; manifests/hpa.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which will outputs the desired HPA definition to a &lt;code&gt;yaml&lt;/code&gt; file in the manifests directory.&lt;br&gt;
The file will then look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;autoscaling/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;creationTimestamp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;null&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
    &lt;span class="s"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
  &lt;span class="na"&gt;targetCPUUtilizationPercentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50&lt;/span&gt;
&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;currentReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;desiredReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Testing the scaling strategy
&lt;/h3&gt;

&lt;p&gt;To test the scaling effects one could use  &lt;a href="https://github.com/codesenberg/bombardier"&gt;Bombardier&lt;/a&gt;  which creates multiple HTTP connections to check the response of a service. The test can be done with a temporary Docker container which can be run with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --ti --rm alpine/bombardier -c 500 -d 120s -l http://192.168.178.41:8081
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;http://192.168.178.41:8081&lt;/code&gt; is in this example my local IP address where the app is accessible. The flags &lt;code&gt;-c 500 -d 120s&lt;/code&gt; tells Bombardier to create 500 concurrent connections for 2 minutes.&lt;br&gt;
To create a Kubernetes Pod which will be deleted after its’ job one can use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl run bombardier --image=alpine/bombardier --rm -it --restart=Never --command -- bombardier -c 500 -d 120s -l http://exercise-svc.exercise-ns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will do the same but calls the &lt;code&gt;exercise&lt;/code&gt; service in it’s &lt;code&gt;exercise-ns&lt;/code&gt; namespace.&lt;br&gt;
After a while starting Bombardier &lt;code&gt;kubectl get hpa -n exercise-ns&lt;/code&gt; will show:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME             REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
rendertron-hpa   Deployment/rendertron   230%/50%   1         10        5          6m36s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows that 5 Replicas of the Pod definition are running because the CPU is at 230%. The Horizontal Autoscaler works because it dynamically created 4 additional Pods to manage the incoming traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  My conclusion
&lt;/h2&gt;

&lt;p&gt;This was my experience and reflections on my first practical task on Kubernetes for a job. Despite the problems at the beginning, the tasks were fairly given and were quite solvable. Towards the end, it was really a lot of fun to solve the tasks and learn new parts of Kubernetes. Kubernetes takes so much of the configuration work out of your hands. Especially in the area of autoscaling, you as a developer or operations team have to worry much less about whether the upcoming traffic can be handled by the systems.&lt;br&gt;
I am very glad to have started with the DevOps with Kubernetes course shortly before. It made me much more confident in setting up the cluster and at least I made quick progress there. Especially after the initial complications, it gave me more confidence. I definitely want to get further into Kubernetes and learn more in the DevOps area. This area still seems to offer so much opportunity to make developers and operations teams’ jobs easier.  &lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;




&lt;p&gt;The code from this post can also be found on GitHub: &lt;a href="https://github.com/niklasmtj/kubernetes-exercise"&gt;niklasmtj/kubernetes-exercise&lt;/a&gt;.&lt;br&gt;
Additionally, I created an &lt;code&gt;arm64&lt;/code&gt; as well as an &lt;code&gt;amd64&lt;/code&gt; docker image for &lt;code&gt;niklasmtj/exercise-app:v1&lt;/code&gt;. So the example app should be usable on other devices as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-1"&gt;Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-2"&gt;Part 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 3&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>career</category>
    </item>
    <item>
      <title>My first DevOps job interview Part 2 of 3</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Thu, 22 Apr 2021 07:09:15 +0000</pubDate>
      <link>https://dev.to/niklasmtj/my-first-devops-job-interview-part-2-of-3-3i0m</link>
      <guid>https://dev.to/niklasmtj/my-first-devops-job-interview-part-2-of-3-3i0m</guid>
      <description>&lt;p&gt;In &lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-1"&gt;the last part&lt;/a&gt; I introduced in the exercises and talked about the complications I had with building a Dockerimage with Chrome inside on an &lt;code&gt;arm64&lt;/code&gt; platform. Also the used &lt;a href="https://nodejs.org"&gt;NodeJS&lt;/a&gt; app and its’ &lt;code&gt;Dockerfile&lt;/code&gt; was presented. This part will be more about &lt;a href="https://kubernetes.io"&gt;Kubernetes&lt;/a&gt;, setting up the Cluster and deploying the NodeJS app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up a Kubernetes Cluster
&lt;/h2&gt;

&lt;p&gt;After creating the &lt;code&gt;Dockerfile&lt;/code&gt; it was about setting up a Kubernetes cluster. Which program I use for this was up to me. In my &lt;a href="https://devopswithkubernetes.com"&gt;DevOps with Kubernetes&lt;/a&gt; course we use &lt;a href="https://k3d.io"&gt;k3d&lt;/a&gt;, this implements &lt;a href="https://k3s.io"&gt;K3s&lt;/a&gt; in Docker. During the course I had not experienced any problems with this solution, so I was sure to use k3d to solve the assignments.&lt;br&gt;
Installing k3d on macOS is easy via &lt;a href="https://brew.sh"&gt;Homebrew&lt;/a&gt; with &lt;code&gt;brew install k3d&lt;/code&gt;. The cluster can then be created with &lt;code&gt;k3d cluster create --port '8082:30080@agent[0]' -p 8081:80@loadbalancer --agents 2&lt;/code&gt;. The &lt;code&gt;8081:80@loadbalancer&lt;/code&gt; will allow our apps to be accessible to us via &lt;code&gt;localhost:8081&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploying the app
&lt;/h2&gt;

&lt;p&gt;With a running Kubernetes cluster, deployment was now on the agenda. I decided from the beginning to logically separate the app from the rest of the cluster and created a separate namespace. This is possible with &lt;code&gt;kubectl create namespace exercise-ns&lt;/code&gt; in Kubernetes. The basic structure for deployment can be found in the Kubernetes documentation (&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#creating-a-deployment"&gt;Deployments | Kubernetes&lt;/a&gt;). So I created a &lt;code&gt;deployment.yaml&lt;/code&gt; in my &lt;code&gt;manifests&lt;/code&gt; folder with the following content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
    &lt;span class="s"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-ns&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;niklasmtj/exercise-app:v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To make the deployment discoverable I also created a service. The Kubernetes documentation for services (&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/#creating-a-deployment"&gt;Deployments | Kubernetes&lt;/a&gt;) again provides the basic structure. The service definition can be found under &lt;code&gt;manifests/service.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-svc&lt;/span&gt;
    &lt;span class="s"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-app&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
      &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is important to note here that the &lt;code&gt;targetPort&lt;/code&gt; corresponds to the port exposed by the Docker container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ingress
&lt;/h2&gt;

&lt;p&gt;After preparing the deployment, the task was now to be able to access it. Thus, the connection via an &lt;a href="https://kubernetes.io/docs/concepts/services-networking/ingress/"&gt;Ingress&lt;/a&gt; came into play. By default, K3s uses the Traefik Ingress Controller for Ingress routing, which I also used. The ingress configuration is quite simple. In an &lt;code&gt;ingress.yaml&lt;/code&gt; file I used the following configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;extensions/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-ingress&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-ns&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
        &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;exercise-svc&lt;/span&gt;
          &lt;span class="na"&gt;servicePort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This way the &lt;code&gt;/&lt;/code&gt; path is directly forwarded to the &lt;code&gt;exercise&lt;/code&gt; service we defined in &lt;code&gt;service.yaml&lt;/code&gt; on port 80.&lt;/p&gt;

&lt;h3&gt;
  
  
  IP-Whitelisting
&lt;/h3&gt;

&lt;p&gt;Another task I had to solve with an Ingress was to limit the IP range that is accepted at all. Unfortunately, due to my previous knowledge, I did not had the possibility to implement the whole thing at that time. Nevertheless I read up on how I would implement it and documented it in my readme. During the task I took a closer look at the NGINX Ingress Controller, because I found the most results about it during my research. There it seems to work by a simple annotation in the &lt;code&gt;ingress.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;ingress.kubernetes.io/whitelist-source-range&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;192.168.0.0/16"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will make sure that only IPs from the &lt;code&gt;192.168.0.0/16&lt;/code&gt; net is able to connect to the apps. So every IP starting at &lt;code&gt;192.168.0.1&lt;/code&gt; to &lt;code&gt;196.168.255.254&lt;/code&gt; are eligible to connect. NGINX will drop every request not coming from this IP range.&lt;/p&gt;

&lt;p&gt;Also, attempts with Traefik as the Ingress controller, which is used by k3s by default, failed. This is an area I need to look at in more detail in the near future to understand exactly how Ingress works and at what level you can block IPs. Additionally, I need to look at whether it makes a difference that my k3s cluster is running inside Docker and how that affects incoming IPs.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;




&lt;p&gt;The code from this post can also be found on GitHub: &lt;a href="https://github.com/niklasmtj/kubernetes-exercise"&gt;niklasmtj/kubernetes-exercise&lt;/a&gt;.&lt;br&gt;
Additionally, I created an &lt;code&gt;arm64&lt;/code&gt; as well as an &lt;code&gt;amd64&lt;/code&gt; docker image for &lt;code&gt;niklasmtj/exercise-app:v1&lt;/code&gt;. So the example app should be usable on other devices as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://niklasmtj.de/blog/my-first-devops-job-interview-part-1"&gt;Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Part 2&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Part 3 - coming April 29&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>career</category>
      <category>docker</category>
    </item>
    <item>
      <title>My first DevOps job interview Part 1 of 3</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Thu, 15 Apr 2021 13:26:16 +0000</pubDate>
      <link>https://dev.to/niklasmtj/my-first-devops-job-interview-part-1-of-3-2299</link>
      <guid>https://dev.to/niklasmtj/my-first-devops-job-interview-part-1-of-3-2299</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the middle of March I had my first interview for a DevOps Engineer job for the time after my studies. During the interview process I was given a task to demonstrate my Kubernetes skills. Basically, the task was to create a Docker image for a NodeJS app and then deploy it to a Kubernetes cluster. I had about 8 hours to complete the task. Below, I will go into detail about each of the tasks I was given. However, I will not formulate the task 1:1, but paraphrase what should be done in the task. Nevertheless, with this article I will try to give a feeling for how a practical task is structured in a DevOps job interview.&lt;/p&gt;

&lt;p&gt;For information: I used a MacBook Pro with M1 chip for the following tasks. Why I mention this will become clear in a moment.&lt;br&gt;
The NodeJS app I was supposed to roll out was built on &lt;a href="https://github.com/puppeteer/puppeteer/"&gt;puppeteer&lt;/a&gt;, which is a library for using a headless Chrome or Chromium browser. To create the Dockerfile I followed the documentation of &lt;a href="https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md#running-puppeteer-in-docker"&gt;puppeteer&lt;/a&gt;. This shows that Chrome can be downloaded directly via &lt;code&gt;apt&lt;/code&gt; package management using the URL &lt;code&gt;http://dl.google.com/linux/chrome/deb/&lt;/code&gt;. After I found out that Chrome does not currently provide an &lt;code&gt;arm64&lt;/code&gt; build, I used an &lt;code&gt;amd64&lt;/code&gt; Docker image of Node. However, this did not lead to the desired goal either. Finally, I downloaded and installed Chrome directly via &lt;code&gt;wget&lt;/code&gt;. This led to a successfully built container image, but shortly after starting the container it threw Chrome errors and was not usable. After 2 1/2 hours I contacted the company to see if it was okay to use a simple NodeJS app for the remaining tasks, so I could at least solve the remaining tasks (there were still 5 tasks ahead of me).&lt;/p&gt;

&lt;p&gt;At the end of the 3 articles is a link to the GitHub repository with all the files that can be used to deploy the following app in Kubernetes.&lt;/p&gt;

&lt;p&gt;The rather unspectacular NodeJS app looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Constants&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// App&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/health&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Running on http://&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Dockerfile
&lt;/h2&gt;

&lt;p&gt;There was still a requirement to create a &lt;code&gt;Dockerfile&lt;/code&gt; for the NodeJS. The following &lt;code&gt;Dockerfile&lt;/code&gt; is not 1:1 the same one I used during the task. I extended it by some best practices afterwards. It is important to make sure that the user running the program in the container is not &lt;code&gt;root&lt;/code&gt;. For this the &lt;code&gt;node&lt;/code&gt; images provide the &lt;code&gt;node&lt;/code&gt; user. This can be set up by adding the line &lt;code&gt;USER node&lt;/code&gt; just before the app is launched.&lt;br&gt;
Another security for the container, but also for the user of the images, is to set a specific &lt;code&gt;node&lt;/code&gt; image. This way you can ensure at any time that the dependencies you find are the ones you tested with.&lt;br&gt;
This is done by using the &lt;code&gt;SHA&lt;/code&gt; code of the respective image e.g. &lt;code&gt;FROM node:14@sha256:00e90d6cbb499653cd2c74a3770f4fa5982699145b113e422bdffe31a7905117&lt;/code&gt; for an &lt;code&gt;arm64&lt;/code&gt; build of node in version 14.&lt;br&gt;
The same is true for the &lt;code&gt;apt-get update&lt;/code&gt; command before installing new dependencies in a container. Since the sources can change, it is otherwise not possible to ensure that there are no problems when building new containers. Thus, it is better to test directly with a new build of the base image. More best practices for Docker images can be found at &lt;a href="https://cloudberry.engineering/article/dockerfile-security-best-practices/"&gt;Docker Security Best Practices from the Dockerfile&lt;/a&gt; or &lt;a href="https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html"&gt;Docker Security - OWASP Cheat Sheet Series&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next part will be about setting up a Kubernetes Cluster and deploying the NodeJS app in the cluster.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;




&lt;p&gt;The code from this post can also be found on GitHub: &lt;a href="https://github.com/niklasmtj/kubernetes-exercise"&gt;niklasmtj/kubernetes-exercise&lt;/a&gt;.&lt;br&gt;
Additionally, I created an &lt;code&gt;arm64&lt;/code&gt; as well as an &lt;code&gt;amd64&lt;/code&gt; docker image for &lt;code&gt;niklasmtj/exercise-app:v1&lt;/code&gt;. So the example app should be usable on other devices as well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The series:&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Part 1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Part 2 - coming April 22&lt;/li&gt;
&lt;li&gt;Part 3 - coming April 29&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>career</category>
      <category>docker</category>
    </item>
    <item>
      <title>My DevOps learning path</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Fri, 26 Feb 2021 12:37:34 +0000</pubDate>
      <link>https://dev.to/niklasmtj/my-devops-learning-path-35aa</link>
      <guid>https://dev.to/niklasmtj/my-devops-learning-path-35aa</guid>
      <description>&lt;p&gt;My goal for the time after my university is to find a job in the field of DevOps or Site Reliability Engineering. The Phoenix Project&lt;sup id="fnref1"&gt;1&lt;/sup&gt; and the DevOps Handbook&lt;sup id="fnref2"&gt;2&lt;/sup&gt; books by Gene Kim where both great books to get a glimpse about what matters in DevOps. Especially The Phoenix Project was great since it is written as a fictional story not as a "real" technical non-fiction book but still delivers in explaining the Three Ways&lt;sup id="fnref3"&gt;3&lt;/sup&gt; which are the three principles of DevOps perfectly.&lt;/p&gt;

&lt;p&gt;Although I have been increasingly involved in software development in recent years, I am drawn to the field of DevOps. Perhaps this has something to do with my background as a system integrator. I am fascinated by the complexity of designing systems reliably and rolling them out as automatically as possible. The knowledge I have built up in JavaScript and NodeJS could help in this process.&lt;/p&gt;

&lt;p&gt;This is the knowledge and understanding of the topic that I have right now.&lt;br&gt;
I will try to write a post about the knowledge I’m gaining about these when I’m going forward in the process. &lt;/p&gt;

&lt;p&gt;Following are the steps I will take to learn those topics. &lt;/p&gt;

&lt;p&gt;First I started with getting knowledge about Kubernetes. It seems to grow to be the state of the art orchestration tool for systems and pretty much every job posting requires at least basic knowledge of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes LinkedIn course
&lt;/h2&gt;

&lt;p&gt;To get a quick introduction to the terms of Kubernetes I used my LinkedIn Learning membership that I can use as a student of the University of Cologne. This course by Karthik Gaekwad gives an overview of the terms and techniques of Kubernetes.&lt;br&gt;
Kubernetes (K8s) is used to orchestrate containers on one’s infrastructure. Orchestration in that case means deploying applications to infrastructure and managing the application’s state, replication status. K8s also has integrated health checks for Pods (the smallest possible entity, one Pod holds one or multiple containers). So when a Pod crashes Kubernetes will make sure that the crashed Pod will be restarted.&lt;br&gt;
In some cases, it lacks a little bit of explanation of terms that are used in the current video. Anyways, overall it was a great video course to get into K8s.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/learning/learning-kubernetes/"&gt;Learning Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  DevOps with Kubernetes course by the University of Helsinki
&lt;/h2&gt;

&lt;p&gt;This free course is developed by Jami Kousa who works at the University of Helsinki. The course has been recommended by a friend who used it to get the knowledge for his Certified Kubernetes Application Developer (CKAD) certificate. It uses &lt;a href="https://k3d.io"&gt;k3d&lt;/a&gt; for setting up the local Cluster. The Google Cloud Platform also is introduced in the course especially the Google Kubernetes Engine which is used in one part of the course.&lt;br&gt;
Kubernetes (K8s) is used to orchestrate containers. &lt;br&gt;
The course introduces one to Kubernetes and shows one how networking and storage works. After that in Part 2 one will work with &lt;em&gt;Clusters&lt;/em&gt;, &lt;em&gt;StatefulSets&lt;/em&gt; and &lt;em&gt;Jobs&lt;/em&gt;. Plus there is a chapter about monitoring the cluster. Part 3 looks to be about the Google Kubernetes Engine and building a Deployment Pipeline. Part 4 is newly added and will introduce GitOps. What it is and why it is important will be described in the next chapter. The last part of the course is about the internals of Kubernetes, &lt;em&gt;Custom Resource definitions&lt;/em&gt; and &lt;em&gt;Service Meshes&lt;/em&gt;.&lt;br&gt;
I think that this course will be a great introduction to Kubernetes and a good way to get the hands dirty.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources:
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://devopswithkubernetes.com/"&gt;DevOps with Kubernetes 2020&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Gitops
&lt;/h2&gt;

&lt;p&gt;GitOps is a topic that is newly added as a part of the DevOps with Kubernetes course. So I think this is a good opportunity to get more into the actual implementation and usage of Kubernetes „in the real world“.&lt;br&gt;&lt;br&gt;
When working with Kubernetes Clusters GitOps is a way to manage the clusters and automate the application delivery via git. Git in this case is the single source of truth. This means that the desired production environment is defined in a git repository which will be used to identify any drifts off of the running infrastructure. Using those container definitions helps to automate the continuous delivery of one’s applications.&lt;br&gt;
Because the definitions are managed in a git repository it is easy to see the changes of an application definition. Plus it is way easier to rollback to a working definition of a system when problems happen, since the prior version can be found inside of the git history.&lt;br&gt;
The &lt;a href="https://www.weave.works/technologies/gitops/"&gt;Guide To GitOps&lt;/a&gt; by Weaveworks seems to be a great source of knowledge about GitOps. I heard about the company and their knowledge base in a recent Software Engineering Radio podcast with Alexis Richardson the founder and CEO of Weaveworks &lt;sup id="fnref4"&gt;4&lt;/sup&gt;.&lt;br&gt;
I will use the two sources below and the course section as my starting point for learning about GitOps. &lt;/p&gt;

&lt;h3&gt;
  
  
  Resources:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gitops.tech"&gt;GitOps.tech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.weave.works/technologies/gitops/"&gt;Guide To GitOps&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ansible / Terraform
&lt;/h2&gt;

&lt;p&gt;Ansible and Terraform or Infrastructure as Code tools in general are important things to know in the world of automating deployments.&lt;br&gt;
Both applications seem to be fundamental tools in building automated infrastructure. Also in the world of GitOps, they look to have an important role since both of those tools help declaring the infrastructure.&lt;br&gt;
Ansible is a tool that is mostly used to provision a server. This means that the definition contains the applications and configs that have to be present on the machine. It was acquired by Red Hat in 2015. Ansible uses so-called playbooks that are written in the YAML format. It is also possible to do Ad-hoc task executions for example to ping all the available hosts.&lt;br&gt;
Terraform uses its’ own configuration language. It is called HashiCorp Configuration Language or HCL for short. Terraform is mostly used to configure cloud resources and helps for example to define the availability zones or instance types of a resource. It can also help to organize infrastructure across multiple clouds.&lt;/p&gt;

&lt;h3&gt;
  
  
  Resources:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.ansible.com"&gt;Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io"&gt;Terraform by HashiCorp&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So these are the topics I will learn in my semester break and possibly in the months after that. Going through these will probably bring up a lot more topics that will follow for example command-line tools like &lt;code&gt;jq&lt;/code&gt; or &lt;code&gt;yq&lt;/code&gt; and &lt;code&gt;awk&lt;/code&gt;. I will keep you informed about the current state.&lt;/p&gt;

&lt;p&gt;Thank you for reading, &lt;br&gt;
Niklas&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;a href="https://itrevolution.com/book/the-phoenix-project/"&gt;https://itrevolution.com/book/the-phoenix-project/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;&lt;a href="https://itrevolution.com/the-devops-handbook/"&gt;https://itrevolution.com/the-devops-handbook/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;&lt;a href="https://itrevolution.com/the-three-ways-principles-underpinning-devops/"&gt;https://itrevolution.com/the-three-ways-principles-underpinning-devops/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn4"&gt;
&lt;p&gt;&lt;a href="https://www.se-radio.net/2020/12/episode-440-alexis-richardson-on-gitops/"&gt;https://www.se-radio.net/2020/12/episode-440-alexis-richardson-on-gitops/&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>devops</category>
      <category>learninginpublic</category>
      <category>sre</category>
    </item>
    <item>
      <title>Simple Analytics iOS widget with Scriptable app</title>
      <dc:creator>Niklas</dc:creator>
      <pubDate>Mon, 22 Feb 2021 19:37:27 +0000</pubDate>
      <link>https://dev.to/niklasmtj/simple-analytics-ios-widget-with-scriptable-app-35mf</link>
      <guid>https://dev.to/niklasmtj/simple-analytics-ios-widget-with-scriptable-app-35mf</guid>
      <description>&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--IyCZBZN6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f8tnbxjhomqj2couyn5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--IyCZBZN6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6f8tnbxjhomqj2couyn5.jpg" alt="example widget picture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Today I quickly build an iOS widget to see my page views and visitors on my iPhone. Since I don't always want to start my laptop to see my current website stats in Simple Analytics I created this widget based on an example widget I found in the &lt;a href="https://scriptable.app"&gt;Scriptable&lt;/a&gt; app. The great thing about Scriptable is that uses the &lt;a href="https://developer.apple.com/documentation/javascriptcore"&gt;JavaScript Core&lt;/a&gt; of iOS. This means that a lot of features of ES6 are available to use. You can find a documentation in the Scriptable app or at &lt;a href="https://docs.scriptable.app"&gt;docs.scriptable.app&lt;/a&gt;. &lt;br&gt;
Since it would be very tedious to write code on the small touchscreen, I wrote the code on my laptop and then pasted it on my phone. &lt;/p&gt;

&lt;p&gt;Without further ado here are the installation steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to install
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Load the scriptable app for your device&lt;/li&gt;
&lt;li&gt;Copy the content of the &lt;a href="https://github.com/niklasmtj/simple-analytics-widget/blob/main/getSimpleAnalyticsStats.js"&gt;getSimpleAnalyticsStats.js&lt;/a&gt; file on GitHub and fill in the first 3 lines of the file with your apiKey, userId and the domain you want to track. You can get your keys in your account settings &lt;a href="https://simpleanalytics.com/account"&gt;https://simpleanalytics.com/account&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Try to run the script and see if everything works.&lt;/li&gt;
&lt;li&gt;Go back to your home screen and trigger the "jiggle-mode"&lt;/li&gt;
&lt;li&gt;Now add scriptable and choose your newly created script as the script to run&lt;/li&gt;
&lt;li&gt;Have fun with your Simple Analytics iOS widget&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The GitHub repository can be found &lt;a href="https://github.com/niklasmtj/simple-analytics-widget/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading,&lt;br&gt;
Niklas&lt;/p&gt;




&lt;p&gt;This post and more you can find on &lt;a href="https://niklasmtj.de"&gt;niklasmtj.de&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>javascript</category>
      <category>widgets</category>
    </item>
  </channel>
</rss>
