# Kubernetes first steps

Analysis paralysis

You know, that's a pretty nice blue.

One of my stated goals for this year, was to have a Kubernetes cluster running somewhere. As stated in the goals post, the deadline for this was April 1st. As of Saturday night, I’ve got one.

I ended up using Digital Ocean for this, taking advantage of the Changelog podcast’s sponsored signup offer, giving me two months to spend a $100 credit. While I’ve heard a lot of good things about Digital Ocean being very affordable, their managed Kubernetes offering can easily rack up some costs if you don’t take care. But now that I’ve got the cluster up and running, I’m at a bit of a loss. Just where do I go from here? What’s the next thing to do? What should I prioritize? And why didn’t I just start off with Minikube? ## Why didn’t I just use Minikube for now? Actually, let’s start with the last question: Why didn’t I just use Minikube? Honestly, it was mostly a moment of weakness. I was actually busy procrastinating when I thought, ‘hmm, I wonder how long it’d take to get set up with Digital Ocean’. In hindsight: sure, Minikube would have been cheaper (at least once my credit is up), but having the actual cluster feels more ‘real’. Like it’s something I need to take care of. It’s also available from anywhere and doesn’t use my machine’s processing power. ## What do I want to get out of this? So I got the cluster. Now what? Well, why did I want to do this? While the option to just spawn little demo applications and have them available from wherever is great, I’m more interested in looking at Kubernetes for the overarching architecture and operational side of things. Specifically, I’d like to use this as a way to familiarize myself with: • service meshes and API gateways • monitoring and telemetry • security best practices Each of these topics require a great deal of time and practice to master, but I’m not expecting to be a wizened monk any time soon. These are areas that I find fascinating and that I want to explore, but I cannot yet lay out any specific goals, as I need more time to research them. ## Wait, didn’t I have another goal for Kubernetes? Sure did! In addition to just getting a cluster ready, my second Kubernetes goal was to expose a Haskell app with an API. This is still on the cards, but isn’t due any time soon. This leans more towards the Nix and Haskell side of things, and less directly towards Kubernetes, so I can take some time to figure out how I want to do it. ## Now what? So where does this leave me? I think the biggest issue I have identified is my lack of knowledge. I’ve been working with RedHat’s OpenShift for the past year, and feel like I have a pretty good grasp of how that works, at least from the dev side. But when faced with that clean cluster, I froze. I didn’t know where to go, didn’t know what commands to run, or where to turn to for advice. So I need to formulate a plan. I think I would like to familiarize myself with Kubernetes more or less from the ground up. That means reading documentation, doing tutorials, and making sure my understanding is correct. Second: I would like to have a look at basic security measures and best practices. At the very least, I want to enable and configure RBAC. When I get this far, I think it would be an appropriate time to take a step back and reevaluate where I’m headed and recalculate. # Corecursion and anamorphisms Unfolding what lies beneath You know that feeling where you hear about something and you immediately need to look into it? I had that while listening to the most recent episode of Adam Gordon Bell’s Corecursive podcast today, where they were talking about where the name of the podcast came from. Up until then I had just assumed that corecursive meant mutually recursive1, but boy, was I wrong! ## The C-word According to Wikipedia, ‘corecursion is type of operation that is a dual to recursion’. This quickly gets very theoretical, but the long and short of it is that corecursion can be seen as a kind of opposite of recursion: Where recursion allows you to operate on arbitrarily complex data as long as you can reduce it down to a set of base cases, corecursion allows you to generate arbitrarily complex data when given a base case. Err … what? Think of it like this: Given a list of numbers (arbitrarily complex data), you can define a recursive function to sum all the numbers using simple base cases: is the list empty or are there more elements to add? Your language of choice may well have a sum function that does just this. If not, you can implement it with a fold or reduce. However, given a number, can you create a list that when summed up would equal this number? This would be a form of corecursion, where we take simple data (a number), and generate arbitrarily complex data based on the input (the resulting list of numbers). Let’s talk about fold functions specifically. As we talked about in a previous post on folding, fold functions are catamorphisms. They take a data structure and reduce it to a ‘lower’ form. The opposite of a catamorphism is an anamorphism2, and the opposite of a fold is an unfold (at least in Haskell). An anamorphism generates a sequence by repeatedly applying a function onto its previous result. According to Wikipedia, ‘the anamorphism of a coinductive type denotes the assignment of a coalgebra to its unique morphism to the final coalgebra of an endofunctor.’ Don’t worry: you needn’t understand that to understand unfolding and corecursion (I sure don’t). Instead, let’s try and get a feel of what we might use corecursion for. Returning to our previous example of destructuring a number into a list of terms, let’s look at a couple of ways to do it using unfold. First, let’s look at the unfoldr function itself. It is defined in the Data.List module, and its type signature is: unfoldr :: (b -> Maybe (a, b)) -> b -> [a] Given a function from b to Maybe (a, b) and a b, it will produce a list of a. If the function (let’s call it f) returns Just (x, y), x will be added to the result, and f will be called again with y. This continues until f returns Nothing, at which point unfoldr terminates, returning the list it has created. With this, we can create a function that takes an integral value and returns a list that when summed, is equal to the value we passed in. We’ll start with an easy variant that just destructures the input into ones. For simplicity’s sake, we’re ignoring non-negative numbers. module Unfold where import Data.List (unfoldr) unroll :: Integral a => a -> [a] unroll = unfoldr f where f n | n <= 0 = Nothing | otherwise = Just (1, n - 1) Easy enough. The function passed to unfoldr returns nothing if there is no more to sum. Otherwise, add 1 to the list, and call again with n-1. unroll 5 -- [1,1,1,1,1] unroll 0 -- [] But we can have some more fun with this. How about we try and destructure a number into a list of the pieces we’d need to create a binary representation of it? binary :: Integral a => a -> [a] binary = unfoldr f where f n | n <= 0 = Nothing | otherwise = let powerOfTwo = 2 ^ floor (logBase 2$ fromIntegral n)
in Just (powerOfTwo, n - powerOfTwo)

This is a bit more complicated, but only because we need to map the input value to a power of two. Luckily, we can use logBase 2 to get the exponent you’d need to get n, and then floor it to get the greatest integral exponent. This becomes the next entry to the list. What’s left gets passed in to the next application of the function.

binary 0 -- []
binary 5 -- [4,1]
binary 255 -- [128,64,32,16,8,4,2,1]
binary 256 -- [256]

Pretty neat, huh? What if we take it a step further and convert the number to its binary representation instead, as if it was base 2?

base2 :: Integral a => a -> a
base2 = sum . unfoldr f
where
f n
| n <= 0 = Nothing
| otherwise =
let exponent = floor (logBase 2 \$ fromIntegral n)
in Just (10 ^ exponent, n - 2 ^ exponent)

As you’d expect:

base2 0 -- 0
base2 5 -- 101
base2 10 -- 1010

Not too shabby at all.

Alright, I think we have had enough fun with corecursion for now. It’s been a very unexpected, but very insightful little journey, and I thank you for taking it with me. Until next time!

## Footnotes

1. I’m probably not the only one to do this. Wikipedia has a note under disambiguation on its article on corecursion that says ‘not to be confused with mutual recursion’

2. Similar to how your body can be in either anabolic or catabolic states, for all you fitness people out there.

# My first Emacs Lisp

Or: How I can't let Vim go

Not as hard as you might think.

In my previous post on reading the Emacs manual, I mentioned that there were a couple of things that I was missing from my editing workflow when using regular Emacs bindings. The most notable of which was the ability to kill up until a search hit. I knew that it’d be possible to write a function to do it, but I didn’t really know where to start, so I figured I’d just do it later.

In the Reddit thread about the post, user e17i gave me a little snippet to get me started for writing such a function. Turns out that was all I needed to get started. I put aside my fear of Lisp and went to work.

I ended up with three functions for Vim-like search movement: just exiting a search at a result, killing up to a search result, and copying up to a search result. Not particularly complicated, but I was so proud of myself when I got it working. The functions are included below for your viewing pleasure:

(defun isearch-vim-style-exit ()
"Move point to the start of the matched string, regardless
of search direction."
(interactive)
(when (eq isearch-forward t)
(isearch-repeat 'backward))
(isearch-exit))

(defun isearch-vim-style-kill ()
"Kill up to the search match when searching forward. When
searching backward, kill to the beginning of the match."
(interactive)
(isearch-vim-style-exit)
(call-interactively 'kill-region))

(defun isearch-vim-style-copy ()
"Copy up to the search match when searching forward. When
searching backward, copy to the start of the search match."
(interactive)
(isearch-vim-style-exit)
(call-interactively 'kill-ring-save)
;; set prefix arg to move point back to where search started
(let ((current-prefix-arg '(4)))
(call-interactively 'set-mark-command)))

I’ve mapped the functions to three separate key bindings in isearch-mode-map to make them easily accessible while searching:

(define-key isearch-mode-map
(kbd "<C-return>") 'isearch-vim-style-exit)

(define-key isearch-mode-map
(kbd "<M-return>") 'isearch-vim-style-kill)

(define-key isearch-mode-map
(kbd "<C-M-return>") 'isearch-vim-style-copy)

## Justification and motivation

In Emacs, when searching with isearch, when you ‘accept’ a match and move point there, Emacs will put you at the end of your matched text. Sometimes this is exactly what you want. Often, though, I find that I’d rather have point move to just before where the search string matches. This is how it works in Vim, and I have convinced myself that it’s also the standard way of moving cursors to searches in other text editors. In addition to just moving to a search result, I want the same pattern to apply for killing and for copying the text between your original cursor position and the search match. In short, these functions act on everything between your original cursor position and the start of the selected match, regardless of whether you search forwards or backwards.

This functionality is the same as regular isearch when searching backward, but when searching forward it’s the same as adding an extra C-r (isearch-backward) after picking a match.

## The Reddit snippet

The tip I got on Reddit gave me a little code snippet to start me off. At first I thought it was just what I wanted, but I realized later that it wasn’t quite what I was looking for. The original snippet is very symmetrical in that it goes to the end of a match when searching backward and to the start of a match when searching forward. However, I have found that I always want to move to the start of a match, no matter what side I come at it from. This may seem asymmetrical, but one nice thing about it is that it’ll work the same on any match, regardless of whether you’re searching forward or backward. This is especially useful if your search has wrapped.

The original snippet was:

(define-key isearch-mode-map (kbd "<C-return>")
(lambda () (interactive)
(isearch-repeat(if (eq isearch-forward nil)
'forward
'backward))
(isearch-exit)))

So even if it wasn’t quite what I needed, it gave me the tools necessary to start working on my own implementation, namely the isearch-repeat and isearch-exit functions and the isearch-forward variable. With this I had all I needed to start playing around with the functionality myself.

## Elisp crash course

If you’ve never encountered Lisp before, here’s a short (and very incomplete) introduction to Emacs Lisp. Do bear in mind that this is the first Emacs Lisp code I’ve written myself, so I’m probably missing a lot of context and nuance. If you find any errors, please do tell me; I’ll be very grateful. For a more complete introduction to the language, check out the Emacs Lisp manual.

defun [name] (args)
The first line of the function contains the keyword defun, signifying that we’re defining a function, the name of the function, and a list of parameters. In all the above functions, the parameter list is empty, so it’s just a set of empty parentheses (()).

After the first line of each function, I’ve added a string describing what the function does. This works as documentation. When looking up the function in Emacs (C-h f <name of function>), this text gets displayed. It’s not a requirement to put into a function, but it’s nice to have when you need to look things up.

Furthermore, lines starting with ; are standard code comments, such as the one about setting current~prefix-arg.

(interactive) and call-interactively

In Emacs Lisp, (interactive) turns a Lisp function into a command. In short, this means that you can assign it to a key sequence and call it from anywhere in Emacs by using M-x. Similarly, call-interactively is used to call interactive commands that take arguments. These commands can either be given explicit arguments and called like normal functions, or we can use call-interactively. When doing the latter, certain parameters can be passed implicitly. For instance kill-region needs two arguments BEG and END to know what region to operate on. When called interactively, BEG and END get the values of point and mark, so we don’t need to pass them explicitly. For more information about the commands and interactive, check out the Emacs Lisp manual, chapter 21.2.

What’s with the quotes?

Again, the manual (chapter 10.3) has info on what the single quotes do on a deeper level, but in our case, what we want is simply to pass the quoted function as an argument, and not the result of evaluating that function. In short, it’s passing a function pointer, rather than the result of a function.

when

when is the Lisp way to only evaluate some code when a condition holds true. It’s similar to an if expression, but an if expression needs an else-clause to run if the provided condition doesn’t hold true. In languages like Python, Rust, JavaScript, etc., it’s the equivalent of just using an if expression/statement without an else-clause.

let and current-prefix-arg

There’s not a whole lot of variable binding going on in these functions. The one place it’s done is in the isearch-vim-style-copy function. As you might expect, the let keyword assigns a value to a variable (in this case (4) to current-prefix-arg). In Emacs Lisp, variables can have global scope, so the let binding ensures that the variable is only bound to this value within this scope. The variable current-prefix-arg is used to augment set-mark-command. We do this to move point back to where the search started after copying the region.

And that’s the story of how I got started writing Lisp. It’s dead simple, but I’ve got a taste for it now, and I think I like it. … yeah. I think I like it.

First Prev Page 1 Next Last