k3s Installation


k8s -> k3s

In March 2021 I first posted about setting up a Pi cluster. Initially, I had tried (and subsequently failed) to set up a full-fledged Kubernetes (k8s) cluster. Then, I discovered k3s, a lightweight distribution of Kubernetes designed for edge environments (which also works on ARM devices). It ships with an embedded sqlite3 database as default storage when setting up a server node but it is trivial to use etcd3/MySQL/PostgreSQL if desired. I was very pleased with how simple the k3s launcher is and it made the entire installation experience straightforward.

Architecture

k3s architecture

I based my installation on the single-server setup with an embedded database documented by Rancher. In my configuration, node1 is running k3s in server mode and node[2:6] is running it in agent mode. Here’s a breakdown of what I had to do to install k3s.

Installation

Note During the installation process I encountered this error:


[INFO]  Failed to find memory cgroup, you may need to add "cgroup_memory=1 cgroup_enable=memory" to your linux cmdline (/boot/cmdline.txt on a Raspberry Pi)

To resolve the issue, I added the recommended flags to the linux cmdline as the error message suggests on each node before installing k3s.

On node1 I installed k3s.


curl -sfL <<<<<<<<https://get.k3s.io>>>>>>>> | sh -

Then, I started k3s in server mode:


k3s server

The other nodes were even easier. When it’s installed and run, k3s will check for K3S_URL and K3S_TOKEN environment variables. If they are set, then k3s assumes this is a worker node and starts up in agent mode. As root, I copied the token value from node1:/var/lib/rancher/k3s/server/node-token and used the following command to install k3s and automatically register the node with the server running on node1 (full token values omitted):


curl -sfL <<<<<<<<https://get.k3s.io>>>>>>>> | K3S_URL=<<<<<<<<https://node1:6443>>>>>>>> K3S_TOKEN=K109...f2bb::server:bc1...2e9 sh -

Result

I checked out the results of my installation on node1 using k3s kubectl get nodes.

k3s server and nodes listed using `kubectl get nodes` command


Authored by Michael Lamb.
Published on 20 January 2022.


Using the jekyll-seo-tag plugin


Social Cards

Without using SEO tags to serve metadata for social media, links will appear plainly and without much to draw the eye. I wanted to change how my blog post presents on social media, so I searched for how to add SEO tags to my existing blog. Since my blog is built on GitHub Pages and thus uses Jekyll, I was able to install the jekyll-seo-tag plugin. Here’s how.

Install jekyll-seo-tag

In the Gemfile of the Jekyll project add the following line:


gem 'jekyll-seo-tag'

The Gemfile is used when testing my blog locally using bundle exec jekyll serve.

In the _config.yml of the project, find the plugins_dir section and add the following line:


- jekyll-seo-tag

A GitHub Actions workflow will execute builds and include the jekyll-seo-tag plugin now configured in _config.yml when generating the static assets during the build process.

Add SEO frontmatter

My blog is a fork of gh-pages-blog. jekyll-seo-tag requires frontmatter to be added to two files, located in the _includes/head directory. I simply added the following frontmatter expression in the <head> tags of page.html (which represents the parent blog) and post.html (which represents any individual post):


{ % seo % }

NOTE I added spaces on either side of the Liquid expression (the braces and percent symbol) to avoid Jekyll from replacing it with the SEO data it generates during the build process of the blog. Check out more about static files from Jekyll.

After this change in the static files, I am now able to add an image property to any post to specify a social card. Any post without a value for image will use a default social card, which is also the card used for the parent blog. Posts are written in Markdown (GFM) and the property values are written in a comment header. An example below is how Jekyll knows what to call this post.


---

layout : post
title : Using the jekyll-seo-tag plugin
category : social

image : "/seo/2022-01-11.png"
---

Resources

I put all this together thanks to this very helpful blog post from Meagan Waller.

These pages are useful for testing meta tags:


Authored by Michael Lamb.
Published on 11 January 2022.
Category: Social


Swagger UI and Go


What is Swagger

Swagger is a suite of tools to generate and represent RESTful APIs. It promotes the OpenAPI 2.0 spec which describes an API using JSON representation in a specification file called swagger.json. Since YAML is a superset of JSON, the specification file can conform to YAML.

In my projects, I want to be able to generate swagger.json from an annotated codebase to serve using Swagger UI.

Swagger Tools

  • go-swagger
  • enables generating a Go client or server from an existing Swagger spec
  • can scan an annotated codebase and generate a Swagger spec
  • Swagger UI
  • serves visual documentation of an API based on a given Swagger spec

Generating a Swagger spec

go-swagger is a CLI tool. The following is an example command for generating a Swagger spec


swagger generate spec -o ./swagger.json

Installing Swagger UI in a Go application

Based on the installation docs Swagger UI can be served as part of an application using plain HTML/CSS/JS.

How to serve plain HTML Swagger UI

The folder /dist includes all the HTML, CSS and JS files needed to run SwaggerUI on a static website or CMS, without requiring NPM.

  1. Download the latest release.
  2. Copy the contents of the /dist folder to your server.
  3. Open index.html in your HTML editor and replace “««««https://petstore.swagger.io/v2/swagger.json”»»»» with the URL for your OpenAPI 3.0 spec.

What is the purpose

Reading and writing code is a big part of my daily life and the opportunity to put things together in Go is a fun break from the enterprise application development I do at C Spire. Swagger UI is a tool we use in our Spring/Spring Boot applications to easily share our API documentation with each other and the teams which integrate with us. This post is my personal documentation for how to integrate a tool I know and love in a new programming language.


Authored by Michael Lamb.
Published on 22 October 2021.
Category: Golang


Using OAuth2 in Go


Reference

Go Reference - Http "net/http"

Go Reference - OAuth2 for Go "golang.org/x/oauth2"

Go Reference - OAuth2 (Google APIs) "golang.org/x/oauth2/google"

Authorization Server Flow

Diagram of OAuth authorization flow

  1. A Go client will serve a redirect to a browser. This redirect will include a random state variable and a following redirect URL back to the Go client from the authorizing server.
  2. The user enters credentials into the browser (user agent).
  3. The user agent send credentials to the authorization server (u := config.AuthCodeURL(oauthState) prepares the URL, a call to http.Redirect() accepts u and the server redirects the browser).
  4. Authorization server returns an authorization code (r.FormValue("code")) via the redirection URL provided in step 1.
  5. The Go client will exchange the authorization code for an access token (the client will store the access token in a cookie).
  6. The Go client uses the access token to access scoped resource servers.

A Simple OAuth Implementation

Download the oauth2 library for import.


go get golang.org/x/oauth2

The app structure I followed is pretty simple:

1. In main.go use net/http to start a server

main.go


package main

import (
"fmt"
"log"
"net/http"

"foundry-automations/handlers"
)

func main() {
server := &http.Server{
    Addr:    fmt.Sprintf(":8000"),
    Handler: handlers.New(),
}

log.Printf("Starting HTTP Server. Listening at %q", server.Addr)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
    log.Printf("%v", err)
  } else {
    log.Println("Server closed!")
  }
}

2. Create a module /handlers and add two .go files

  • base.go
  • <service>.go where <service> is a meaningful representation of the authorization server you are relying on (e.g., google apis)

3. Create a Handler

In base.go create func New() http.Handler {} and set up a mux with two handlers, "/auth/<service>/login" and "/auth/<service>/callback", then return mux. The helper functions passed as the second argument of mux.HandleFunc will live in your <service>.go implementation

base.go


package handlers

import (
{
    "net/http"
)

func New() http.Handler {
mux := http.NewServeMux()
// Root
mux.Handle("/", http.FileServer(http.Dir("templates/")))

// // OauthGoogle
// mux.HandleFunc("/auth/google/login", oauthGoogleLogin)
// mux.HandleFunc("/auth/google/callback", oauthGoogleCallback)
mux.HandleFunc("/auth/pco/login", pcoLogin)
mux.HandleFunc("/auth/pco/callback", pcoCallback)

return mux
  }
}


Authored by Michael Lamb.
Published on 07 October 2021.
Category: Golang


Error Handling


I am continuing to learn more about Go and its history.

The language follows idioms.

Idioms of Go

  • get the job done
  • one way of doing things
  • be explicit
  • build things by composing them

Fundamentals of Software Development

One of the most important aspects of my job is writing meaningful error messages. It’s something a lot of people don’t ever stop to consider: where do error messages come from?

Software developers create tools used by others and by nature of the broad interconnected tooling systems in computers and the internet at-large it cannot be difficult to imagine how collaboration is vitally important. Error messaging is how a software developer communicates with their users about expected and unexpected behavior. I believe that software which does not provide meaningful error messages (here’s what went wrong, here’s how to fix it) is not well-written software.

Error handling is a fundamental skill for software engineering.

Error Handling in Go

Review the following go packages for an example of returning an error. Source from golang.org

greetings.go


package greetings

import (
    "errors"
    "fmt"
)

// Hello returns a greeting for the named person.
func Hello(name string) (string, error) {
    // If no name was given, return an error with a message.
    if name == "" {
        return "", errors.New("empty name")
    }

    // If a name was received, return a value that embeds the name
    // in a greeting message.
    message := fmt.Sprintf("Hi, %v. Welcome!", name)
    return message, nil
}

main.go


package main

import (
    "fmt"
    "log"

    "example.com/greetings"
)

func main() {
    // Set properties of the predefined Logger, including
    // the log entry prefix and a flag to disable printing
    // the time, source file, and line number.
    log.SetPrefix("greetings: ")
    log.SetFlags(0)

    // Request a greeting message.
    message, err := greetings.Hello("")
    // If an error was returned, print it to the console and
    // exit the program.
    if err != nil {
        log.Fatal(err)
    }

    // If no error was returned, print the returned message
    // to the console.
    fmt.Println(message)
}

Notes

  • the log and errors packages are golang builtins
  • I would want to use constants for error messages, e.g., errors.New(UNEXPECTED_ERROR) or errors.New(EXTERNAL_SERVICE_ERROR) however Go does not support this pattern since it alternates between camelCase and PascalCase to denote variables which are global to a file vs. global to a package (respectively)

Willow

willow


Authored by Michael Lamb.
Published on 04 October 2021.
Category: Golang



About michaellamb.dev

Michael Lamb is a software engineer working at C Spire. If you have a blog-specific inquiry please create a new issue on GitHub. Feel free to fork this blog and build your own!

Get to know who I am in my first post Hello, World!

© Copyright 2021-2025
Michael Lamb Blog