惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
L
LangChain Blog
IT之家
IT之家
S
SegmentFault 最新的问题
博客园 - 三生石上(FineUI控件)
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
The Blog of Author Tim Ferriss
Blog — PlanetScale
Blog — PlanetScale
N
Netflix TechBlog - Medium
U
Unit 42
B
Blog RSS Feed
GbyAI
GbyAI
Microsoft Security Blog
Microsoft Security Blog
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
T
Threatpost
C
CERT Recently Published Vulnerability Notes
Cisco Talos Blog
Cisco Talos Blog
The Register - Security
The Register - Security
Vercel News
Vercel News
S
Schneier on Security
Spread Privacy
Spread Privacy
C
Cyber Attacks, Cyber Crime and Cyber Security
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
博客园 - 叶小钗
雷峰网
雷峰网
博客园_首页
人人都是产品经理
人人都是产品经理
P
Palo Alto Networks Blog
The Hacker News
The Hacker News
T
Tor Project blog
L
Lohrmann on Cybersecurity
Know Your Adversary
Know Your Adversary
D
Darknet – Hacking Tools, Hacker News & Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
P
Privacy International News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
T
Tenable Blog
V
Vulnerabilities – Threatpost
大猫的无限游戏
大猫的无限游戏
博客园 - 【当耐特】
V
V2EX
Security Latest
Security Latest
A
About on SuperTechFans
Cloudbric
Cloudbric
S
Security Affairs
MongoDB | Blog
MongoDB | Blog
Y
Y Combinator Blog
Martin Fowler
Martin Fowler
TaoSecurity Blog
TaoSecurity Blog

Ivan on Containers, Kubernetes, and Server-Side

A grounded take on agentic coding for production environments Server-Side Playgrounds Reimagined: Build, Boot, and Network Your Own Virtual Labs [not a] Kubernetes 101 - Pods, Deployments, and Services As an Attempt To Automate Age-Old Infra Patterns JavaScript or TypeScript? How To Benefit From the Dichotomy On Software Design... and Good Writing Building a Firecracker-Powered Course Platform To Learn Docker and Kubernetes How To Publish a Port of a Running Container What Actually Happens When You Publish a Container Port A Visual Guide to SSH Tunnels: Local and Remote Port Forwarding Debugging Containers Like a Pro Docker: How To Debug Distroless And Slim Containers How To Extract Container Image Filesystem Using Docker | iximiuz Labs In Pursuit of Better Container Images: Alpine, Distroless, Apko, Chisel, DockerSlim, oh my! How To Start Programming In Go: Advice For Fellow DevOps Engineers Kubernetes Ephemeral Containers and kubectl debug Command How To Develop Kubernetes CLIs Like a Pro Docker Container Commands Explained: Understand, Don't Memorize | iximiuz Labs Learning Docker with Docker - Toying With DinD For Fun And Profit How To Extend Kubernetes API - Kubernetes vs. Django The Influence of Plumbing on Programming How To Call Kubernetes API using Simple HTTP Client Kubernetes API Basics - Resources, Kinds, and Objects OpenFaaS - Run Containerized Functions On Your Own Terms Learning Containers From The Bottom Up Docker Containers vs. Kubernetes Pods - Taking a Deeper Look | iximiuz Labs Learn-by-Doing Platforms for Dev, DevOps, and SRE Folks How HTTP Keep-Alive can cause TCP race condition How to Work with Container Images Using ctr | iximiuz Labs Multiple Containers, Same Port, no Reverse Proxy... Exploring Go net/http Package - On How Not To Set Socket Options Disposable Local Development Environments with Vagrant, Docker, and Arkade DevOps, SRE, and Platform Engineering My Choice of Programming Languages Prometheus Is Not a TSDB How to learn PromQL with Prometheus Playground Prometheus Cheat Sheet - Basics (Metrics, Labels, Time Series, Scraping) Rust - Writing Parsers With nom Parser Combinator Framework pq - parse and query log files as time series Prometheus Cheat Sheet - Moving Average, Max, Min, etc (Aggregation Over Time) Prometheus Cheat Sheet - How to Join Multiple Metrics (Vector Matching) The Need For Slimmer Containers Understanding Rust Privacy and Visibility Model Bridge vs. Switch: Takeaways from a Real Data Center Tour | iximiuz Labs From LAN to VXLAN: Networking Basics for Non-Network Engineers | iximiuz Labs KiND - How I Wasted a Day Loading Local Docker Images Go, HTTP handlers, panic, and deadlocks Exploring Kubernetes Operator Pattern Making Sense Out Of Cloud Native Buzz Service Discovery in Kubernetes: Combining the Best of Two Worlds API Developers Never REST How Container Networking Works: Building a Bridge Network From Scratch | iximiuz Labs Traefik: canary deployments with weighted load balancing Service Proxy, Pod, Sidecar, oh my! You Need Containers To Build Images You Don't Need an Image To Run a Container Not Every Container Has an Operating System Inside Working with container images in Go Master Go While Learning Containers Implementing Container Runtime Shim: Interactive Containers How to use Flask with gevent (uWSGI and Gunicorn editions) My 10 Years of Programming Experience Implementing Container Runtime Shim: First Code Implementing Container Runtime Shim: runc Kubernetes Repository On Flame Dealing with process termination in Linux (with Rust examples) conman - [the] Container Manager: Inception Journey From Containerization To Orchestration And Beyond Linux PTY - How docker attach and docker exec Commands Work Inside Illustrated introduction to Linux iptables From Docker Container to Bootable Linux Disk Image Пишем свой веб-сервер на Python: протокол HTTP 9001 способ создать веб-сервер на Python Explaining async/await in 200 lines of code Explaining event loop in 100 lines of code Save the day with gevent Пишем свой веб-сервер на Python: процессы, потоки и асинхронный I/O Truly optional scalar types in protobuf3 (with Go examples) Node.js Writable streams distilled Node.js Readable streams distilled How to on starting processes (mostly in Linux) Дайджест интересных ссылок – Июль 2016 Пишем свой веб-сервер на Python: сокеты Наследование в JavaScript Мастерить!
How To Call Kubernetes API from Go - Types and Common Machinery
Ivan Velichko · 2022-01-24 · via Ivan on Containers, Kubernetes, and Server-Side

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.

API Resources, Kinds, and Objects

First, a quick recap. Familiarity with the following concepts is vital for the success of the further discussion:

  • Resource Type - loosely, an entity served by a Kubernetes API endpoint: pods, deployments, configmaps, etc.
  • API Group - resource types are organized into versioned logical groups: apps/v1, batch/v1, storage.k8s.io/v1beta1, etc.
  • Object - a resource instance - every API endpoint deals with objects of a certain resource type.
  • Kind - every object returned or accepted by the API must conform to an object schema - a certain composition of attributes defined by its kind: Pod, Deployment, ConfigMap, etc.

It's also important to differentiate between objects in a broad sense and Kubernetes "first-class" Objects - persistent entities like Pod, Service, or Secret serving as a record of intent for the cluster. While every API object must have an API version and kind attributes for the sake of its serialization and deserialization, not every API object is a "first-class" Kubernetes Object.

Kubernetes API - resource types, kinds, objects

Module k8s.io/api

Go is a statically typed programming language. So, where do all the structs corresponding to Pods, ConfigMaps, Secrets, and other first-class Kubernetes Objects live? Right, in k8s.io/api.

Despite the loose naming, the k8s.io/api module seems to be solely for API type definitions. It's full of concrete structs closely resembling those YAML manifests we all know and love:

package main

import (
  "fmt"

  appsv1 "k8s.io/api/apps/v1"
  corev1 "k8s.io/api/core/v1"
)

func main() {
  deployment := appsv1.Deployment{
    Spec: appsv1.DeploymentSpec{
      Template: corev1.PodTemplateSpec{
        Spec: corev1.PodSpec{
          Containers: []corev1.Container{
            { Name:  "web", Image: "nginx:1.21" },
          },
        },
      },
    },
  }

  fmt.Printf("%#v", &deployment)
}

The module defines not only the top-level Kubernetes Objects like the Deployment above but also numerous auxiliary types for their inner attributes:

// PodSpec is a description of a pod.
type PodSpec struct {
  Volumes []Volume `json:"volumes,omitempty" patchStrategy:"merge,retainKeys" patchMergeKey:"name" protobuf:"bytes,1,rep,name=volumes"`
  
  InitContainers []Container `json:"initContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,20,rep,name=initContainers"`
    
  Containers []Container `json:"containers" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=containers"`

  EphemeralContainers []EphemeralContainer `json:"ephemeralContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,34,rep,name=ephemeralContainers"`

  RestartPolicy RestartPolicy `json:"restartPolicy,omitempty" protobuf:"bytes,3,opt,name=restartPolicy,casttype=RestartPolicy"`
    
  ...
}

All the structs defined in the k8s.io/api module come with json and protobuf annotations. But be careful:

  • Marshaling into JSON is supported.
  • Protobuf serialization is discouraged - produced results will likely be incompatible with the existing API servers (see README for more).

Summarizing, the k8s.io/api module is:

  • Huge - 1000+ structs describing Kubernetes API objects.
  • Simple - almost no algorithms, only "dumb" data structures.
  • Useful - its data types are used by clients, servers, controllers, etc.

Module k8s.io/apimachinery

Unlike the narrowly-scoped k8s.io/api module, the k8s.io/apimachinery module is rather manifold. The README describes its purpose as:

This library is a shared dependency for servers and clients to work with Kubernetes API infrastructure without direct type dependencies. Its first consumers are k8s.io/kubernetes, k8s.io/client-go, and k8s.io/apiserver.

It'd be hard to cover all the responsibilities of the apimachinery module in a single post without being too shallow. So instead, I'll talk about packages, types, and functionality from this module I stumble upon in the wild the most often.

Useful Structs and Interfaces

While the k8s.io/api module focuses on the concrete higher-level types like Deployments, Secrets, or Pods, the k8s.io/apimachinery is a home for lower-level but more universal data structures.

For instance, all these common attributes of Kubernetes Object: apiVersion, kind, name, uid, ownerReferences, creationTimestamp, etc. If I were to construct my own Kubernetes Custom Resource, I wouldn't need to define data types for these attributes myself - thanks to the apimachinery module.

The k8s.io/apimachinery/pkg/apis/meta package defines two handy structs - TypeMeta and ObjectMeta that can be embedded into a user-defined struct, making it look much like any other Kubernetes Object.

Additionally, the TypeMeta and ObjectMeta structs implement meta.Type and meta.Object interfaces that can be used to point to any compatible object in a generic way.

How to represent data structures from Kubernetes manifests as Go types.

Click here for the uncompressed version of the diagram.

Another handy type defined in the apimachinery module is the interface runtime.Object. Due to its simplistic definition, it may look useless:

// pkg/runtime

type Object interface {
  GetObjectKind() schema.ObjectKind
  DeepCopyObject() Object
}

But in reality, it's used a lot! Kubernetes code was written long before Go got the support of true generics. So, the runtime.Object is much like the traditional interface{} workaround - it's a generic interface that is widely type-asserted and type-switched in the codebase. And the actual type can be obtained by checking the kind of the underlying object.

More useful apimachinery types:

Unstructured struct

Yes, you've heard it right. But jokes aside, it's another important and widely used data type.

type Unstructured struct 🤔

Ok, what's next, Kubernetes? Untyped type? Unsigned sign?

— Ivan Velichko (@iximiuz) January 23, 2022

Working with Kubernetes Objects using concrete k8s.io/api types is convenient, but what if:

  • You need to work with Kubernetes Objects in a generic way?
  • You don't want to or cannot depend on the api module?
  • You need to work with Custom Resources that aren't defined in the api module?

The unstructured.Unstructured struct for the rescue! This struct allows objects that do not have Go structs registered to be manipulated as generic JSON-like objects:

type Unstructured struct {
  // Object is a JSON compatible map with
  // string, float, int, bool, []interface{}, or
  // map[string]interface{} children.
  Object map[string]interface{}
}

// And for the list of objects you can 
// use the UnstructuredList struct.
type UnstructuredList struct {
  Object map[string]interface{}

  Items []Unstructured
}

Under the hood, these two structs are just map[string]interface{}. However, they come with a bunch of handy methods simplifying nested attribute access and JSON marshaling/unmarshaling.

Type Conversion - Unstructured to Typed and vice versa

Naturally, a need for converting an unstructured object into a struct of a concrete k8s.io/api type (or vice versa) can arise. The runtime.UnstructuredConverter interface and its default implementation DefaultUnstructuredConverter can help you with that:

type UnstructuredConverter interface {
  ToUnstructured(obj interface{}) (map[string]interface{}, error)
  FromUnstructured(u map[string]interface{}, obj interface{}) error
}

Object Serialization to JSON, YAML, or Protobuf

Another tedious task when working with an API from a statically typed language is marshaling and unmarshaling data structures into and from their wire representation.

A non-trivial amount of apimachinery code is dedicated to this task:

// pkg/runtime

// Encoder writes objects to a serialized form
type Encoder interface {
  Encode(obj Object, w io.Writer) error
  Identifier() Identifier
}

// Decoder attempts to load an object from data.
type Decoder interface {
  Decode(
    data []byte,
    defaults *schema.GroupVersionKind,
    into Object
  ) (Object, *schema.GroupVersionKind, error)
}

type Serializer interface {
  Encoder
  Decoder
}

Noticed these Object's in the snippet above? Yep, those are runtime.Objects aka Kind-able interface{} instances.

Scheme and RESTMapper

The runtime.Scheme concept pops up here and there when working with client-go, especially when writing controllers (or operators 🤔) that deal with custom resources.

It took me a while to understand its purpose. However, approaching things in the right order helped.

Think about the potential implementation of Unstructured to Typed conversion: there is a JSON-like object, and a corresponding object of some concrete k8s.io/api type needs to be created from it. Probably, the very first step would be to figure out how to create an empty instance of the typed object using the kind string.

A naive approach could look like a huge switch statement over all possible kinds (and API groups, actually):

import (
  appsv1 "k8s.io/api/apps/v1"
  corev1 "k8s.io/api/core/v1"
)

func New(apiVersion, kind string) runtime.Object {
  switch (apiVersion + "/" + kind) {  
  case: "v1/Pod":
    return &corev1.Pod{}
  case: "apps/v1/Deployment":
    return &appsv1.Deployment{}
  }
  ...
}

A smarter approach is to use reflection. Instead of the switch, a map[string]reflect.Type can be maintained for all registered types:

type Registry struct {
  map[string]reflect.Type types
}

func (r *Registry) Register(apiVersion, kind string, typ reflect.Type) {
  r.types[apiVersion + "/" + kind] = typ
}

func (r *Registry) New(apiVersion, kind string) runtime.Object {
  return r.types[apiVersion + "/" + kind].New().(runtime.Object)
}

The advantage of this approach is that it requires no code generation and that new type mappings can be added at runtime.

Now, consider a deserialization problem: a piece of YAML or JSON needs to be converted into a Typed object. The very first step - object creation - will be very similar.

Turns out, creating empty objects by their API Groups and kinds is such a frequent task that it got its own component in the apimachinery module - runtime.Scheme:

// Scheme defines methods for serializing and deserializing API objects, a type
// registry for converting group, version, and kind information to and from Go
// schemas, and mappings between Go schemas of different versions. 
type Scheme struct {
  gvkToType map[schema.GroupVersionKind]reflect.Type
  
  typeToGVK map[reflect.Type][]schema.GroupVersionKind
  
  unversionedTypes map[reflect.Type]schema.GroupVersionKind
  
  unversionedKinds map[string]reflect.Type

  ...
}

The runtime.Scheme struct is such a registry containing the kind to type and type to kind mappings for all kinds of Kubernetes objects.

The runtime.Scheme struct is actually very powerful - it has a whole bunch of methods and implements some foundational interfaces like:

// ObjectTyper contains methods for extracting 
// the APIVersion and Kind of objects.
type ObjectTyper interface {
  ObjectKinds(runtime.Object) ([]schema.GroupVersionKind, bool, error)
  Recognizes(gvk schema.GroupVersionKind) bool
}

// ObjectCreater contains methods for instantiating
// an object by kind and version.
type ObjectCreater interface {
  New(kind schema.GroupVersionKind) (out Object, err error)
}

However, the runtime.Scheme is not almighty. It has mappings from kinds to types, but what if instead of kind only the resource name is known?

That's where the RESTMapper kicks in:

type RESTMapper interface {
  // KindFor takes a partial resource and returns the single match.  Returns an error if there are multiple matches
  KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error)

  // KindsFor takes a partial resource and returns the list of potential kinds in priority order
  KindsFor(resource schema.GroupVersionResource) ([]schema.GroupVersionKind, error)

  ...
  
  ResourceSingularizer(resource string) (singular string, err error)
}

The RESTMapper is also some sort of a registry. However, it maintains mapping of resources to kinds. So, feeding a string like apps/v1/deployments to the mapper gives the API Group apps/v1 and the kind Deployment. The RESTMapper also can deal with resource shortcuts and singularization: po, pod, and pods can be registered as aliases for the same resource.

Kubernetes RESTMapper and runtime.Scheme

Field and Label Selectors

Types, creation, and matching logic for fields and labels also live in the apimachinery module. For instance, here is what can be done with the k8s.io/apimachinery/pkg/labels package:

lbl := labels.Set{"foo": "bar"}
sel, _ = labels.Parse("foo==bar")
if sel.Matches(lbl) {
  fmt.Printf("Selector %v matched label set %v\n", sel, lbl)
}

API Error Handling

Working with the Kubernetes API in code is impossible without handling its errors properly. The API server might be completely gone, requests may be unauthorized, objects might be missing, and concurrent updates may conflict. Luckily, the k8s.io/apimachinery/pkg/api/errors package defines some handy utility functions to deal with the API errors. Here is an example:

_, err = client.
  CoreV1().
  ConfigMaps("default").
  Get(
    context.Background(),
    "this_name_definitely_does_not_exist",
    metav1.GetOptions{},
  )
if !errors.IsNotFound(err) {
  panic(err.Error())
}

Miscellaneous Utils

Last but not least, the apimachinery/pkg/util package is full of useful stuff. Here are some examples:

  • util/wait package eases the task of waiting for resources to appear or to be gone, with retries and proper backoff/jitter implementation.
  • util/yaml helps to unmarshal YAML or convert it into JSON.

Summarizing

The k8s.io/api and k8s.io/apimachinery packages is a good starting place to learn how to work with Kubernetes objects in Go. If you need to write your first controller, jumping straight to client-go, or even to controller-runtime or kubebuilder will likely make the learning experience too rough - there might be way too many knowledge gaps. However, taking a look and playing around with the api and apimachinery packages first will help you keep peace of mind during the rest of the journey 🧘

Stay tuned

It's been three articles, and I haven't touched client-go yet. Next time, it'll be an article about the client, I promise!