How To Develop Kubernetes CLIs Like a Pro

A short one today. Just wanted you to meet my new favorite Go library to work with Kubernetes - k8s.io/cli-runtime. It's probably the most underappreciated package in the whole k8s.io/* family based on its value to the number of stars ratio.

Here is what the README file says about it:

Set of helpers for creating kubectl commands, as well as kubectl plugins.

This library is a shared dependency for clients to work with Kubernetes
API infrastructure which allows to maintain kubectl compatible behavior.

If the above description didn't sound too impressive, let me try to decipher it for you - with the cli-runtime library, you can write CLI tools that behave like and are as potent as the mighty kubectl!

Here is what you actually can achieve with just a few lines of code using the cli-runtime library:

  • Register the well-know flags like --kubeconfig|--context|--namespace|--server|--token|... and pass their values to one or more client-go instances.
  • Look up cluster objects by their resources, kinds, and names with the full-blown support of familiar shortcuts like deploy for deployments or po for pods.
  • Read and kustomize YAML/JSON Kubernetes manifests into the corresponding Go structs.
  • Pretty-print Kubernetes objects as YAML, JSON (with JSONPath support), and even human-readable tables!

Interested? Then have a look at the usage examples below 😉

Read more

The Influence of Plumbing on Programming

I strive to produce concise but readable code. One of my favorite tactics - minimizing the number of local variables - usually can be achieved through minting or discovering higher-level abstractions and joining them together in a more or less declarative way.

Thus, when writing Go code, I often utilize io.Reader and io.Writer interfaces and the related io machinery. A function like io.Copy(w io.Writer, r io.Reader) can be a perfect example of such a higher-level abstraction - it's a succinct at devtime and efficient at runtime way to move some bytes from the origin r to the destination w.

But conciseness often comes at a price, especially in the case of I/O handling - getting a sneak peek at the data that's being passed around becomes much trickier in the code that relies on the Reader and Writer abstractions.

So, is there an easy way to see the data that comes out of readers or goes into writers without a significant code restructure or an introduction of temporary variables?

Read more

How To Call Kubernetes API using Go - Types and Common Machinery

The official Kubernetes Go client comes loaded with high-level abstractions - Clientset, Informers, Cache, Scheme, Discovery, oh my! When I tried to use it without learning the moving parts first, I ran into an overwhelming amount of new concepts. It was an unpleasant experience, but more importantly, it worsened my ability to make informed decisions in the code.

So, I decided to unravel client-go for myself by taking a thorough look at its components.

But where to start? Before dissecting client-go itself, it's probably a good idea to understand its two main dependencies - k8s.io/api and k8s.io/apimachinery modules. It'll simplify the main task, but that's not the only benefit. These two modules were factored out for a reason - they can be used not only by clients but also on the server-side or by any other piece of software dealing with Kubernetes objects.

How to learn Kubernetes API Go client.

Read more

Exploring Go net/http Package - On How Not To Set Socket Options

Go standard library makes it super easy to start an HTTP server:

package main

import "net/http"

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello there!\n"))
    })

    http.ListenAndServe(":8080", nil)
}

...or send an HTTP request:

package main

import "net/http"

func main() {
    resp, err := http.Get("http://example.com/")
    body, err := io.ReadAll(resp.Body)
}

In just ~10 lines of code, I can get a server up and running or fetch a real web page! In contrast, creating a basic HTTP server in C would take hundreds of lines, and anything beyond basics would require third-party libraries.

The Go snippets from above are so short because they rely on powerful high-level abstractions of the net and net/http packages. Go pragmatically chooses to optimize for frequently used scenarios, and its standard library hides many internal socket details behind these abstractions, making lots of default choices on the way. And that's very handy, but...

What if I need to fine-tune net/http sockets before initiating the communication? For instance, how can I set some socket options like SO_REUSEPORT or TCP_QUICKACK?

Read more

Disposable Local Development Environments with Vagrant, Docker, and Arkade

I use a (rather oldish) MacBook for my day-to-day development tasks. But I prefer keeping my host operating system free of development stuff. This strategy has the following benefits:

  • Increasing reproducibility of my code - it often happened to me in the past that some code worked on my machine but didn't work on others; usually, it was due to missing dependencies. Developing multiple projects on the same machine makes it harder to track what libraries and packages are required for what project. So, now I always try to have an isolated environment per project.
  • Testing code on the target platform - most of my projects have something to do with server-side and infra stuff; hence the actual target platform is Linux. Since I use a MacBook, I spend a lot of time inside virtual machines running the same operating system as my servers do. So, I'd need to duplicate the development tools from my macOS on every Linux OS I happen to use.
  • Keeping the host operating system clean and slim - even if I work on something platform-agnostic like a command-line tool, I prefer not to pollute my workstation with the dev tools and packages anyway. Projects and domains change often, and installing all the required stuff right into the host operating system would make it messy real quick.
  • Decreasing time to recover in case of machine loss - a single multi-purpose machine quickly becomes a snowflake host. Coming up with the full list of things to reinstall in the case of a sudden machine loss would be hardly feasible.

Since I usually work on several projects at the same time, I need not one but many isolated development environments. And every environment should be project-tailored, easy to spin up, suspend, and, eventually, dispose. I figured a way to achieve that by using only a few tools installed on my host operating system, and I'm going to share it here.

The approach may be helpful for folks using macOS or Linux:

  • to work on server-side and full-stack projects
  • to do Linux systems programming
  • to play with Cloud Native stack
  • to build some cool command-line tools.
Language logos

Read more