I am in my fourth attempt of learning Go

I am not particularly fond of Go, but I work with Kubernetes, and Go is to Kubernetes what C is to Unix. So after a point, you have to know some Go in order to understand more of Kubernetes’s implementation, design and other quirks (and why not, implement something too).

My first try was with The Go Programming Language. Up in the mountains, no Internet, just me, the book and my laptop. It felt like when reading K&R, only it didn’t. Times have changed. This is not the way. The Book is OK to be by your side, but I need something else.

The second time was when I was asked to write a review of Go Systems Programming by Mihalis Tsoukalos. I read the book through and through and submitted corrections on errors and such and my opinion of it to the author and the publisher. But life happened and I paused from immediate need for Go and Kubernetes. I forgot almost all of Go that I learned through the process.

I then tried the Exercism track for Go. I’ve tried many languages on Exercism and I consider it a valuable tool for everyone. It is just not for me. That effort faded quickly.

I am now in my fourth attempt. Mind you, I am not trying to become super proficient in Go, or even idiomatic Go. I want to have the relative ease to understand code that I read and to be able to write 100 lines of Go that work. This time I’ve chosen Go by Example. I’m following this tutorial one example per day. I had a small hiatus during the holidays, but today I came back. This looks like it may work.

That’s why I am writing the post. It is a sort of public commitment. Like Stickk without Stickk.

RUN –mount=type=ssh is not always easy

Let’s take a very barebones Jenkinsfile and use it to build a docker image that clones something from GitHub (and possibly does other stuff next):

pipeline {
  agent any

  environment {

  stages {
    stage('200ok') {
      steps {
        sshagent(["readonly-ssh-key-here"]) {
          script {
            sh 'docker build --ssh default -t adamo/200ok .'

We are using the SSH Agent Plugin in order to allow a clone that happens in the Dockerfile:

# syntax=docker/dockerfile:experimental
FROM bitnami/git
RUN mkdir /root/.ssh && ssh-keyscan github.com >> /root/.ssh/known_hosts
RUN --mount=type=ssh git clone git@github.com:a-yiorgos/200ok.git

This builds fine. But what if you need this to be some "rootless" container?

# syntax=docker/dockerfile:experimental
FROM bitnami/git
USER bitnami
WORKDIR /home/bitnami
RUN mkdir /home/bitnami/.ssh && ssh-keyscan github.com >> /home/bitnami/.ssh/known_hosts
RUN --mount=type=ssh git clone git@github.com:a-yiorgos/200ok.git

This will fail with something like:

#14 [7/7] RUN --mount=type=ssh git clone git@github.com:a-yiorgos/200ok.git
#14       digest: sha256:fb15ac6ca5703d056c7f9bf7dd61bf7ff70b32dea87acbb011e91152b4c78ad4
#14         name: "[7/7] RUN --mount=type=ssh git clone git@github.com:a-yiorgos/200ok.git"
#14      started: 2021-12-17 12:00:22.859388318 +0000 UTC
#14 0.572 fatal: destination path '200ok' already exists and is not an empty directory.
#14    completed: 2021-12-17 12:00:23.508950696 +0000 UTC
#14     duration: 649.562378ms
#14        error: "executor failed running [/bin/sh -c git clone git@github.com:a-yiorgos/200ok.git]: exit code: 128"

rpc error: code = Unknown desc = executor failed running [/bin/sh -c git clone git@github.com:a-yiorgos/200ok.git]: exit code: 128

Why is that? Is not the SSH agent forwarding working? Well, kind of. Let’s add a couple of commands in the Dockerfile to see what might be the issue:

# syntax=docker/dockerfile:experimental
FROM bitnami/git
USER bitnami
WORKDIR /home/bitnami
RUN mkdir /home/bitnami/.ssh && ssh-keyscan github.com >> /home/bitnami/.ssh/known_hosts
RUN --mount=type=ssh env
RUN --mount=type=ssh ls -l ${SSH_AUTH_SOCK}
RUN --mount=type=ssh git clone git@github.com:a-yiorgos/200ok.git

Then the build output gives us:

#13 [6/7] RUN --mount=type=ssh ls -l ${SSH_AUTH_SOCK}
#13       digest: sha256:ce8fcd7187eb813c16d84c13f8d318d21ac90945415b647aef9c753d0112a8a7
#13         name: "[6/7] RUN --mount=type=ssh ls -l ${SSH_AUTH_SOCK}"
#13      started: 2021-12-17 12:00:22.460172872 +0000 UTC
#13 0.320 srw------- 1 root root 0 Dec 17 12:00 /run/buildkit/ssh_agent.0
#13    completed: 2021-12-17 12:00:22.856049431 +0000 UTC
#13     duration: 395.876559ms

and subsequently fails to clone. This happens because the socket file /run/buildkit/ssh_agent.0 for the SSH agent forwarding is not accessible by user bitnami and thus no ssh identity is available to it.

I do not know whether it is possible to make use of RUN --mount=type=ssh in combination with USER where the user is not root. Please leave a comment if you know whether/how this can be accomplished.

On drawing lines and keeping notes

This post is sparked from a discussion I had with friends around “What do you use to keep notes?”.

Well, the tl;dr answer to this for me is Evernote. I got an Evernote subscription when I bought a LiveScribe. The LiveScribe did not stick, but Evernote sure did. Anything that I may use in the future (99% chance I won’t, but I keep in a bucket for just in case) goes there. That is mostly papers and blog posts that I find interesting at the time I happen across.

But a longer answer includes more than Evernote. I keep hand written notes in a variety of media:
– I use a Wacom One because when I do on-line teaching, I need to draw figures and share them. Originally I used Jamboard and Whiteboard. Now I am simply using jspaint.app.
– I have a Mi Writing LCD tablet. I use this for either very temporary notes and doodles, or when late at night in the sofa watching TV and need to dump an idea that just sparked. Next morning it will either find its way to Evernote or get deleted because it was not as cool as I initially thought it was.
– I was gifted a Rocketbook. I use it for work. Whatever notes I keep during the day to keep track on my work. Like that ticket I was working yesterday and it still needs something, other notes that you need to not forget while working on something stuff like that. If anything needs permanence, then the Rocketbook app makes sure it gets saved.
– Since I follow Conway’s recipe, I have more than one interesting projects open. Some of them personal. And I need to keep notes and write them on paper. For that I use a Moleskine Smart Writing System (again a gift). Please do make fun of me for using Moleskines.
– Other Moleskines because I like writing with a fountain pen.
– When I don’t have any of the above handy, I fold a PocketMod just to dump whatever on my head on paper.

Do you need all that? Does this sound overwhelming and unnecessary? Most likely yes. If you feel that is the case, you need only a few of things:
– A book about making better sketches.
– Any pen and paper you can write on.
– Your phone’s camera and a scanning application to capture your thoughts.

Do I have that many great ideas? Most likely not. But I like to write them down in order to evaluate them.

The case of an etcd restore that was not happening

When you provision a kubernetes cluster with kubeadm etcd is a static pod and its configuration file etcd.yaml is located in the /etc/kubernetes/manifests/ directory.

Assuming a non-production installation, I happened across a case where a backup of etcd was taken, a deployment was deleted and then the backup was restored. Naturally the expected result after editing etcd.yaml so that data-dir pointed to the restored database, was for the previously deleted deployment to reappear. It did not! Six restores in a row did not result in bringing it back. Let’s see the steps taken in a test cluster created to replicate what happened:

First, two deployments were created:

$ kubectl create deployment nginx --image nginx
deployment.apps/nginx created

$ kubectl create deployment httpd --image httpd
deployment.apps/httpd created

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
httpd-757fb56c8d-vhftq   1/1     Running   0          4s
nginx-6799fc88d8-xklhw   1/1     Running   0          11s

Next, a snapshot of the etcd was requested:

$ kubectl -n kube-system exec -it etcd-ip-10-168-1-35 -- sh -c "ETCDCTL_API=3 \
ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt \
ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key etcdctl --endpoints= \
snapshot save /var/lib/etcd/snapshot.db "
Snapshot saved at /var/lib/etcd/snapshot.db

Oh my god, we deleted an important deployment!

$ kubectl delete deployment nginx 
deployment.apps "nginx" deleted

$ kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
httpd-757fb56c8d-vhftq   1/1     Running       0          53s
nginx-6799fc88d8-xklhw   0/1     Terminating   0          60s

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
httpd-757fb56c8d-vhftq   1/1     Running   0          114s

Quick! Bring it back. First let’s restore the snapshot we have, shall we?

$ kubectl -n kube-system exec -it etcd-ip-10-168-1-35 -- sh -c "ETCDCTL_API=3 \
ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt \
ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key etcdctl --endpoints= \
snapshot restore --data-dir=/var/lib/etcd/restore /var/lib/etcd/snapshot.db "
{"level":"info","ts":1637178021.3886964,"caller":"snapshot/v3_snapshot.go:309","msg":"restored snapshot","path":"/var/lib/etcd/snapshot.db","wal-dir":"/var/lib/etcd/restore/member/wal","data-dir":"/var/lib/etcd/restore","snap-dir":"/var/lib/etcd/restore/member/snap"}

And now just edit /etc/kubernetes/manifests/etcd.yaml so that it points to the restored directory:

- --data-dir=/var/lib/etcd/restore

And after kubelet does its thing for a minute or two, it should work, right? No:

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
httpd-757fb56c8d-vhftq   1/1     Running   0          11m

This was the situation I was pointed at and asked to offer an opinion.

Could there be an issue with etcd?

journalctl -u kubelet | grep etcd reveals nothing.

kubectl -n kube-system logs etcd-ip-10-168-1-35 does not reveal anything:

2021-11-17 19:50:24.208303 I | etcdserver/api/etcdhttp: /health OK (status code 200)
2021-11-17 19:50:34.208063 I | etcdserver/api/etcdhttp: /health OK (status code 200)

But look at this:

$ kubectl -n kube-system logs etcd-ip-10-168-1-35 | grep restore
2021-11-17 19:48:34.261932 W | etcdmain: found invalid file/dir restore under data dir /var/lib/etcd (Ignore this if you are upgrading etcd)
2021-11-17 19:48:34.293681 I | mvcc: restore compact to 1121

So there must be something there that directs etcd to read from /var/lib/etcd and not from /var/lib/etcd/restore. What could it be?

# ls /etc/kubernetes/manifests/
etcd.yaml       httpd.yaml           kube-controller-manager.yaml
etcd.yaml.orig  kube-apiserver.yaml  kube-scheduler.yaml

The person who asked my opinion thoughtfully wanted to have a backup of the etcd.yaml file. Only it happened that keeping it in the same directory messed up the setup. Look what happens next:

$ sudo rm /etc/kubernetes/manifests/etcd.yaml.orig 

$ kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
httpd-757fb56c8d-vhftq   1/1     Running   0          16m
nginx-6799fc88d8-xklhw   1/1     Running   0          16m

Note that the nginx pod returned with the exact same name as before.

So the takeaway from this adventure is that kubelet reads all files in /etc/kubernetes/manifests not only the *.yaml files and thus do not keep older versions of files in there, for results will be unexpected.

What are you using your RPi for?

This is what a friend whom I’d not seen for some time asked me yesterday. Well these days not much.

In the past I’ve used it for running OpenELEC, but these days a FireTV and whatever Android box the ISP gives you do the job fine.

I’m currently using it for two things:

Next project, with no actual ETA because I’m interested in other stuff more: run these in k3s.

I’m sure other people have more exciting stuff to do with it, but what little time I have for spare computing right now, I’m using it on Groovy and J.

Using systemd-nspawn to run an older Ubuntu version and Docker images

Work for this post was partly triggered by something I saw at work and partly because of a lightning talk I saw on Monday. There was a remark by a friend that even though we love and want to run latest and greatest supported versions, we cannot always upgrade in time. And as such, sometimes we end-up with unsupported OS versions for a significant time. Not unlike the law of welded systems.

But it got me thinking. Assuming, for example, that you have a number of Xenial systems that due to library dependencies you cannot upgrade, is there a middle ground that might be acceptable for a while until you push forward? Using a Xenial docker container and treating it as a lightweight VM somehow, could be a solution. But what if you need to run docker containers too? Maybe you need something more elaborate.

I fired up a VM running Ubuntu Focal and decided to figure out how to run a systemd-nspawn Xenial container.

Create the machine:

# debootstrap --arch=amd64 xenial /var/lib/machines/xenial1

Start the machine

# systemd-nspawn -D /var/lib/machines/xenial1
root@xenial1 # rm /etc/securetty
root@xenial1 # passwd root
root@xenial1 # apt-get update
root@xenial1 # apt-get install dbus resolvconf
root@xenial1 # systemctl enable systemd-resolved
root@xenial1 # cat > /etc/resolvconf/resolv.conf.d/base
options edns0 trust-ad
search home
root@xenial1 #

Yes, I know that it is best to fix securetty instead of removing it, but this is a PoC on my VM. You can now proceed to configure systemd to run the container:

# /etc/systemd/system/xenial1.service

Description=Xenial1 Container

ExecStart=/usr/bin/systemd-nspawn --machine=xenial1 --directory=/var/lib/machines/xenial1/ --bind /var/run/docker.sock:/var/run/docker.sock --bind /mnt2:/mnt2 -b 


In your host system you can now:

root@focal # systemctl daemon-reload
root@focal # systemctl start xenial1

You can of course log into the machine with machinectl login xenial1. Notice above that the nspawn container mounts the docker socket, since we assume that you have docker installed in the Focal host and that you want to "run" docker containers from within the Xenial machine.

We only need to install the docker client in the Xenial container and as such:

root@xenial1 # cat > /etc/apt/sources.list.d/docker.list
deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu xenial stable
root@xenial1 # apt-get update
root@xenial1 # apt-get install docker-ce-cli=5:18.09.7~3-0~ubuntu-xenial

Now you’re all set. The docker client inside Xenial, starts whatever container you want in Focal and does whatever you like.

Suppose that you want a non-root user like ubuntu in the Xenial machine to be able to run docker commands; what do you do? You add the /etc/group line for the docker group from Focal in Xenial and in Xenial you simply useradd -aG docker ubuntu.

ubuntu@xenial1 > docker run -d -p 8080:8080 bitnami/nginx
ubuntu@focal > curl
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.


/dev/null as a service

This is kind of an obvious trick, but you never know when you’re going to need a web server that just receives stuff and responds back that all is well, while discarding its input. If you’re in such a situation:

docker run -p 8080:8080 docker.elastic.co/logstash/logstash-oss:7.15.0 -e "input { http { } } output { sink {} }"

It puts the http input plugin and sink to good use.

Return a blank favicon.ico with Python bottle

Bottle is a fine framework when you need to quickly start a small Python application and serve web calls from it. However, you will soon notice that browser calls to your application ask for favicon.ico. Of course you can ignore those calls, but what if you want to serve them too?

Once choice is to use server_static() and return the icon as described here. But what if you want to return a blank icon, since this is a quick hack anyway and you do not want to spend time searching for whatever icon or not?

Starting from the description of a blank favicon, you can add this to your code:

def get_favicon():

    response.content_type = 'image/x-icon'


What if my Kubernetes cluster on AWS cannot pull from ECR?

When you run an RKE cluster on AWS, pulling images from an ECR is something to be expected. However, it is not the easiest of things to do that, since the credentials produced by aws ecr get-login expire every few hours and thus, you need something to refresh them. In Kubernetes world this means we should use a CronJob.

What we do in this post is a simple improvement on other work here and here. The biggest difference is using the Bitnami docker images for aws-cli and kubectl to achieve the same result.

So we need a CronJob that will schedule a pod to run every hour and refresh the credentials. This job will:

  • run in a specific namespace and refresh the credentials there
  • have the ability to delete and re-create the credentials
  • do its best not to leak them
  • use "well known" images outside the ECR in question

To this end our Pod needs an initContainer that is going to run aws ecr get-login and store it in an ephemeral space (emptyDir) for the main container to pick it up. The main container in turn, will pick up the password generated by the init Container and complete the credential refreshing.

Are we done yet? No, because by default the service account the Pod operates on, does not have the ability to delete and create credentials. So we need to create an appropriate Role and RoleBinding for this.

All of the above is reproduced in the below YAML. If you do not wish to hardcode the AWS region and ECR URL, you can of course make them environment variables.

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
  namespace: dns
  name: ecr-secret-role
- apiGroups: [""]
  - secrets
  - serviceaccounts
  - serviceaccounts/token
  - 'create'
  - 'delete'
  - 'get'
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
  namespace: dns
  name: ecr-secret-rolebinding
- kind: ServiceAccount
  name: default
  namespace: dns
  kind: Role
  name: ecr-secret-role
  apiGroup: ""
apiVersion: batch/v1beta1
kind: CronJob
  name: ecr-update-login
  namespace: dns
  schedule: "38 */1 * * *"
          restartPolicy: Never
          - name: awscli
            image: bitnami/aws-cli
            - /bin/bash
            - -c
            - |-
              aws ecr get-login --region REGION_HERE | cut -d' ' -f 6 > /ecr/ecr.token
            - mountPath: /ecr
              name: ecr-volume
          - name: kubectl
            image: bitnami/kubectl
            - /bin/bash
            - -c
            - |-
              kubectl -n dns delete secret --ignore-not-found ecr-registry
              kubectl -n dns create secret docker-registry ecr-registry --docker-username=AWS --docker-password=$(cat /ecr/ecr.token) --docker-server=ECR_URL_HERE
            - mountPath: /ecr
              name: ecr-volume
          - name: ecr-volume
            emptyDir: {}

You can now use the ECR secret to pull images by adding to your Pod spec:

  - name: ecr-registry

And yes, it is possible to solve the same issue with instance profiles:


This is a solution when for whatever reason, for when you cannot. Plus it may give you ideas for other helpers using the aws-cli and kubectl. Or for other clouds and registries even.

Why I like Groovy

I am writing this following a discussion with a colleague, where he pointed out his dislike of the Jenkins pipeline language and I commented that I liked Groovy. He sounded astonished, so this gives me a chance to elaborate a bit on that:

I run systems and I am not a Software Engineer, but I do write glue code all the time. The past few years I’ve come across a number of Jenkins installations, and Groovy is a bit of a required asset for more complex Jenkins stuff. That’s how I got my working, trial and error, knowledge of Groovy.

I happen to like functional languages. But since I am not a SWE and since in my location no FP jobs existed in the market, I am paid to do stuff people know I do well, not stuff I want to play with. To this end, Groovy is the closest thing to FP I can get paid working with. And it is a trick in life to find what is play that people think is work that want to pay you for.

On the same track, Groovy runs on the JVM. Why not Clojure you say? Because I can get paid working with Groovy, I can only be considered a junior engineer seeking Clojure work. And time is a valuable and constrained resource. I do not have infinite free time to learn Clojure. I did happen to be allowed to learn Groovy to save the day on a system.

Even though I first worked with Java when it run on SPARC Solaris 2.3 machines, I decided to not invest my time in the language, foreseeing that such an investment of time, will make me a monoglot, and I sure enjoy more my ability to switch languages, even for glue code and/or projects. But, again it happens that the JVM is one of the most engineered pieces of software of the past 25 years and almost everywhere you will find it running. So when you run systems you need to understand it somehow. And you will be required some times to write code that runs on it.

Hence my answer: Groovy and the book next to me on the desk.

It is not my first choice, but it seems to be the optimal for me. Today Groovy is number 12 on the TIOBE index and Go (my next similar choice because of Kubernetes) 14.